12 KiB
12 KiB
Textpipe SMS API Gateway - Implementation Plan
Overview
Build an Android SMS API Gateway with an embedded HTTP server (Ktor), dual SIM support, local message database, and background service.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ MainActivity │
│ (Compose UI + Permissions) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ TextpipeService │
│ (Foreground Service + Lifecycle) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ TextpipeServer (Ktor CIO) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ /api/status │ │/api/sms/send│ │/api/sms/messages│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ SimManager │ │ SmsSender │ │ SmsRepository │
│ (API Keys) │ │ (SmsManager)│ │ (Room + Send) │
└─────────────┘ └─────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────────────────────┐
│ AppDatabase (Room) │
│ sms_messages table │
└─────────────────────────────────┘
Package Structure
sh.sar.textpipe/
├── MainActivity.kt (modify)
├── TextpipeApplication.kt (create)
├── data/
│ ├── db/
│ │ ├── AppDatabase.kt
│ │ ├── SmsMessageDao.kt
│ │ └── SmsMessageEntity.kt
│ ├── model/
│ │ └── ApiModels.kt
│ └── repository/
│ └── SmsRepository.kt
├── server/
│ ├── TextpipeServer.kt
│ ├── auth/
│ │ └── ApiKeyAuth.kt
│ └── routes/
│ ├── SmsRoutes.kt
│ └── StatusRoutes.kt
├── service/
│ ├── TextpipeService.kt
│ ├── BootReceiver.kt
│ ├── SmsReceiver.kt
│ └── SmsSender.kt
├── sim/
│ └── SimManager.kt
├── root/
│ └── RootManager.kt
└── ui/
├── MainScreen.kt
└── MainViewModel.kt
API Endpoints
Authentication
- API key in header:
Authorization: Bearer <key>orX-API-Key: <key> - Each SIM slot has its own unique API key (auto-generated, persistent)
- API key determines which SIM sends the message
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/status |
No | Server health, SIM info, uptime |
| POST | /api/sms/send |
Yes | Send SMS |
| GET | /api/sms/messages |
Yes | List messages (optional ?type=sent|received) |
| GET | /api/sms/status/{id} |
Yes | Get specific message status |
Request/Response Examples
POST /api/sms/send
// Request
{ "to": "+1234567890", "text": "Hello World" }
// Response
{ "id": 1, "status": "pending", "simSlot": 0 }
GET /api/sms/messages
[
{
"id": 1,
"type": "sent",
"address": "+1234567890",
"text": "Hello World",
"simSlot": 0,
"status": "delivered",
"timestamp": 1707148800000,
"deliveryTimestamp": 1707148805000
}
]
GET /api/status
{
"running": true,
"port": 8080,
"uptime": 3600000,
"sims": [
{ "slot": 0, "carrier": "Carrier1", "number": "+1...", "hasApiKey": true },
{ "slot": 1, "carrier": "Carrier2", "number": "+1...", "hasApiKey": true }
]
}
Dependencies to Add
libs.versions.toml additions
[versions]
ktor = "3.0.3"
room = "2.6.1"
kotlinxSerialization = "1.7.3"
datastorePreferences = "1.1.1"
ksp = "2.0.21-1.0.28"
coroutines = "1.9.0"
[libraries]
ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-status-pages = { group = "io.ktor", name = "ktor-server-status-pages", version.ref = "ktor" }
ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors", version.ref = "ktor" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
Permissions Required
<!-- SMS -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<!-- Phone/SIM -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<!-- Network -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Background Service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Battery -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Implementation Order
Phase 0: Build Configuration
gradle/libs.versions.toml- Add all new dependenciesbuild.gradle.kts(root) - Add KSP and serialization pluginsapp/build.gradle.kts- Add dependencies and pluginsAndroidManifest.xml- Add permissions, services, receivers
Phase 1: Data Layer
data/model/ApiModels.kt- Request/response data classesdata/db/SmsMessageEntity.kt- Room entitydata/db/SmsMessageDao.kt- Database operationsdata/db/AppDatabase.kt- Room database
Phase 2: Core Managers
sim/SimManager.kt- SIM detection, API key managementroot/RootManager.kt- Root access and iptables port redirect
Phase 3: SMS Operations
service/SmsSender.kt- Send SMS with delivery trackingdata/repository/SmsRepository.kt- Combine database + sendingservice/SmsReceiver.kt- Receive incoming SMS
Phase 4: HTTP Server
server/auth/ApiKeyAuth.kt- API key validationserver/routes/StatusRoutes.kt- Health check endpointserver/routes/SmsRoutes.kt- SMS API endpointsserver/TextpipeServer.kt- Ktor server configuration
Phase 5: Service Layer
service/TextpipeService.kt- Foreground serviceservice/BootReceiver.kt- Auto-start on bootTextpipeApplication.kt- Application class
Phase 6: UI Layer
ui/MainViewModel.kt- UI state managementui/MainScreen.kt- Compose UIMainActivity.kt- Update with permissions and new UI
Key Technical Decisions
HTTP Server
- Ktor CIO engine (not Netty) - lighter weight for Android
- Server runs on configurable port (default 8080)
- For ports 80/443: Use iptables NAT redirect via
su(don't run JVM as root)
Dual SIM Support
SubscriptionManager.getActiveSubscriptionInfoList()for SIM detectionSmsManager.createForSubscriptionId(subscriptionId)for sending- API key maps to subscription ID, not slot index
SMS Delivery Tracking
- PendingIntents for sent confirmation and delivery reports
- Update database status: pending → sent → delivered/failed
- Multipart SMS via
sendMultipartTextMessage()
Background Persistence
- Foreground service with persistent notification
FOREGROUND_SERVICE_TYPE_SPECIAL_USEfor API 34+- Boot receiver for auto-start
API Key Storage
- SharedPreferences (not DataStore) for synchronous reads
- Key format: UUID, stored per SIM slot
- Generated on first run, persistent across app updates
Files to Create/Modify (22 total)
| # | File | Action |
|---|---|---|
| 1 | gradle/libs.versions.toml |
Modify |
| 2 | build.gradle.kts (root) |
Modify |
| 3 | app/build.gradle.kts |
Modify |
| 4 | app/src/main/AndroidManifest.xml |
Modify |
| 5 | data/model/ApiModels.kt |
Create |
| 6 | data/db/SmsMessageEntity.kt |
Create |
| 7 | data/db/SmsMessageDao.kt |
Create |
| 8 | data/db/AppDatabase.kt |
Create |
| 9 | sim/SimManager.kt |
Create |
| 10 | root/RootManager.kt |
Create |
| 11 | service/SmsSender.kt |
Create |
| 12 | data/repository/SmsRepository.kt |
Create |
| 13 | service/SmsReceiver.kt |
Create |
| 14 | server/auth/ApiKeyAuth.kt |
Create |
| 15 | server/routes/StatusRoutes.kt |
Create |
| 16 | server/routes/SmsRoutes.kt |
Create |
| 17 | server/TextpipeServer.kt |
Create |
| 18 | service/TextpipeService.kt |
Create |
| 19 | service/BootReceiver.kt |
Create |
| 20 | TextpipeApplication.kt |
Create |
| 21 | ui/MainViewModel.kt |
Create |
| 22 | ui/MainScreen.kt |
Create |
| 23 | MainActivity.kt |
Modify |
UI Features (Basic for now)
- Server status indicator (Running/Stopped)
- Port configuration input
- Start/Stop toggle button
- SIM cards list with:
- Carrier name
- Phone number (if available)
- API key with copy button
- Root port redirect toggle (if root available)
- Battery optimization exemption button
Out of Scope (Deferred)
- WebUI for viewing messages in browser
- Rate limiting
- IP whitelisting
- HTTPS/TLS (beyond port redirect)
- Message scheduling
- Webhook callbacks