Cross-posted from the Spendlee repo, where the full technical postmortem lives in docs/PIVOT-FROM-SMS-TO-VOICE-AI.md.
The pitch that seemed obvious
Every UPI transaction in India comes with an SMS receipt. So the first version of what's now Spendlee skipped manual expense entry entirely — a BroadcastReceiver would catch the bank text, regex out the amount and merchant, and fire a notification: "New transaction detected — log it?"
Zero typing. Zero friction. The single biggest reason expense trackers get abandoned, solved before the user even opened the app.
It worked. I built the whole thing — SMS receiver, bank-specific and generic UPI parsers, the notification flow, confirm/categorize screens, Room storage. Then I tried to actually ship it.
Where it fell apart
Google Play Protect doesn't care that you're not malware. The moment a signed APK requesting RECEIVE_SMS/READ_SMS was installed outside the Play Store, Play Protect blocked it on sight — the same heuristic that catches SMS-stealing malware doesn't stop to ask whether this particular app is a personal finance tool you wrote yourself. Getting around it meant disabling a security feature and enabling "install from unknown sources," which is a fine ask for a developer testing their own build and a hard no for handing the APK to a friend or spouse.
Distribution was harder than the app. You can't even email the thing to yourself — Gmail strips .apk attachments outright. Every distribution path led back to the same trade: disable one protection, then another, for an app whose entire value proposition was supposed to be effortless.
The Play Store isn't a clean escape hatch either. Declaring RECEIVE_SMS/READ_SMS there requires a separate Permissions Declaration Form with strict, inconsistent review outcomes, plus a mandatory privacy policy — a reasonable cost for a company shipping an official banking app, a lot of process risk for a side project whose core feature is exactly the permission pair Google scrutinizes hardest.
And then the wall that actually mattered: iOS. No third-party iOS app has ever been allowed to read SMS content — not behind a permission dialog, not through a special entitlement, not at any App Store review tier. This isn't a policy form to fill out; it's OS-level sandboxing Apple enforces uniformly. Which meant the SMS-reading architecture could never have an iOS counterpart, full stop — any plan to eventually port this to iPhone was dead the moment SMS reading became the core mechanism, regardless of how much more Android engineering went into it.
The pivot
I replaced automatic detection with user-initiated voice/text logging:
- Tap-to-speak using Android's on-device
SpeechRecognizer— no cloud speech API, no extra permission risk. - A "Type" mode for the same flow when talking out loud isn't practical.
- The transcribed or typed sentence ("spent 250 on groceries") goes to a Supabase Edge Function, which calls an LLM to extract amount, merchant, and category. The LLM API key lives only as an Edge Function secret — the Android app never holds it.
- A review step shows the recognized text before it's parsed, so a speech-recognition slip is a two-second edit instead of a silently wrong transaction.
- Everything downstream — confirm, categorize, Room storage, reports — was already built for the SMS flow and needed only cosmetic changes to plug into the new input source.
Why this turned out to be the better design, not just the available one
-
No sensitive permissions.
RECORD_AUDIOisn't in Android's Restricted Permissions bucket — no Play Protect flag, no special declaration form. - Actually portable to iOS. Speech-to-text and typed text aren't platform-gated the way SMS content is. A future iOS build could use Apple's own on-device speech APIs and call the same Edge Function.
- Covers cash, not just UPI. SMS-based detection only ever saw electronic payments. Voice/text logging works for anything, spoken in plain language.
What I'd tell past-me
- Check platform distribution policy before the architecture locks around a permission. Play Protect's behavior and Apple's SMS policy were both knowable in advance — finding out after the feature was fully built meant rebuilding the core interaction model, not tweaking it.
- "Works on my dev device" isn't "shippable." Sideloading friction is invisible right up until you try to hand the APK to someone who isn't you.
- A platform-level restriction has no engineering workaround. When the OS vendor enforces a constraint uniformly, no amount of extra dev time recovers the original plan — only a different design does.
- Preserve reusable investment when you pivot. Because the confirm/categorize/report UI never cared how a transaction entered the queue, swapping the input mechanism was all the pivot cost.
Full writeup with more detail: docs/PIVOT-FROM-SMS-TO-VOICE-AI.md. Repo: github.com/shadow-monarch9/Spendlee.
Top comments (0)