Skip to content
← All work
The Revenue OS command centre, showing the next action to approve as a batch

Revenue OS

A pipeline that cannot record what it cannot prove.

Year
2026
Role
Design and engineering
Stack
TypeScript · Next.js · Supabase · Postgres · Vercel Cron · Gmail API

The third time the same question turned up, pointed at my own income: how do you know a record is true? Proof-Lens asks it of a file and Melody of a filing. Revenue OS asks it of a claim my own system makes about itself — that an email was sent, to someone who agreed to be contacted, after a human said yes.

This one is private and stays private. It runs a real pipeline containing real people's contact details, so there is no repository to read and no live link to follow. What is shown here is the part that is safe to show and also the part worth showing.

The problem

Anything that sends email on your behalf is a small machine for embarrassing you. The failure modes are not exotic: a message goes to someone who asked never to hear from you again, a retry sends the same thing twice, a batch you never approved goes out because a script read the wrong flag, or the system records a send that never actually happened and you follow up on a conversation that does not exist.

Every one of those is a data-integrity problem wearing a sales hat.

The approach

The rules live in the database, not in the application.

The outreach boundary — drafted, approved, verified sent, above the exceptions the database raises

A Vercel cron job is the only execution path. It runs once a day, sends only the current London date's approved group, records lineage before marking anything sent, and is capped at twenty messages. It cannot approve, draft, rewrite or add a recipient.

What was hard

Deciding that the application layer could not be trusted — including the parts I wrote.

Every one of those guarantees started life as a check in TypeScript, which is exactly where such checks go to die: a new code path forgets one, a script written in a hurry bypasses the service entirely, an agent with database credentials does something reasonable-looking at three in the morning. Moving them into triggers means the rule holds no matter what touches the row, and it means a bug in my own code fails loudly instead of sending something it should not have.

The other hard part was accepting that an unprovable send is not a send. If the provider outcome cannot be recorded, the message is held for human review and never automatically retried — because the alternative is a system that resolves its own uncertainty by guessing, which is the failure this whole site is about.

What I'd change

The twenty-a-day cap is a number I chose rather than derived, and it is doing two jobs at once — protecting deliverability and protecting me from myself. Those deserve separate controls, because the right answer to the first is a function of domain reputation and the right answer to the second is a matter of judgement.

The approval model is per-batch, which is the right unit for sending and the wrong unit for reviewing. Reviewing twenty messages as one decision means the twentieth gets less attention than the first, and the design currently makes that invisible rather than surfacing it.

What I would build next is the reconciliation pass. The system proves what it sent; it does not yet prove that what it recorded matches what the provider believes a week later. Everything is in place for that check and it does not exist, which means the ledger is trustworthy at write time and merely probably correct thereafter.