Letting AI assistants work in the mailbox and the ledger, safely
We wanted AI assistants that could triage mail and post to the accounts. A general API in an agent's hands can send, delete or post the wrong number, so we built MCP servers where the dangerous verbs simply don't exist.
At a glance
- 12
mailbox tools, none of which can send
- 26
accounting tools behind a dry-run gate
- 6
instances in production
- 61
tests on the mailbox server alone
The challenge
AI assistants are genuinely useful on a busy mailbox and a backlog of bookkeeping. They're also very confident, and a general-purpose mail or accounting API gives them the same power as the account owner: send anything, delete anything, post anything.
We wanted an assistant that could do the useful work on live mailboxes and live books, and was physically unable to do the dangerous things, rather than politely asked not to.
Architecture
Swipe to explore the diagram
Every call passes a fence before it reaches the real system. The mailbox server has no send path, and every accounting write is verified by reading it back.
What we built
Mailbox server
- 12 tools for listing, reading, moving and filing mail, plus attachments and drafts
- One mailbox per instance, with no mailbox parameter for a model to change
- Outbound is draft-only. There's no send code path, so a person reviews everything before it leaves
- Delete moves to Deleted Items and nothing is ever hard deleted
- Attachment bytes never pass through the model's context: they're staged on the server, fetched by URL and expire after 10 minutes
Accounting server
- 26 typed tools over the QuickBooks Online API, mostly reads and reports, with a deliberately small set of writes
- Every write is a dry run until explicitly committed, and idempotent
- A read-back gate checks each posting ties to the penny and rolls it back if it doesn't
- One codebase for VAT-registered and non-VAT companies, switched by config that fails closed at boot
Delivery
- A base repo for the code and runbook, and a config-only deploy repo per instance that pins a tag, so an upgrade is a pull request
- Unit tests with Graph and MSAL faked, dependency and image scanning in CI, and hash-pinned dependencies
Engineering decisions
The tool surface is the security boundary
We don't ask a model nicely not to send mail. The capability isn't there to call, and mailbox-scoped RBAC backs that up a layer further down.
Money never moves automatically
Bill payments, card payments and tax payments are intentionally absent from the accounting server. It can record what happened. It can't pay anyone.
Gross is sovereign
The caller declares the document total and the server derives net and VAT, refusing any line that doesn't reconcile. A wrong VAT figure fails loudly instead of landing in the books.
Fence the company
Before every write the server compares the live company identity with the one it was configured for. If they differ, or either is missing, the fence closes completely.
Fail visibly
Identity calls carry connect and read timeouts, and auth failures surface the real error code. A hang that looks like a slow tool is far worse than an honest error.
Outcome
Assistants now triage, extract documents and draft replies on live mailboxes, and post daily takings, supplier bills and month-end journals into four companies' books, gated and reconciled. Bringing another company on board took a read-only harvest and a config file.