One alert, one shared path.
- 01
Receive
An incoming transaction alert reaches the edge service.
- 02
Validate
Check the incoming message and parse its transaction information.
- 03
Reconcile
Deduplicate the record and use the reported balance as the anchor.
- 04
Update
Refresh the mobile view, widgets, and applicable notifications.
The challenge.
A useful daily spending view should stay current without manually entering every transaction. Incoming alerts also need to survive duplicate delivery, missed messages, and background execution limits.
I built a personal app around bank-reported balances, shared ingestion logic, and a small event-driven backend.
The boundaries.
- Use transaction information without giving the app a way to move money.
- Deduplicate alerts arriving through more than one ingestion path.
- Separate administrative access from read-and-log capabilities.
- Keep mobile widgets and notifications current outside an active app session.
How it fits
together.
A TypeScript service runs on Cloudflare Workers with Hono, Turso/libSQL, and Drizzle. Email and HTTP ingestion share transaction parsing and deduplication. Scheduled work and background notification delivery feed an Expo client and widgets. The implementation checks incoming mail authentication verdicts before ingestion; this is a personal product, with no money-movement functionality.
The decisions
behind the details.
Anchor to the reported balance
Accumulating transactions indefinitely makes every missed event matter forever. A bank-reported balance gives the product a fresh reconciliation point.
TradeoffA stale alert must remain distinguishable from a current balance.
Share the ingestion path
Email, HTTP, and scheduled work should agree on what a transaction means. Shared parsing and deduplication make a second delivery a repeat, rather than a second expense.
TradeoffChanges to parsing need regression checks against the existing event formats.
Separate capabilities
A device that reads and logs transactions does not need every administrative action. Scoped tokens keep those responsibilities separate.
TradeoffClients must handle a denied operation explicitly instead of assuming every token has full access.







