-
Notifications
You must be signed in to change notification settings - Fork 14
Architecture and Data Flow
This document explains how the SMS Router Android client intercepts SMS messages, applies Clean Architecture boundaries, and forwards payloads to Mattermost or Telegram webhooks with delivery logging and retries.
The SMS Router follows a three-layer structure:
- Presentation: Android components (BroadcastReceiver, Compose screens) + permission handling.
- Domain: Core business use cases, models, and retry orchestration.
- Data: API services, mappers, repositories, Room database, SharedPreferences.
Key capabilities:
- Intercept SMS via high-priority BroadcastReceiver.
- Support multiple webhooks (Mattermost, Telegram) per SIM card.
- Persist delivery state with Room and expose live updates via Compose UI.
- Apply automatic retry with a 60-second overall timeout.
- Provide configuration UI for per-SIM routing parameters.
The flow separates presentation, domain, and data modules to keep dependencies directional (UI → Domain → Data) and maintain testability.
Entry point registered for Telephony.Sms.Intents.SMS_RECEIVED_ACTION.
| Responsibility | Description |
|---|---|
| SMS Detection | Listens for broadcast and prioritizes early interception |
| Message Extraction | Reassembles multipart SMS (GSM 7-bit / UCS-2) |
| SIM Identification | Reads subscription ID, operator name, and phone number |
| Data Packaging | Builds SmsData domain model (sender, body, SIM metadata) |
| Async Dispatch | Launches coroutine on IO dispatcher to avoid blocking receiver |
Central coordinator that drives logging, configuration lookup, webhook invocation, and status updates.
Processing flow:
- Generate SMS ID (sender + timestamp) for tracking.
- Insert initial log entry with
IN_PROGRESSstatus. - Retrieve SIM-specific configuration (webhook URL, secret, type, Telegram chat ID).
- Register retry callback so UI reflects intermediate attempts.
- Delegate actual HTTP call to the forwarding repository.
- Update log to
DELIVEREDorERRORdepending on result.
HTTP layer built on Ktor with structured retry logic.
| Parameter | Value | Purpose |
|---|---|---|
| Max Attempts | 3 | Initial request + 2 retries |
| Overall Timeout | 60 seconds | Ensures forwarding concludes quickly |
| Retry Tracking | In-memory map keyed by SMS ID | Maintains attempt counts |
| Callback | Invoked per retry | Notifies UI/log repository |
Maps domain models into webhook-specific DTOs:
| Webhook | Payload Structure | Required Fields |
|---|---|---|
| Mattermost | { "text": "message" } |
text |
| Telegram | { "chat_id": "id", "text": "message" } |
chat_id, text
|
- Android broadcasts incoming SMS.
- SmsReceiver builds
SmsDataand hands it to the domain layer. - ForwardSmsUseCase logs and orchestrates forwarding.
- SettingsRepository provides per-SIM configuration.
- SmsDataDtoMapper creates webhook payload.
- SmsApiService sends HTTP POST with retries.
- Retry callbacks and final states persist via SmsLogsRepository.
- Compose UI observes log flow for real-time status.
Stores SIM routing data in SharedPreferences (JSON serialized per SIM):
| Property | Description |
|---|---|
simId |
Unique identifier (subscription ID) |
simName |
Display label (carrier/operator) |
webhookUrl |
Destination endpoint |
secretKey |
Bearer token for Authorization header |
webhookType |
Enum (MATTERMOST, TELEGRAM) |
chatId |
Telegram-specific chat identifier |
Repository exposes reactive flows for UI plus CRUD helpers for configuration screens.
Backed by Room database smsrouter-db with table sms_logs to persist delivery history.
| Field | Purpose |
|---|---|
id |
Primary key (SMS ID) |
sender |
Originating phone number |
message |
SMS body |
simType |
Operator/SIM label |
timestamp |
Epoch millis |
status |
IN_PROGRESS, DELIVERED, ERROR
|
errorDetails |
Optional error message |
retryCount |
Attempts performed |
Repository converts entities to domain models and emits a StateFlow consumed by the Messages UI.
- All webhook calls include
Authorization: Bearer <secretKey>. - Optional cleartext traffic allowed for local testing; production should enforce HTTPS.
- Dangerous permissions required at runtime:
RECEIVE_SMS,READ_SMS,READ_PHONE_STATE,READ_PHONE_NUMBERS,INTERNET. - Receiver priority is set high (999) to ensure messages reach the app before the default SMS client.
Koin DI module wires:
| Component | Scope | Depends On |
|---|---|---|
| HttpClient | Singleton | Ktor configuration |
| SmsApiService | Singleton | HttpClient |
| SmsDataDtoMapper | Singleton | None (stateless) |
| SmsForwardingRepository | Singleton | SmsApiService, SmsDataDtoMapper |
| SettingsRepository | Singleton | Android Context |
| AppDatabase + SmsLogDao | Singleton | Android Context |
| SmsLogsRepository | Singleton | SmsLogDao |
| SimCardProvider | Singleton | Context + SubscriptionManager |
| Property | Description |
|---|---|
sender |
Originating phone number |
operatorName |
Carrier label from SIM |
messageBody |
Full concatenated SMS text |
recipientPhoneNumber |
Device phone number for the SIM |
simId |
Unique SIM identifier |
- Retry counters reside in an in-memory map keyed by SMS ID.
- Each failure increments the counter; callbacks update UI/log.
- Successful delivery clears the counter; final failure persists the attempt count in Room.
- A supervisor-backed coroutine scope ensures retries do not crash the receiver.
- 60-second global timeout stops retry attempts early if exceeded.
-
SIM Enumeration: Requires
READ_PHONE_STATE; settings screen re-requests permission when operators swap SIM cards. - Multipart SMS: Receiver concatenates parts according to GSM/UCS-2 encoding limits before forwarding.
- Battery Optimization: Devices should whitelist the app from battery savers so broadcasts arrive instantly.
- Extend
WebhookTypeenum. - Create DTO implementing the mapper contract.
- Update
SmsDataDtoMapperand associated tests. - Add configuration fields and UI elements for the new webhook settings.
| Issue | Check | Resolution |
|---|---|---|
| SMS not forwarding | Verify per-SIM configuration (URL, secret) | Update settings and retry |
| Telegram failures | Ensure chatId is populated |
Provide valid chat ID |
| Retries exhausted | Inspect logs UI for error details | Resolve upstream issue, resend |
| Permission denied | Confirm runtime permissions granted | Request missing permission |
Use Logcat tags (ForwardSmsUseCase, SmsApiService) and the Messages UI for live diagnostics.