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, over roughly 115 commits between August 2024 and May 2025.
Invoicing looks like a solved problem right up until the point where you build one. The happy path, which is to create an invoice and send it and get paid for it, is a weekend of work, and everything that comes after the happy path is the actual 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 integration that shaped everything, and that is QuickBooks. The client's books already lived in QuickBooks, which meant that this application could not be a system of record. So invoices and customers had to sync in both directions, and there had to be a write path for statuses and for payments, so that the dashboard and the accountant never disagreed with each other. Building alongside an existing ledger rather than replacing that ledger is a much narrower brief than a blank page is, 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, and of product category, and of the customer's specific address, which here is the address of the property being serviced, and 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 half, so it was Clerk for auth and Postgres under Drizzle for data. That gap between the document and the repository is a normal gap and it is worth showing. The spec was right about the shape, which is passwordless entry and managed infrastructure and low fixed cost, and the spec was wrong about the specific vendors, and that is the usual ratio.
What it actually took
This is what billing software really turns out to be, listed in the order I built it, and after the customers and the invoices and the dashboard came all of the following:
- 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 of the work are the two 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, which is an account whose address falls outside what the tax service can calculate for, and where the correct behaviour is to bypass the tax service and let a human set the amount by hand. Every integration eventually meets a case that it cannot answer, and the difference between a product and a demo is whether there is a path through the software 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.