A mobile-first dashboard that reads a founder's Stripe account and shows live MRR, churn, and a daily revenue email, without ever writing back.

Landing: the pitch, plus a live dashboard preview

Dashboard: MRR, ARR, churn, and a 30-day projection

Breakdown: MRR split by plan, computed from Stripe Prices

Churn feed: cancellations with reason, each row clickable

Customer detail: LTV, first paid date, and event timeline
// OVERVIEW
Founders running on Stripe can see every charge but not the number that matters: where MRR actually moved this month and who churned. I built Gleam Revenue to answer that on a phone. A founder connects their Stripe account through OAuth and gets live MRR, ARR, a churn feed, a plan-level breakdown, and a daily email digest. The whole thing is read-only by design.
That read-only promise is the spine of the build. Stripe only grants standard Connect platforms the read_write OAuth scope, so I enforced the constraint in code instead: a Vitest test walks the source tree and flags any stripe.*() call that isn't a list, retrieve, search, or an auth call, and it runs in CI on every push. MRR is calculated from Stripe Price objects rather than invoice amounts, then broken into new, expansion, contraction, churn, and reactivation movements so the chart explains itself.
The sync layer is built to survive Stripe's timing rules. Webhooks write the raw event to Postgres first and process it asynchronously through Inngest, which keeps the endpoint inside the 5-second ack window; idempotency is enforced twice, on Stripe's event.id and again on Inngest retries. Around a dozen Inngest jobs handle initial sync, nightly delta, weekly reconcile, MRR snapshots, the email digest, and disconnect handling. Row-level security landed in the first migration, not bolted on later, and Connect tokens are stored under AES-256-GCM with a unique IV each.
I shipped it across five weekly milestones on the locked stack: Next.js 14 on the App Router, Supabase Postgres with Drizzle, NextAuth v5, Inngest, Resend, and Sentry, deployed on Vercel. The headline result is a guarantee: a financial tool wired to live customer Stripe accounts that provably cannot write to them, checked by the test suite on every commit.
// KEY FEATURES
The product never calls a Stripe write endpoint. A Vitest test scans every stripe.*() call across app, lib, and the Inngest jobs and fails CI on anything outside a list, retrieve, search, and OAuth allowlist.
MRR is computed off Stripe Price objects per active subscription, then split into new, expansion, contraction, churn, and reactivation movements rendered as a stacked bar and a waterfall.
Each Stripe webhook persists the raw event first and hands processing to Inngest, so the endpoint acks inside Stripe's 5-second window. Idempotency is keyed twice: on Stripe's event.id and on the Inngest retry.
Stripe access tokens are sealed with AES-256-GCM using a fresh IV per token, with the encryption key kept separate from the database credentials.
A scheduled Inngest job sends a React Email revenue digest through Resend; in-app, the churn feed links straight to a per-customer page with lifetime value, first paid date, and the full subscription event flow.
// CLIENT REVIEW
Nurpais was easy to work with, thoughtful, and achieved all outcomes to a high standard. Would highly recommend for your project.
// RELATED WORK