SMS Mobile App
com.aoneahsan.smsapp
SMS Mobile App is an Android-first SMS-automation app on React 19 + Vite 8 + Capacitor 8.4 + Firebase 12 that dispatches user/admin-authored campaigns through the phone's own SIM β via a custom in-tree `NativeSms` Capacitor plugin and an Android foreground service β with no paid SMS gateway, plus a 15k-row recipient directory, triple-platform analytics, full SEO/AEO infrastructure, and a separately-deployed public Docusaurus 3 documentation site.
At a glance
- Category
- mobile-app
- Status
- Live
- Started
- Nov 2025
- Last updated
- 2 months ago
Identifier
com.aoneahsan.smsappPreview
Take a look before you go
The still below is painted first, then the running site loads behind it β so the dialog is never an empty rectangle waiting on a network round trip. Where a site allows framing the live app takes over; where it refuses, the still stays and says so. Nothing is requested until you press.
About
What this actually is
The phone in your pocket is already an SMS gateway.
It has a SIM, a carrier agreement and an allowance somebody is paying for whether it gets used or not. What it does not have is a way for one person to hand it a list and a message and walk away.
SMS Mobile App is that missing half.
An admin imports recipients from a CSV or from the built-in persons directory of 15,851 rows, then assigns a send batch to volunteer devices that have opted in. Each device sends through its own SIM.
That directory is a virtualised table, which is what a list of that size needs if the screen is to stay usable at all. Templates map fields into the send flow, so a message can carry a name without anybody editing them one at a time.
The plumbing is a plugin.
A custom in-tree Capacitor plugin called NativeSms wraps the Android send call, and a foreground service keeps a batch moving while the application is closed or the screen is locked. There is no SIP relay in the path and no per-send fee beyond what the carrier already charges.
It is deliberately send-only.
No inbox. No READ_SMS permission, ever.
That is a product decision and a store decision at the same time. The message-reading permissions are among the ones a review examines hardest, and an application that never asks for one never has to explain why it wanted it.
The batch cap is enforced where it cannot be argued with.
Ten devices per batch, written as a Firestore rules invariant rather than as a check in the admin screen, with atomic parent-batch counters underneath so that two devices finishing together cannot both write the same total.
A rule the client enforces is a rule the client can skip.
That matters more here than in most products, because the clients are handsets belonging to volunteers and running wherever those volunteers happen to be. The only enforcement worth having is the kind that lives on the other side of the network.
It runs on Firebase's free tier, with Cloudflare Workers doing the work that would otherwise want paid functions.
That is the constraint the whole product is shaped by. No paid service in the critical path, and no server that has to be kept alive. That is what makes it possible for one person to keep a set of things running at the same time rather than one after another.
It is documented properly.
A public documentation site structured on DiΓ‘taxis carries 41 pages and roughly 33,000 words, which is the difference between a tool somebody else can operate and a tool only I can.
What it cost is the phones.
This design trades a paid gateway for a set of real devices that have to be switched on, charged and willing. A gateway scales by paying more. This scales by finding more volunteers, which is a harder thing to arrange on a Tuesday afternoon and a much cheaper one to run for a year.
It is live at smsapp.aoneahsan.com, with an Android application on Google Play and the documentation at smsapp-docs.aoneahsan.com.
Send-only was the first decision. Everything else rests on it. An inbox would have doubled the permission surface and changed what the application is for, and neither of those is a trade worth making for a feature nobody asked for.
Worth knowing
- The custom in-tree `NativeSms` plugin is not an off-the-shelf community plugin β it wraps `SmsManager.sendTextMessage` + a foreground service (`FOREGROUND_SERVICE_DATA_SYNC`) for silent batch dispatch, which the `@byteowls/capacitor-sms` community plugin does not support. Both are shipped: UI-prompted AND silent.
- The 10-device per-batch cap is enforced in `firestore.rules` by hand-unrolling the `assignedDeviceIds` array (rules cannot iterate arbitrary arrays) β written, not copy-pasted.
- Parent-batch counters use atomic Firestore `DocumentTransform.increment` (via REST `Document.commit` from `BatchSendRunner.java`) so concurrent runner devices cannot race the counters.
- Send-only by design: no `READ_SMS`/`RECEIVE_SMS` anywhere in the codebase. What IS stored in Firestore is outbound, owner-authored content in owner-scoped docs (drafts/templates/jobs/batches) β deliberately distinct from received-SMS exfiltration. Privacy policy + Play Data Safety form reflect this distinction.
- Capacitor camera/geolocation plugins were removed because unused β preventing AndroidManifest auto-injection of unwanted sensitive permissions before Play Store upload (a known Play Console rejection vector).
- The persons directory (~15,851 rows) is seeded with the `firebase-admin` Node SDK via `tools/seedPersons.ts` and a gitignored service-account JSON β never committed; phone-as-doc-ID makes re-import idempotent (`setDoc({ merge: true })`).
- Logger upgraded to the full R-029 spec: levels + runtime `localStorage` switch + `__setLogLevel`/`__getLogLevel` devtools hooks + boot-time console patching so third-party stragglers stay quiet. `no-console` is an ESLint error.
- Zero-cost architecture: Firebase free tier + Cloudflare Workers (token proxy + IndexNow ping) instead of paid Firebase Functions; FilesHub instead of Firebase Storage.
Built with
- React 19
- TypeScript
- Vite 8
- CapacitorJS
- Firebase
- Radix Themes
- TanStack Query
- TanStack Table
Key features
What it does, and what that costs to build
Each of these is a capability the stack genuinely provides. None of them is an outcome nobody measured β there is no "cut onboarding by 40%" on this page, because no one measured onboarding.
- Native SIM-based SMS send (no paid gateway)
- Custom in-tree NativeSms plugin + foreground service
- 15,851-record persons directory (virtualized table)
- Admin CSV import + volunteer-device batch fan-out
- Template field-mapping into send flow
- Send-only design (SEND_SMS only, no inbox)
- Public Docusaurus documentation site
Screenshots
5 slots, 2 of them filled in
These are designed frames, not pictures. Each one records what belongs in the slot and at what width; the capture itself is taken from the running app. Drawing a fake screenshot here would put an image on the site that no build has ever produced.
Timeline
Still being worked on
Nov 2025
Work started
The earliest month on record for SMS Mobile App.
Sep 2026
Last change
Status is completed β 2 months ago. Ongoing, so this row moves whenever something lands.
Questions
Common questions
The questions that actually arrive, answered in the first sentence.
- Who built SMS Mobile App?
- Ahsan Mahmood built the whole of it, including the native plugin. It is live at smsapp.aoneahsan.com, ships an Android application through Google Play, and has a public documentation site at smsapp-docs.aoneahsan.com carrying 41 pages and roughly 33,000 words. The plugin that talks to the SIM, the rules that cap a batch, the admin screens and the documentation were written by the same person, which is why the documentation describes what the code does rather than what somebody hoped it did.
- How does a message actually leave the phone?
- Through the device SIM, using a custom in-tree Capacitor plugin called NativeSms that wraps the Android SmsManager send call. An Android foreground service keeps the batch moving while the application is closed or the screen is locked, which is what makes it usable rather than a thing you have to sit and watch. There is no SIP relay anywhere in the path and no per-send fee beyond whatever the carrier already charges for a message on that plan.
- Can it read the replies?
- No, and it never asks for the permission that would let it. The product is deliberately send-only: no inbox, no READ_SMS. That is a product decision and a store decision at once, because the message-reading permissions are among the most restricted a review looks at, and an application that never requests one never has to justify it. It also narrows what the app can be misused for, which is a reasonable thing to want from software that dispatches messages in volume.
- What stops a batch being handed to more devices than intended?
- A cap of 10 devices per batch, written as a Firestore rules invariant rather than as a check in the interface. A rule the client enforces is a rule the client can skip, and the client here is an application running on volunteer handsets outside anybody's control. Atomic parent-batch counters sit underneath it so two devices finishing at the same moment cannot both write the same total. That is the sort of limit worth putting where it cannot be argued with.
- Does SMS Mobile App work on iPhone?
- No. It is Android-only by construction, because the send path is an Android API reached through a plugin written for it, and there is no equivalent route on another platform. There is no iOS release behind any of my work either, because there is no Apple Developer account here and nothing I build has shipped to the App Store. The admin side runs on the web, so the part that imports recipients and assigns batches is reachable from any machine.
Keep looking
More mobile work
- PR
PregnancyPal
com.aoneahsan.pregnancypal
Week-by-week maternal wellness, nutrition, vitals and prenatal exercise.
- HA
HabitForge
com.aoneahsan.habitforge
Habit tracking with visual strength scoring and offline-first sync.
- CA
CallVault
com.aoneahsan.callvault
Offline-first Android app that records your own calls and backs them up to storage and a database you control.