Invoice Dashboard
LaunchedDeprecatedA billing product built and launched under contract — customers, invoices, subscriptions, card payments, sales tax, and the long tail of things real billing turns out to need.
A complete invoicing and billing application, designed and built under contract and launched into production. Roughly a hundred and fifteen commits between August 2024 and May 2025.
Invoicing looks like a solved problem right up until you build one. The happy path — create an invoice, send it, get paid — is a weekend. Everything after that is the product.
The stack
Next.js and TypeScript, with Postgres under Drizzle. Clerk for authentication, Finix for card and ACH processing, TaxJar for sales tax calculation, Resend with React Email for transactional mail, and @react-pdf/renderer to generate the invoice document itself. Tailwind and Radix for the interface.
The fourth integration is the one that shaped everything: QuickBooks. The client's books already lived there, which meant this could not be a system of record. Invoices and customers had to sync in both directions, with a write path for statuses and payments, so that the dashboard and the accountant never disagreed. Building alongside an existing ledger rather than replacing it is a much narrower brief than a blank page, and most of the design falls out of it.
Choosing a dedicated tax service rather than a rate table is the decision I would defend hardest. US sales tax is a function of jurisdiction, product category and the customer's specific address — here, the address of the property being serviced — it changes without warning, and getting it wrong is a liability rather than a bug.
The spec and the build disagreed
The requirements document specced React, Next.js, Vercel and Supabase, with a cost line beside each choice: nothing during development, $20 a month for Vercel and $25 for Supabase in production. It also called for magic-link email login and a deliberate one-account-per-email policy, on the grounds that a customer portal nobody can get into is worse than no portal.
What shipped kept the front half and replaced the back: Clerk for auth, Postgres under Drizzle for data. That gap between the document and the repository is normal and worth showing. The spec was right about the shape — passwordless entry, managed infrastructure, low fixed cost — and wrong about the specific vendors, which is the usual ratio.
What it actually took
What billing software really is, in the order I built it. After customers, invoices and a dashboard came:
- Subscriptions and line item categories, because most billing is recurring and needs to be reported on by category — monthly, quarterly and annual patterns, with quarterly invoices generating automatically on a fixed date each cycle
- Payment terms — due on receipt as the default, and net terms, which then determines what "past due" means
- A "# days past due" column everywhere it matters: the dashboard tables, the admin invoice view, and the customer's own view
- Send reminder on open invoices
- Draft invoices — save a half-built invoice and come back to it, which sounds trivial and is the difference between a tool people use and one they abandon mid-form
- Duplicate invoice, because most invoices resemble the last one
- Stored payment instruments, with a default
The awkward cases are the real work
Two changes near the end are the ones I would point to in an interview.
Admin viewing as customer — letting an administrator see exactly what a customer sees, then go further and add a payment instrument, change the default, and submit a payment on the customer's behalf. That is what support actually needs, and it has to be built deliberately rather than faked, because the permissions and the audit trail both matter.
Untaxable addresses — an account whose address falls outside what the tax service can calculate for, where the correct behaviour is to bypass the service and let a human set the amount manually. Every integration eventually meets a case it cannot answer, and the difference between a product and a demo is whether there is a path through when it does.
There is a similar one earlier: allowing an invoice to be created for an account with no administrator set, with email suppressed in that case. Somebody hit that in the real world, and the honest fix was to let the work proceed and turn off the part that could not function.
I built it under contract, so the client stays unnamed. The decisions above are mine.