Building Recevix
Recevix is a web dashboard that sends invoices for small marketing agencies and follows up on the ones that go late. It's client work, and it's live in public beta at recevix.cloud, free while the beta runs.
The client arrived with a written brief and the two promises the product would be sold on, Get Paid Fast and Never Chase. I turned the brief into a spec and a data model before writing any features, then built the first version from that. It covers both promises. The screenshots here use seeded demo data, and Northstar Marketing, the agency in them, doesn't exist.
The problem
Look at the invoice below, from the demo data. Cedar & Co. is billed $42,640 for September, and only $5,240 of that is the agency's money. The other $37,400 is ad spend, which is the client's own budget on its way to the ad platforms. An ordinary invoicing tool would add the two into one total, and the agency's revenue would look about eight times larger than it is.
Keeping those two apart is the first thing Recevix is for. The second is follow-up. In a small agency the person who should send the payment reminder is usually the person who owns the client relationship, so the reminder goes out late or doesn't go out.
What it does today
Invoices
Every line on an invoice is a management fee, ad spend, or something else, and the three are kept apart in subtotals, tax, and reports. Ad spend is untaxed unless a client is set up otherwise. An agency that bills no ad spend leaves that line off.
The fee comes from the client's pricing model, which can be a percentage of spend, a flat retainer, or a base fee plus a percentage. Cedar & Co. is on $1,500 plus 10%, so $37,400 of spend gives the $5,240 fee above. There's a calculator for trying a model against a spend figure before anyone is invoiced.
Recurring schedules run weekly, monthly, or quarterly, billed up front or in arrears. Arrears is the awkward case, because the ad spend isn't known until the period closes. Those invoices wait as drafts until someone confirms the real number.
The agency's client never logs in. They get a tokenised link to a page laid out for a phone first, where an invoice can be downloaded as a PDF and a proposal can be accepted or declined.
Collections
Once an invoice is sent, a reminder sequence is scheduled against it. The default sequence has six emails, starting three days before the due date and ending thirty days after, and the tone moves from friendly to firm to a final notice. Each email is a template the agency can edit. They go out at an hour the agency chooses, in the agency's own timezone, and they stop when the invoice is paid or voided.
Late fees are optional. A policy can be a flat fee, a monthly percentage, or a daily percentage, with a grace period and a cap, and it can be set for the whole agency or for one client. The fee is always worked out from the invoice total, never from the total plus an earlier fee. In the calculator below, a $5,000 invoice that is 45 days late at 1.5% a month, with five days of grace, comes to $150. Forty days are counted, and every started 30-day period counts as a month.
A final notice, a late fee, a voided invoice, a bulk send, or a deleted client can each be held for the agency owner to approve or reject. The first late fee on an invoice is held by default.
Everything that changes state is written to an activity log, including what the system does by itself.
Cash flow
The forecast places each open invoice on the date that client is likely to pay, using their own average lateness. It then adds the recurring invoices that haven't been sent yet and totals the result at 30, 60, and 90 days. A monthly collection report exports as CSV or PDF.
How it's built
Recevix is one Next.js application with no separate API. Pages read through services, Server Actions do the writes, and the scheduled jobs are HTTP endpoints in the same app.
The money rules sit apart from everything else. Pricing, late fees, invoice status, billing periods, reminder timing, and the forecast are plain functions in a library that imports nothing from the database or the framework, and they have unit tests. Amounts are integer cents and rates are fractions throughout.
Each invoice keeps a copy of the pricing model and late-fee policy it was created under, so changing a client's rate next quarter doesn't alter last quarter's invoices. An invoice's status can only change through one function, which checks the move against a small state machine. Totals are recomputed by one other function.
Email is tied to the database transaction. A write that sends mail queues it, and the mail goes only after the commit succeeds, so a payment that rolls back doesn't produce a receipt.
The scheduled work is six jobs: generating recurring invoices, marking invoices overdue, applying late fees, sending reminders, following up on missing documents, and working through the accounting sync queue. Schedulers retry and overlap, so each job is written to change nothing and send nothing on a second run. For reminders, each row is claimed with FOR UPDATE SKIP LOCKED, and the reminder log has a unique key on invoice and step.
Two timing cases needed their own handling. An invoice sent after some of its reminder dates have passed doesn't replay them, because send times are worked out when the invoice goes out and anything already in the past is marked skipped. And 9am on the due date is a different instant for each agency and shifts with daylight saving, so dates are stored without a time and converted in the agency's timezone when a reminder is scheduled.
Every service function takes the tenant first and filters by agency id, and the integration tests include tenant-isolation checks. Client documents go from the browser to storage on a presigned URL, and the server checks the size and file signature of what arrived before recording it.
The stack
Next.js 16 with the App Router and Server Actions, React 19, and TypeScript. The interface is Tailwind 4 and shadcn/ui, with Recharts for the charts. Data is PostgreSQL through Drizzle. Sign-in is better-auth, using its organization plugin so that an organization is an agency. Mail goes through Resend with React Email templates, PDFs come from @react-pdf/renderer, and uploads go to S3-compatible storage. Tests are Vitest, with the integration tests running against a real Postgres.
With no email or storage keys set, mail is written to a local outbox folder and files to a local directory, which is how the app runs on a laptop without those accounts.
What's still needed
These were left out of the first version and are parked for later:
- Online payments. Today a payment is recorded by hand when it arrives, as a bank transfer, ACH, wire, check, card, or cash.
- Real accounting connections. The QuickBooks sync is an interface and a queue with a stub provider behind it. Xero and FreeAgent are named in the schema but not built.
- SMS and WhatsApp reminders. Reminders are email only.
The payment and accounting parts of the data model were kept generic with these in mind.