Android-only React Native SMS/MMS gateway that runs entirely on the phone. It receives SMS and MMS, sends SMS and MMS, and exposes a local WebSocket server so nearby systems can exchange messaging commands and events without any cloud relay.
- Runs a foreground Android service to keep a local WebSocket server alive in the background.
- Requests the default SMS role so the app can reliably send, receive, and persist SMS traffic.
- Streams inbound SMS/MMS and outbound delivery events over WebSocket.
- Supports API-key authenticated local clients on the same LAN.
- Restarts after reboot when the gateway was previously enabled.
- Supports one outbound MMS attachment per message using inline base64 over the websocket API.
- Supports Android default-dialer call control, including DTMF digits on active calls.
App.tsx: React Native control panel for role/permission setup and gateway controls.src/SmsGateway.ts: JS wrapper around the native Android module.android/app/src/main/java/com/smssocketapp/gateway: foreground service, SMS receivers, WebSocket server, persistence, and RN bridge.docs/asyncapi.yml: local WebSocket contract.
This app is designed for Android only and expects to become the device's default SMS app before the gateway is started.
Required user-facing setup:
- Grant the default SMS role.
- Allow notifications on Android 13+ so the foreground service notification can be shown.
- Exempt the app from battery optimizations if the device vendor is aggressive about background services.
- Start the gateway and store the generated API key somewhere safe.
The server is local and cleartext by default. Typical message flow:
- Client connects to
ws://<phone-ip>:8787. - Client includes
Authorization: Bearer <api-key>in the opening WebSocket handshake. - Client sends commands like
sendSms,sendMms,rehydrate,getGatewayState,listSubscriptions, orack. - Server pushes events such as
sms.received,mms.received,sms.outbound.sent,mms.outbound.sent, andgateway.state.
See docs/asyncapi.yml for the exact wire shape.
Dialer commands are additive on the same websocket contract: requestDialerRole, getDialerState, placeCall, answerCall, rejectCall, endCall, setMuted, sendDtmf, and showInCallScreen.
Install dependencies:
npm installStart Metro:
npm startRun Android:
npm run androidRun tests:
npm test
cd android && .\gradlew testA websocket-focused Python TUI lives under tools/dev. It reads the default gateway URL from docs/asyncapi.yml and includes interactive commands for:
- connecting with bearer-authenticated websocket handshakes
- sending SMS with
sendSms - sending MMS with
sendMms - requesting history with
rehydrate - checking gateway state and SIM subscriptions
Quick start:
python -m venv .venv
.venv\Scripts\activate
pip install -r tools/dev/requirements.txt
python -m tools.dev.sms_gateway_tuiTo connect to a different device or override the AsyncAPI default URL:
python -m tools.dev.sms_gateway_tui --url ws://192.168.1.25:8787/Suggested interactive flow:
- Start the Android gateway from the app and note the API key.
- Launch the TUI from the repo root.
- Run
auth <api-key>. - Run
connect [ws-url]if you need to reconnect or switch URLs with the stored key. - Use
stateto verify the gateway is healthy andsubscriptionsto inspect SIM slots. - Send a message with
send <phone> <message> [subscription-id]. - Request history with
history [since-ms] [limit].
Example session:
auth abc123
state
subscriptions
send +15551234567 "hello from the terminal"
sendmms +15551234567 .\photo.jpg "caption from the terminal"
history 0 20
The TUI prints websocket events as they arrive, so inbound SMS and outbound delivery updates will show up in the console automatically.
- Outbound MMS in v1 supports one attachment plus an optional text body.
- Supported outbound attachment MIME families are
image/*,video/*,audio/*, andapplication/pdf. - The React Native composer compresses oversized picked images where possible and rejects non-image files over 1 MB.
- Group MMS is supported for inbound threads and websocket events, but outbound compose remains single-recipient in this first pass.
- DTMF is sent through Android Telecom one character at a time using the active
Call; cadence can vary a bit by device, and pause/wait characters are intentionally not supported in v1. - TLS is intentionally not bundled into the on-device server. If you need encryption beyond a trusted LAN, terminate TLS elsewhere.
- Android background behavior still depends on OEM battery management. The foreground service and reboot recovery do the heavy lifting, but some vendors remain committed to chaos.
