Finman turns the chaos of transactional SMS into three honest numbers — income, expenses, savings — and reconciles every account to the rupee, without a single raw message ever leaving the phone.
A privacy-first Android app that parses transactional bank/UPI SMS on the device and turns them into a clean, reconciled personal ledger — income, expenses, savings, and per-account balances. Built solo, end-to-end: a framework-free TypeScript reference engine, a Flutter/Dart device port kept in lockstep by golden vectors, and a NestJS + Prisma + PostgreSQL server that only ever sees redacted skeletons. Passion project — Android-only by necessity, foundation complete, source open.
Android-only passion project · not distributed · code is open
Role
Solo — design, engine, server, device, tests
Timeline
June 2026 · one focused week
Platform
Android only (iOS blocks SMS access)
Status
Paused · foundation complete
Every month my real spending lived scattered across dozens of bank and UPI text messages — different formats, OTPs and promos mixed in, balances that quietly drifted. The apps that promise to organise it want read access to your entire financial life. I wanted the opposite: three honest numbers, computed on my own phone, with the raw messages never leaving it.
So I built Finman as a passion project — partly to have the tool I wanted, partly as an excuse to do the engineering properly: a privacy model that holds up, money math that can't round wrong, and parsing logic tested hard enough that I'd trust the numbers.
And yes — I know Finman is a terrible name. I was deep in a Marvel binge at the time, so the finance tracker got named like a superhero. I'm sorry. (I'm not. Finman answers to no one.)
Dozens of banks, dozens of formats, one-time passwords and promos in the mix, and available balances that drift between messages. The signal is all there; it just isn't legible — and it's too sensitive to hand to a third party. Finman parses every transactional SMS on-device and turns it into a clean, reconciled ledger.
Every message runs the same deterministic pipeline into an idempotent ledger entry — keyed (UUIDv5) so a re-sync or a duplicate SMS can never double-count. Unknown message shapes are induced server-side from a redacted skeleton only, cached as a template, then re-parsed on-device. The raw body never travels — the full diagram is in the walkthrough below.
Raw — device-local only
Rs.4,510 spent on Card xx1234 at ZOMATO on 02-06. Avl Bal Rs.18,240. Ref 559210
Redacted skeleton — the only thing that syncs
Rs.§AMT§ spent on Card §ACCT§ at §MERCHANT§ on §DATE§. Avl Bal Rs.§AMT§. Ref §REF§
Raw SMS never leaves the phone; only a redacted skeleton syncs — and the masker and redactor are one code path, so they can’t drift.
server re-checks redaction · 422
Parsing lives twice — TypeScript engine and Dart port — kept honest by language-neutral input → expected fixtures run in both.
engine matrix + 109 device tests green
A balance belongs to the account, not the card — deposit sightings collapse to one line per issuer and chain to the rupee; unexplained moves are flagged, not hidden.
fixed the “off by ₹1,000” fragmentation bug
Every fix began red. The server is hardened with fail-closed JWT, rate limiting, security headers, per-install keys, and no cloud backup.
12 server e2e tests green
7-slide walkthrough — auto-plays, or step with ← → · open full-screen ↗
A few decisions were deliberate. Money is integer paise, never a float — the one rule I refused to bend. Parsing was duplicated across two languages on purpose and kept honest by golden vectors, because the device must work offline and the server can't be the only source of truth. And the infra is intentionally boring, because a passion project shouldn't need a Kubernetes cluster.
Deciding up front that raw messages could never leave the device shaped every layer that followed.
The golden-vector approach was the single most valuable engineering decision in the project.
fast-check invariants — like by-tag totals reconciling to by-category totals — caught bugs example-based tests missed.
Why it's Android-only, and why it's paused. The whole idea rests on reading transactional SMS on the device, and Apple doesn't allow apps to read your messages — so an iOS version isn't possible. It's a passion project, so I built the core loop end-to-end — capture → ledger → the three numbers → per-account reconciliation — then chose to stop there rather than ship something I'd have to support.
I'm not distributing an APK; the goal was the engineering, not a launch. The full source is open (linked above). Future phases — payee/tag learning, settlement, liabilities, Drive backup, real Google Sign-In, TLS — are sketched if I ever revisit.