๐ŸฆŠ FoxyInvoice

After the Rust cutover: offline-first invoicing, a real tax engine, and one loud week of shipping

The build-in-public series covered the rewrite โ€” fifteen chapters of porting a .NET invoicing SaaS to Rust. The cutover happened on a Tuesday. This post is about everything that came after: the first week running Rust in production, the bugs real users found, and the features that turned a fast backend into a product freelancers can bet their invoicing on. Spoiler: your drafts now survive a browser crash, and the sales-tax engine finally explains itself.

Watch the 39-second tour โ€” templates, the client's invoice view, the sales-tax guide, and where to start:

The scorecard

Seven days of production Rust, measured in shipped user-facing features โ€” not commits, lines, or benchmarks:

ShippedWhat it means for you
Offline-first invoicingDrafts save as you type and survive a browser crash. Reads work offline. Lost writes queue themselves and replay when the connection returns.
A sales-tax engine you can driveDeclare nexus per state yourself (Tax โ†’ Nexus), override taxability per product type, and see exactly why a line is taxed โ€” or why it honestly isn't.
Tax-ready exportsOne click downloads your year's invoices as CSV, QuickBooks IIF, or Tally XML โ€” balanced ledgers, localized dates.
Quotes that close themselvesSend a quote; the client clicks Accept on the shared link and a draft invoice appears in your workspace, with an email telling you the new number.
Payment methods on every invoicePayPal, Venmo, Stripe links, full bank details โ€” printed on the PDF and the client's payment page.
A PDF worth the paperBranded header, multi-page flow with page numbers, exact per-line tax amounts, visible discounts, and nothing ever clipped.
Multi-companyOne login, up to five companies, fully isolated data, numbering, and branding โ€” switch without logging out.
Invites with rolesInvite your bookkeeper to one company with viewer rights; the activation link does the rest.
The port, finishedA mechanical audit of every .NET endpoint against the Rust router: every gap found was closed within the week. Nothing is half-ported.

The crash that proved the architecture

The best feature of the week wasn't planned โ€” it was a browser crash. Mid-invoice, the tab died. In every other invoicing app that's the moment your work dies with it. Here, the editor had been autosaving to local storage the whole time โ€” tenant-scoped, every field, line items included. Reopening the page offered the draft back, timestamped. One click later the invoice was exactly where the crash found it.

That's the local-first architecture earning its keep: the app keeps a full read cache and a write-ahead queue in your browser. Reads render instantly and work offline; writes land in an outbox that replays, idempotently, when you're back. The server recomputes every total on arrival โ€” the client's math is advisory, the server's is truth. Crash-safe isn't a mode you switch on; it's just what happens when the UI stops assuming the network is a given.

The $0 tax mystery โ€” and why it was the law, not a bug

Week one's loudest report: "tax computes zero on my drafts." The investigation took three layers and produced the most-shared thing we've written โ€” a plain-English guide to sales-tax nexus โ€” but the short version belongs here because it says something honest about the engine:

A $0 tax line with no nexus isn't a bug. It's the law, encoded.

The engine only taxes a jurisdiction where your business has declared a collection obligation โ€” physical presence, or the post-Wayfair economic thresholds (typically ~$100K in sales or 200 transactions; California sets ~$500K and generally doesn't tax services at all). Until you declare nexus, taxing anything would be wrong. What was missing was the ability to declare it โ€” the cutover had left nexus management unported. That's shipped now: Tax โ†’ Nexus, one row per state, and the invoice editor warns you out loud when a line targets a state your workspace hasn't declared. Which matters more than it sounds, because โ€” as our founder discovered live โ€” nexus is per company. Declare it in the workspace you invoicing from, not the one you're sitting in.

Quotes that close themselves

Quotes used to be read-only artifacts: send, wait, and find out by email what the client decided. Now the shared quote page carries Accept / Decline buttons wired to the backend. Accept, and the server clones the quote into a fresh draft invoice โ€” new number from your sequence, due in 30 days, line items copied with their computed tax โ€” and emails you the new invoice number. Decline, and the quote voids itself and tells you. Your client gets certainty; you get an inbox that says "Quote QUO-2026-0004 was accepted โ€” new draft INV-2026-0005 created."

And on your side, the Invoices list finally has the Convert to Invoice button it always advertised โ€” same clone, on your terms.

Invoices your client can actually pay

Two upgrades to the money conversation. First, payment methods: tell FoxyInvoice where you accept money โ€” PayPal, Venmo, a Stripe link, or full bank details โ€” and it prints a PAYMENT section on every PDF and a "How to pay you" block on the client's online invoice. No more emailing wire instructions as a follow-up.

Second, the PDF got rebuilt from a text dump into something clients judge you by: your brand color in the header, a real line-item table with exact per-line tax amounts (rate label included), discounts that show as "โˆ’10%" on the line they apply to, totals that visibly add up, and multi-page flow โ€” long notes and terms flow onto numbered continuation pages instead of being clipped. Every number matches the app to the cent, because the renderer reads the same stored totals the app does.

One week of production bugs, fixed the day they were found

Real users on a fresh stack find things no test suite imagines. The honest list from week one โ€” each fixed and shipped the same day it was reported:

This is what the rewrite bought: a backend small enough to understand end-to-end, so every one of these was a same-day fix rather than a sprint. The full-Rust stack now runs both the API and every scheduled job (recurring invoices, reminders, lead radar) from two small container images.

What's next

More of the same trajectory: making the tax engine explain itself everywhere it computes, more of the client-facing surfaces (reminders, receipts) carrying your brand, and growing the AI-assistant surface โ€” the MCP server is on the official registry, and it shares this exact REST core, so everything in this post is already available to your AI assistant.