skip to content
project archive / case file

PayNudge

AI drafts the awkward email. You still hit send.

shippedSolo build — product, backend, frontend, AI pipelineProof-of-work project
PayNudge — hero shot
PayNudge — hero shot

380+

automated tests

3

AI tone tiers

0

emails sent without approval

100%

RLS-covered tables

01 / the real problem
01

chasing money is uncomfortable

freelancers avoid the follow-up email longer than they should, and invoices quietly go from due to forgotten.

02

manual chasing doesn't scale

past a handful of clients, remembering who's overdue — and how firmly to word the next email — turns into a spreadsheet nobody maintains.

03

full automation feels risky

nobody wants a bot emailing their clients unsupervised. the trust gap is real, and it's the reason most people give up on automating this.

02 / the approachbuild log, in order
  1. human approves, AI drafts

    every follow-up is generated first, reviewed second. nothing reaches a client's inbox without an explicit click.

  2. a state machine, not a prompt

    the draft pipeline runs on LangGraph — structured nodes for drafting, validating, and retrying — instead of one long prompt hoping for the best.

  3. tone escalates on a schedule

    polite → firm → final notice, gated by a strict tier-increase guard so the same tone never fires twice in a row.

  4. business rules stay pure functions

    escalation logic, invoice numbering, and validation are side-effect-free and tested with property-based tests (fast-check) — thousands of random inputs, not a handful of examples.

  5. RLS as the actual security boundary

    every table enforces Row Level Security in Postgres. a bug in a route handler still can't leak another user's invoices.

  6. PDFs with no browser in the loop

    invoices render in-memory with pdfkit — no headless Chrome, no filesystem writes, no cold-start tax.

03 / how it's built
frontend
Next.js 14, React 18, TypeScript, Tailwind, shadcn/ui
backend
Express.js (TypeScript, ESM)
database
Supabase — Postgres, Auth, Storage, RLS
ai
Google Gemini via LangGraph state machine
email
Resend (transactional delivery)
pdf
pdfkit — in-memory generation
testing
Vitest + fast-check (property-based), 380+ tests
04 / what it doesclick a drawer

05 / a few screensimages land here
image coming soon
dashboard — overdue, outstanding, and what needs attention
image coming soon
invoice detail — timeline, status, and the PDF
image coming soon
follow-up preview — AI draft, edit, approve
image coming soon
calendar view — due dates at a glance
06 / why, not just what

why LangGraph instead of one Gemini call?

a single generateContent() call works until it doesn't. the graph gives structured retries, failure counts, and a tier guard as named nodes — each debuggable on its own.

why pure validators instead of Zod for business rules?

Zod is great at parsing shapes. "escalation tier must strictly increase" isn't a shape problem, it's a logic problem — a plain function is easier to test and reason about.

why pdfkit instead of a headless browser?

pdfkit renders a clean invoice from pure data in ~50ms. no Chrome, no Docker, no cold starts — a fair trade for a single-page layout.

why RLS instead of app-level auth checks?

defense in depth. even a buggy route handler can't leak another tenant's rows, because Postgres enforces the boundary, not application code.

that's the whole file← back to the archive