
Full stack
SMS Relay
- Flutter
- Next.js
- NestJS
- PostgreSQL
Project overview
A self-hosted SMS gateway connecting Flutter Android nodes to a Next.js dashboard and NestJS API.
what went into it
- 01
Built a monorepo with a Flutter Android node, Next.js dashboard and NestJS API.
- 02
Implemented QR pairing, heartbeat health reporting and delivery-status streams.
- 03
Included recipient rate limits, node quotas and hashed OTP verification.
build log
How I built it.
The problem
Commercial SMS gateways bill per message, and most phones sit idle with an unused SIM. I wanted a self-hosted gateway where my own Android phones become the sending nodes, with a dashboard to see which ones are healthy.
Architecture
- Monorepo
- Flutter Android node, Next.js dashboard and NestJS API live in one repository, so the API contract changes in one commit.
- Native channel
- SMS sending runs through an Android platform channel, because Dart can't touch SmsManager. Dart owns the queue and retry logic; Kotlin only sends and reports the result.
- Pairing & health
- A node pairs by scanning a QR code from the dashboard, then reports a heartbeat. Missed heartbeats mark the node offline, so work stops going to it.
- Delivery streams
- Delivery status is pushed back as a stream instead of polled, so the dashboard shows sent, delivered or failed as it happens.
- Abuse limits
- Per-recipient rate limits and per-node quotas keep a bug or a bad caller from burning through a SIM. OTPs are stored hashed.
What I’d change
- Queue messages in a local database on the node so a crash or reboot doesn't drop them.
- Add a dead-letter view in the dashboard for messages that exhaust their retries.
- Put a contract test between the Flutter node and the API before the schema drifts.
