Mobile
Nqood — نقود
Arabic-first AI money tracker · full-stack, solo — schema to UI to ops
1 / 6
Overview
Nqood is a production, Arabic-first (EGP / MENA) personal money-tracking product — a Flutter app on a self-managed ASP.NET Core backend — that I designed and built end-to-end, solo, from database schema to UI to deployment and ops. It pairs an agentic AI assistant that advises, acts, and auto-captures transactions with a CQRS-inspired polyglot persistence layer (PostgreSQL + Redis + MongoDB) that turns unbounded analytical queries into bounded, pre-aggregated snapshot lookups. Live on Google Play, wrapped in a device-to-database defense-in-depth stack, and operated with zero-downtime deploys, PITR backups, and full monitoring.
The problem
Egyptians juggle cash, bank cards, and wallet apps with no single source of truth. Existing trackers are slow to log, ignore local bank-SMS formats, don't understand Arabic, and can't enforce rules like saving goals or expense limits at the moment money is spent. Nqood makes capture effortless — by voice, by reading bank messages on-device, or by asking an AI assistant — while keeping every number grounded in the user's real data and every byte on infrastructure the owner controls.
Architecture
CQRS-inspired polyglot persistence: PostgreSQL is the durable system of record; Redis holds live daily totals via atomic HINCRBYFLOAT counters (O(1) writes, no race conditions) and serves the read path for 'today'; MongoDB stores promoted daily/monthly snapshot documents. Hangfire background jobs promote snapshots, run the rule lifecycle, generate recurring occurrences, send reminders, roll up AI usage, and repair/backfill. Every read model is deterministically reconstructable from PostgreSQL — Redis holds no irreplaceable data. The whole thing runs behind a layered defense-in-depth stack: device RASP → Cloudflare edge (network + mTLS origin lock) → nginx TLS → API → internal-only, least-privilege databases.
Tech stack
Key features
- Agentic AI assistant that advises (grounded Q&A, cash-flow forecasting, spending analysis), acts (one-tap confirmable in-app workflows), and captures (auto-drafts transactions from text, voice, bank SMS & notifications)
- Privacy-first on-device capture: reads financial messages even when the app is killed, gated locally against a server-driven allow-list — OTPs and personal messages never leave the device
- Layered, cost-optimizing parse pipeline (regex → Redis cache → pre-LLM guard → Gemini → entity resolver) where each layer short-circuits the paid model below it
- Arabic-aware entity matching that normalizes spelling variants to a canonical key, with a 'recommend, don't force' policy that suggests create-new instead of a wrong match
- Financial rules engine (saving goals, expense limits, minimum balance) evaluated per-transaction against bounded snapshots with a confirm/cancel flow
- Offline-first mobile: local queue + connectivity-aware replay, deep native integration (home widgets, shortcuts, deep links, actionable notifications), biometric app-lock
- Scheduled recurring transactions, reminders, and per-user DST-safe timezone with configurable week/month financial periods
Engineering deep-dive
AI layer — Advise · Act · Capture
- Grounded advisor agent assembles a bounded per-turn context (financial digest, safe-to-spend, live goal status, deterministic cash-flow forecast, recent history) and returns strict JSON — the model explains the numbers, it never computes them.
- Proposes confirmable in-app actions across 8 families — transactions, scheduled/recurring items, transfers, categories/merchants/payment-methods, financial rules, and settings — with server-side name→ID resolution and prerequisite chaining.
- Safe by construction: treats all conversation as data (prompt-injection resistant), never reveals internal prompts/IDs, refuses out-of-scope requests, mirrors the user's dialect, and only ever proposes — the user confirms in-app.
- Multimodal, source-agnostic capture — voice (single transcribe-and-extract call), bank SMS, notifications, share-sheet, and manual entry — all converging on one pre-filled, user-confirmed transaction draft.
- Voice in / speech out: spoken questions transcribed server-side and answered in one pass; replies synthesized to speech (PCM→WAV) with client-side caching so replays are free.
On-device capture engine (Android)
- Reads incoming bank SMS & app notifications via background isolates + a NotificationListenerService foreground service — resilient to reboot and to the app being killed.
- Local privacy gate runs before any network call: allow-list match on sender-id / package-id plus an OTP-exclusion and money-signal heuristic (unit-tested).
- Time-independent FNV-1a content-hash dedup, reserved before the network call, absorbs OS message replays and listener re-binds so each message captures exactly once.
- Offline raw-capture queue drained by WorkManager on reconnect, with a visible 'detecting…' placeholder upgraded in place; a server-flag mirror drops messages even from a still-bound receiver.
- Bank coverage ships without an app release — the allow-list is served from the backend, cached with a TTL, and grown from anonymized, digit-redacted 'unknown sender' telemetry.
Data & persistence architecture
- PostgreSQL is the durable system of record; Redis holds atomic real-time daily totals (HINCRBYFLOAT, O(1) write cost); MongoDB stores promoted daily/monthly snapshot documents.
- Reduced analytical queries from scanning unbounded transaction histories (O(T)) to aggregating a bounded set of daily snapshots (O(D)) — powering AI, dashboards, forecasting, and rule evaluation.
- Every read model is deterministically reconstructable from PostgreSQL; Redis holds only caches, counters, tokens, or rebuildable snapshots.
- TPH inheritance for Transactions (Income/Expense) and Financial Rules (SavingGoal / MinimumBalance / ExpenseLimit) via EF Core discriminator columns.
- Global soft-delete via EF Core query filters, implicit per-user ownership (CreatedBy) enforced by a global query filter, and an automatic CreatedAt/UpdatedAt/DeletedAt audit trail.
Backend, security & ops
- Layered defense-in-depth: device RASP → Cloudflare edge (network + mTLS origin lock) → nginx TLS/headers → API → internal-only, least-privilege databases.
- Full ASP.NET Identity + JWT auth: strict-validated HMAC tokens, Redis refresh-token rotation, lockout, Google OAuth, and GDPR account deletion.
- Hangfire background jobs: snapshot promotion, rule lifecycle, scheduled transactions, reminders, AI-usage rollup, and backfill/repair.
- Zero-downtime GHCR-over-SSH deploys with one-shot migrations, health checks, monitoring/alerting, pgBackRest PITR, off-site backups with restore drills, and an append-only audit log.
Flutter mobile app
- Cubit/BLoC clean architecture with a polished, animated UI fully localized in Arabic and English (RTL-aware, light/dark theming).
- Offline-first sync layer: local queue + connectivity-aware replay against the same backend I built.
- Deep native integration: home-screen widgets, app shortcuts, deep links, and actionable local notifications wired to background handlers.
- On-device security: RASP root/tamper detection, encrypted secure storage, and biometric app-lock.
Architecture diagrams
click to zoomAI capture, matching & advisor agent
Mobile → backend flow: the on-device privacy gate and background isolates, the layered cost-optimized parse pipeline, the Arabic-aware matching engine, and the grounded tool-proposing agent.
Polyglot storage & Redis keyspaces
How state is split across durable PostgreSQL, in-memory Redis hot state, and MongoDB snapshots — plus the Hangfire jobs that promote and roll up data between them.
Database schema (ERD)
Every PostgreSQL table (PKs, FKs, TPH inheritance, soft-delete + per-user query filters) alongside the MongoDB snapshot documents rebuilt from Postgres + Redis.
Security considerations
- Device-to-database defense-in-depth: RASP → Cloudflare mTLS origin lock → nginx TLS → API → internal-only, least-privilege databases
- Full Identity + JWT auth with Redis refresh-token rotation, lockout, Google OAuth, and GDPR account deletion
- Privacy-first capture — OTPs and non-financial messages never leave the device; the feature can be killed server-side even from a bound receiver
- Self-hosted by design — user financial data never leaves infrastructure the owner controls
Challenges
- Making AI capture cheap: a layered pipeline where regex, a Redis structural cache, and a pre-LLM guard short-circuit most captures before they ever reach a paid model
- Reading real-world Arabic bank SMS reliably — the 'balance trap' (amount vs. running balance), OTP/reference/masked-card traps — with a hardened prompt and few-shot corpus
- Keeping rule evaluation and analytics O(D) instead of O(T) while guaranteeing every derived read model can be rebuilt from PostgreSQL after a Redis flush
- Operating a self-managed VPS with production-grade guarantees: zero-downtime deploys, PITR backups, restore drills, and monitoring — solo
Outcome
- Designed and shipped a full-stack product solo — schema, backend, mobile app, deployment, and production ops — live on Google Play
- Built an end-to-end agentic AI layer (advise / act / capture) with a killed-app-safe, privacy-first on-device capture engine
- Architected a CQRS-inspired polyglot persistence layer cutting analytical queries from O(T) to O(D) while keeping all read models reconstructable from PostgreSQL
- Operated a self-managed VPS with defense-in-depth security, zero-downtime deploys, and PITR backups with restore drills





