A modern invoicing tool for a small team: create invoices, track payments manually with proof-of-payment attachments, and run monthly recurring billing that generates invoices automatically. Built for one business with Admin / Staff roles.
Product spec:
docs/PRD.md· Technical spec:docs/BACKEND.md· Backend decisions:docs/BACKEND_NOTES.md
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router) + React 19 + TypeScript |
| Styling | Tailwind CSS v4 (custom "financial ledger" design system, light + dark) |
| Database | Neon Postgres via Drizzle ORM |
| Auth | Better Auth (email + password, DB sessions, roles) |
| Files | Vercel Blob — private proof storage, signed URLs |
@react-pdf/renderer (server-rendered invoices) |
|
| Recurring | Vercel Cron → daily generation job |
| Hosting | Vercel |
Money is stored as integer minor units (cents); quantities x1000; tax in basis points — never floats.
This machine uses pnpm via corepack (no global npm). Run Node from Homebrew:
export PATH="/usr/local/opt/node/bin:$PATH".env.local already has generated BETTER_AUTH_SECRET and CRON_SECRET. Fill in the two account-specific values:
DATABASE_URL— create a free project at console.neon.tech and paste the pooled connection string.BLOB_READ_WRITE_TOKEN— Vercel dashboard → Storage → Blob → create a store → copy the token.
corepack pnpm db:migrate(or corepack pnpm db:push to push the schema directly during development.)
corepack pnpm devOpen http://localhost:3000, click Sign up — the first account automatically becomes the Admin and creates your business. Fill in business settings, add a customer, create your first invoice.
After signing up:
corepack pnpm db:seedAdds two sample customers, an unpaid invoice, and a monthly recurring plan.
| Command | What it does |
|---|---|
pnpm dev |
Start the dev server |
pnpm build / pnpm start |
Production build / serve |
pnpm typecheck |
tsc --noEmit |
pnpm lint |
ESLint |
pnpm db:generate |
Generate a new migration from schema changes |
pnpm db:migrate |
Apply migrations |
pnpm db:push |
Push schema directly (dev) |
pnpm db:studio |
Drizzle Studio (browse data) |
pnpm db:seed |
Seed demo data (sign up first) |
A daily Vercel Cron (vercel.json → 0 2 * * *) calls GET /api/cron/recurring, protected by CRON_SECRET. It finds active plans whose nextRunDate has arrived and generates the next finalized invoice. You can also click Generate now on any plan for a manual cycle.
- Phase 1 (built): onboarding, customers, invoices, manual payments + proof, tracking, recurring plans.
- Phase 2 (built): team invites with roles (token-bound accept), invoice email (PDF attached) + recurring auto-email, reporting dashboard + CSV export.
- Phase 3 (built): payment reminders (before-due + overdue cadence), activity/audit log, trash with restore + 30-day auto-purge.
See docs/PRD.md for the full phased plan.
In Settings → Payment reminders (admin), enable a reminder a set number of days before the due date and/or recurring overdue reminders (every N days, capped). A daily cron (/api/cron/reminders, 08:00) emails the customer the invoice PDF and records each reminder so none is sent twice. Uses the same free email setup above.
The admin-only Activity page shows who did what and when across invoices, payments, customers, recurring plans, and team changes. Deleted invoices/customers/recurring plans move to Trash (a tab on the same page) where they can be restored; a daily cron (/api/cron/purge-trash, 03:00) hard-deletes anything older than 30 days. All three crons are protected by CRON_SECRET.
An admin invites a teammate from Team → they get a link (/invite/<token>). Membership is granted only by presenting that token (never by email alone), so knowing an invited address is not enough to join. Email delivery of the link uses the same email setup below; until configured, the admin copies the link shown after creating the invite.
No config needed in development — the app spins up a free Ethereal test inbox and the "Send" dialog shows a preview link to view the exact email. For real delivery set SMTP_HOST/PORT/USER/PASS + EMAIL_FROM (any free SMTP works: Gmail app password, Brevo, Mailtrap, Resend SMTP). Recurring plans with auto-email on will email each generated invoice.
- Push to a Git repo and import into Vercel.
- Add all
.env.localvariables in the Vercel project (Environment Variables). - Vercel picks up
vercel.jsonand schedules the cron automatically (it sendsCRON_SECRETas a Bearer token). - Run
corepack pnpm db:migrateagainst your productionDATABASE_URL(or use a deploy hook).