Files
textpipe/PLAN.md

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> or X-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

  1. gradle/libs.versions.toml - Add all new dependencies
  2. build.gradle.kts (root) - Add KSP and serialization plugins
  3. app/build.gradle.kts - Add dependencies and plugins
  4. AndroidManifest.xml - Add permissions, services, receivers

Phase 1: Data Layer

  1. data/model/ApiModels.kt - Request/response data classes
  2. data/db/SmsMessageEntity.kt - Room entity
  3. data/db/SmsMessageDao.kt - Database operations
  4. data/db/AppDatabase.kt - Room database

Phase 2: Core Managers

  1. sim/SimManager.kt - SIM detection, API key management
  2. root/RootManager.kt - Root access and iptables port redirect

Phase 3: SMS Operations

  1. service/SmsSender.kt - Send SMS with delivery tracking
  2. data/repository/SmsRepository.kt - Combine database + sending
  3. service/SmsReceiver.kt - Receive incoming SMS

Phase 4: HTTP Server

  1. server/auth/ApiKeyAuth.kt - API key validation
  2. server/routes/StatusRoutes.kt - Health check endpoint
  3. server/routes/SmsRoutes.kt - SMS API endpoints
  4. server/TextpipeServer.kt - Ktor server configuration

Phase 5: Service Layer

  1. service/TextpipeService.kt - Foreground service
  2. service/BootReceiver.kt - Auto-start on boot
  3. TextpipeApplication.kt - Application class

Phase 6: UI Layer

  1. ui/MainViewModel.kt - UI state management
  2. ui/MainScreen.kt - Compose UI
  3. MainActivity.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 detection
  • SmsManager.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_USE for 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