We got tired of our time-tracking platform, so we built the replacement
A first-party build with no client to anonymize. Billable runs our own time tracking, retainer balances, client portal, and invoicing, and it exists because the platform we were paying for could not express how we bill.
- RunsOur own billing
- Automated tests467 assertions
- End-to-end suites15
- Platform subscriptionRetired
- StackNext.js + Postgres
- Client-facingPortal, live
Client
Ops Automators (we built this for ourselves)
Size
Small consultancy, retainer and fixed-fee mix
Stack
Next.js · Postgres (Neon) · Stripe · Vercel
Why bother
We were paying for a well-known project-management platform and using it mostly as a timer with an invoice generator bolted on. It worked. The problem was the parts that didn't, and they were all in the same place: the way we bill.
Retainers are the obvious example. A client buys ten hours a month, uses six in July and fourteen in August, and unused hours roll forward exactly one month before expiring. Every platform we looked at either ignored that entirely or modeled it as a rigid bucket that resets. So the real balance lived in a spreadsheet, reconciled by hand, which meant the number we invoiced from and the number in the system were different numbers.
The second problem was the client-facing view, which didn't exist. Clients asked what their hours had gone on, and the answer was a PDF export somebody assembled. For a firm that sells operational rigour, explaining our own billing with a manual spreadsheet was an uncomfortable position to argue from.
The build-versus-buy call
We tell clients to buy off the shelf whenever off the shelf fits, so it's worth being explicit about why this one went the other way. Two tests have to pass before building beats buying: the per-seat cost of a platform that fits badly has to exceed the build cost within a couple of years, and the thing you need has to genuinely not exist.
Both passed here, but the second mattered more. Retainer overage carry-forward is not an exotic requirement. It is how a large share of professional services firms actually bill, and the platforms model it badly because they were built around fixed-scope projects. That is exactly the gap where a focused internal tool wins: not because the incumbent is bad software, but because your process is a rounding error in their roadmap.
The scope test we applied was the one we apply to client work: what is the smallest version that replaces the spreadsheet? Not the smallest version that replaces the platform. Everything else went on a later-phase list and stayed there.
What it does
Time is logged against tasks, with rate cards per client and per role, so the same hour bills differently depending on who worked it and under which agreement. Retainer balances carry overage forward automatically on the schedule the contract actually specifies, which removed the spreadsheet and the reconciliation with it.
Invoice drafts generate from approved time rather than from notes, and nothing reaches a client without passing a review gate. That gate is deliberate. The failure mode with automated invoicing is not a wrong number, it is a wrong number sent confidently, and the fix is a human looking at it for fifteen seconds.
There is a client portal showing exactly what was worked on and what it cost, which turned a recurring awkward email into a link. Underneath it sits the unglamorous half: restorable backups, an alerting path, and 467 test assertions plus 15 end-to-end suites, because this thing bills real money and a silent failure in it costs more than a silent failure almost anywhere else we run.
The migration, which was the hard part
Moving historical time entries out of the old platform was the part that took longest, and it is the part most likely to be true of your migration too. The API had the usual character: a locked task would not accept a billable flag, descriptions came back HTML-encoded, and the rate limit was tight enough that a naive loop took hours and then failed partway.
The approach was the one we use on client migrations. Pull everything first and reconcile the record count against the source before writing anything. Dry run into a staging database and compare totals per client, per month. Only then cut over, with the old system still readable for a while afterwards.
That discipline is why this section is short. Migrations go wrong when the export and import are treated as the work, and the deciding-what-not-to-bring-across is treated as an afterthought.
Why we publish this
Most consultancies demo slides. We would rather point at something we depend on, because the incentives are different when your own invoicing runs on the thing. Every edge case in this build was hit with our own money on the line: the partial payment, the disputed charge, the retainer that carried unused hours forward into a month with a rate change.
It is also the honest answer to a question we get asked: can you actually build software, or do you just connect other people's? This is the answer, and it is inspectable in the sense that it bills the invoices our clients receive.
If you have a process your platform cannot express and a spreadsheet quietly holding the real numbers, that is the same shape as this problem, and it is worth half an hour of conversation before you renew.
What we shipped
Six interconnected workflows.
Time capture with rate cards
Hours logged against tasks, priced by client and role, so the same hour bills correctly under different agreements.
Retainer balance with overage carry-forward
Unused hours roll forward on the contract's own schedule and then expire, calculated in the system rather than in a spreadsheet beside it.
Invoice drafting with a review gate
Drafts built from approved time, held for a human to check before anything reaches a client.
Client portal
An authenticated view of what was worked on and what it cost, replacing the assembled PDF export.
Restorable backups and alerting
Scheduled backups that have been restore-tested, plus an alert path, because a billing system that fails quietly is worse than one that fails loudly.
Historical data migration
Every prior time entry pulled, reconciled against source counts, dry-run into staging, and compared per client and per month before cutover.
Want a similar outcome for your team?
Most professional-services firms have the exact same quote-to-cash pain. Request a call and we'll scope yours on the spot.