Send application SMS and one-time passcodes through an Android phone and SIM that you control.
BYO-SIM SMS Gateway turns an Android device into an account-owned SMS gateway. A web dashboard manages devices, SIM routing, API keys, messages, OTP requests, and reliability data. Convex is the authoritative backend, while the Android app claims queued work and hands messages to the selected SIM through a Kotlin SmsManager bridge.
The project is designed for transactional messages sent with the account owner's own device, carrier plan, and recipient consent. It does not provide anonymous sending, sender spoofing, bulk campaigns, contact-list blasting, or carrier infrastructure.
- Password authentication with email verification, recovery, rate limits, and authenticated dashboard access.
- Single-use, expiring pairing codes that bind an Android gateway to one account.
- Android SMS permissions, SIM discovery, enable/disable controls, manual number fallback, and default-SIM selection.
- Hashed, revocable API keys for sending SMS and OTP requests from server-side integrations.
- An atomic
queued → claimed → sending → sent/failedmessage lifecycle with device-scoped claims. - Message history, status details, failure reasons, device health, and reliability drill-downs.
- Android foreground-service, boot recovery, heartbeat, diagnostics, and telemetry support.
- OTP expiry, attempt limits, resend controls, and phone-number throttling.
| Area | Technology | Responsibility |
|---|---|---|
| Admin application | React 18, Vite 6, Tailwind CSS 4 | Authentication, gateway management, messaging, API help, and reliability UI |
| Backend | Convex, Convex Auth | HTTP APIs, authorization, validation, durable state, rate limits, and scheduled cleanup |
| Gateway application | Expo 51, React Native 0.74 | Pairing, readiness, SIM settings, and gateway lifecycle UI |
| Native Android bridge | Kotlin, Android SmsManager |
Permissions, subscriptions, SMS callbacks, foreground service, boot handling, and secure credentials |
| Language and tooling | TypeScript, Node.js, npm | Shared application code, scripts, builds, and tests |
| Testing | Node test runner, Vitest, convex-test, Android/JUnit |
Backend, web, gateway, acceptance, and native regression coverage |
flowchart LR
Integration["Developer application"] -->|"API key: SMS or OTP"| Backend["Convex HTTP API and domain modules"]
Dashboard["Vite admin dashboard"] -->|"Authenticated management requests"| Backend
Backend -->|"Read and write"| Data[("Convex data")]
Backend -->|"Queued jobs"| Gateway["Android gateway app"]
Gateway -->|"Pairing, claims, heartbeat, status, telemetry"| Backend
Gateway --> Native["Kotlin gateway module"]
Native -->|"SmsManager"| Carrier["User SIM and mobile carrier"]
Convex owns identity, account and device ownership, message state, OTP state, and rate limits. The Android app is a device-scoped executor: it keeps local configuration and transient send state, but it is not the source of truth for authorization or delivery status. Polling and authoritative rereads let the gateway recover after connectivity or process interruptions.
See the architecture index, system overview, and architecture decisions for the detailed model.
- Node.js and npm. The repository does not currently declare a minimum Node.js version.
- A Convex project for connected authentication, API, and persistence.
- Android Studio and the Android SDK for native gateway development.
- An Android device with SMS capability, an active SIM/carrier plan, and the required Android permissions.
This repository uses separate npm lockfiles for the root, web app, and gateway app:
npm install
npm --prefix apps/web install
npm --prefix apps/gateway installCopy the tracked environment templates:
cp apps/web/.env.example apps/web/.env.local
cp apps/gateway/.env.example apps/gateway/.env.localConfigure the public application endpoints without committing their values:
# apps/web/.env.local
VITE_CONVEX_URL=
VITE_GATEWAY_API_URL=
# apps/gateway/.env.local
EXPO_PUBLIC_CONVEX_SITE_URL=VITE_CONVEX_URL connects the browser to Convex. VITE_GATEWAY_API_URL and EXPO_PUBLIC_CONVEX_SITE_URL point to the deployed Convex HTTP origin. Production web endpoints must use HTTPS.
The Convex deployment also needs its authentication, email, allowed web origin, and rate-limit secrets configured in the provider environment. Before enabling production, prepare and review operational procedures for identity migration, account recovery, browser sessions, and CSP enforcement.
Start Convex development from the repository root:
npm run convex:devIn another terminal, start the admin application:
npm run devRun the native Android gateway from a configured Android development environment:
npm --prefix apps/gateway run androidFor UI-only development, the web app has an explicit offline-auth mode. Add VITE_ENABLE_OFFLINE_AUTH=true only to a local Vite development environment; it is intentionally blocked from production use.
- Register or sign in through the web dashboard.
- Create a pairing code under Devices.
- Enter that code in the Android gateway app, grant the requested permissions, and configure the available SIMs.
- Start the gateway service and wait for the dashboard to report the device as ready.
- Create an API key and store the one-time secret in your server-side secret manager.
- Send a message from a trusted server-side integration:
export BYO_GATEWAY_URL="https://your-convex-http-origin.example"
export BYO_SMS_API_KEY="replace-with-your-api-key"
export BYO_RECIPIENT="+1xxxxxxxxxx"
curl --fail-with-body -X POST "$BYO_GATEWAY_URL/v1/messages/send" \
-H "Authorization: Bearer $BYO_SMS_API_KEY" \
-H "Content-Type: application/json" \
--data '{"to":"'"$BYO_RECIPIENT"'","message":"Hello from your app"}'A successful request returns a message ID and queued status. Use GET /v1/messages/{messageId} or the dashboard to follow the result. A sent status means Android handed the SMS to the carrier; it does not prove handset delivery.
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/messages/send |
Queue an SMS using an API key |
GET |
/v1/messages/{messageId} |
Read an owned message's status |
POST |
/v1/otp/send |
Create and send a one-time passcode |
POST |
/v1/otp/verify |
Verify a one-time passcode |
The authenticated dashboard and device gateway use separate /dashboard/* and /gateway/* contracts. The web application's API Docs screen documents request bodies, responses, and error codes for the public API.
apps/
├── web/ # Vite admin dashboard
└── gateway/ # Expo/React Native Android gateway
└── modules/gateway-native/ # Kotlin SMS and lifecycle bridge
convex/ # Backend schema, functions, HTTP routes, and tests
docs/ # Architecture, ADRs, security runbooks, and receipts
packages/
├── contracts/ # Reserved shared transport-contract boundary
└── domain/ # Reserved shared domain boundary
scripts/ # Repository verification utilities
tests/acceptance/ # Cross-surface reliability acceptance checks
The root package coordinates the independently locked applications through npm --prefix commands; it is not declared as an npm workspace.
Run the TypeScript test suites:
npm testRun the production web build and gateway typecheck:
npm run buildAdditional backend and native checks:
npx tsc --noEmit -p convex/tsconfig.json
apps/gateway/android/gradlew -p apps/gateway/android :gateway-native:testDebugUnitTestDependency and lockfile checks are also available:
npm run audit:production
npm run audit:complete
npm run verify:lockfilesSome acceptance tests target an explicitly enabled synthetic endpoint. They fail closed unless the required opt-in configuration is present; see the NEXT-01 validation receipt before running them against a deployment.
The system has three independent release surfaces:
- Convex backend: deploy the schema, functions, HTTP actions, crons, and provider-managed secrets.
- Web dashboard: build
apps/webwith its public Convex and HTTP endpoints, then serve the generated static assets with the documented CSP and response headers. - Android gateway: produce and sign a native Android build with the Convex HTTP origin configured. Distribution is a release decision; the repository does not claim a public app-store release.
There is no root one-command production deployment configuration. Review the deployment data flow and security and operations guidance before promoting any surface.
- Account identity comes from the authenticated Convex context rather than caller-supplied user IDs.
- Pairing codes expire and are consumed once; API keys, OTP codes, and device tokens are stored as hashes.
- Ownership checks isolate account and device data, while claimant-only transitions protect message state.
- HTTPS validation, bounded payloads, rate limits, redacted logs, and production CSP controls reduce abuse and data exposure.
- Device credentials are kept in Android secure storage; API keys belong only in trusted server-side integrations.
You are responsible for device ownership, SIM costs or load, recipient consent, applicable law, carrier terms, and local Android/OEM background-service behavior. Never use the project for unsolicited or deceptive messaging.
- Product baseline
- Roadmap
- Architecture documentation
- Data and integration model
- Security and operations
- Convex backend notes
- Web application notes
- Android gateway notes
The project is available under the MIT License. Third-party dependencies and vendored tooling remain subject to their respective licenses.