Case study

Dispatch Desk

Approval-gated email automation for client-facing teams

Working prototype — built to demonstrate the approach to prospective clients

An agentic email workbench for teams whose inboxes carry client correspondence. It reads, triages, drafts and schedules against a live mailbox — and stops for a human decision before anything is sent, because unsupervised automation is not something a business will put in front of its customers.

~1.5s
A mailbox action, click to result
30s+
The same fetch routed through a model
19
Server tools, 8 of them approval-gated
36
Automated checks, no network required
Python 3.13LangChain 1.3LangGraphMCPOpenAI GPT-5Next.js 16React 19TypeScript

The problem

In any business that runs on client correspondence, a large share of skilled time goes on the inbox rather than the work. Someone reads every message to find the few that matter, then writes similar replies to similar requests. The expensive part is the reading, not the typing.

The same shape appears wherever documents queue up to be judged — an HR team opening every CV to find a shortlist has exactly the same bottleneck.

No business will let software send mail to its customers unsupervised. Most demonstrations of this idea quietly assume the agent may send — and that assumption is what makes them undeployable.

A wrong internal summary is an annoyance. A wrong message sent to a client, in the company’s name, cannot be recalled. So the requirement is not an AI that handles email. It is one that does the reading at machine speed and then stops, without making the stopping so frequent that the time saved is given back.

What it does

It loads the inbox in about a second, classifies unread mail by priority and whether it genuinely needs a reply, drafts responses in the correct conversation thread, and proposes meeting times from real calendar availability.

Reversible actions — opening, archiving, marking read — happen on a click. Irreversible ones stop at an approval showing exactly what would happen, where it can be accepted, edited or refused with a reason.

you@example.comLIVE1 AWAITING YOUCAN SEND
InboxUnreadSent
Jane OkonkwoCoffee next week?reply2h
Northwind DigestYour weekly roundup is here5h
Acme RewardsOnly 3 days left on your points1d
Priya RamanIntegration review — Thursday?reply2d
1 selectedArchiveMark readReplySchedulesolid = acts now · dashed = asks the agent
Send emailAWAITING YOU
To
jane@northwind.exampleexternal1 of 5 allowed
Subject
Re: Coffee next week?
Thread
↳ Replying in thread 1a0527a2dd

Hi Jane,

Tuesday works — I have 3pm free. Shall we say the place on Wells Street?

Best, Bijith

Approve AEdit EReject R▸ raw arguments
The approval checkpoint, shown with example data. The threading verdict and the external-recipient flag are the two things a reviewer needs before deciding.

How it works

The design question that mattered was not how to give a model email tools. It was deciding what should not go through the model.

A first version routed everything through the agent. Fetching the inbox took over thirty seconds, because a model had to read the request and decide to call a search tool. The same fetch made directly took about a second and a half. Those two numbers settled the architecture.

Dispatch Desk (browser)message list · reading pane · assistant dockDIRECT — no model in the pathAGENT — the model decidesDeterministic graphlist · open · archive · mark readallowlist excludes send and deleteAgent graphtool scoping · dynamic prompt · summarisationcompose · schedule · answerHuman approvalapprove · edit · rejectMCP server (Gmail + Calendar tools)19 tools, 8 of them gated · OAuth held server sideGmail & Google Calendarabout 1.5sclick to result30s+model decides first
Clicking a message never waits on a language model. Only composition does — and anything it proposes to send stops at the approval gate.

Why it can be trusted

Approval exists to gate actions a model chose. Clicking Archive on rows you selected is already your decision, so the direct path does not prompt. Asking the assistant to archive does — and that asymmetry is deliberate.

Model requestsa toolClassify by verbunknown tool ⇒ treat as writeRead or draftsearch · open · label list · draftWrites somethingsend · labels · calendar · unknownRuns immediatelyno promptApprove → it runsEdit → runs amendedReject → redraftsA button the user clicked skips this entirely — the click is the decision.
Classification is default-deny: a tool the rules do not recognise is treated as a write and stops for approval, rather than being assumed safe.

The exemption is bounded by an allowlist with no send and no delete in it, by construction rather than convention. Tools are classified by verb, and anything unrecognised is treated as a write.

The other half of trust is being able to answer why did it do that? Every step of a run is a durable checkpoint recording the state and the exact node about to execute — forty of them for a single conversation — so a decision can be reconstructed after the fact rather than guessed at.

What went wrong

All found by using the application rather than reading the code — the argument for driving a thing end to end before believing it works.

Threading was verified against Gmail directly rather than trusted from the agent’s report: after sending, the conversation was queried and both messages confirmed to share one thread identifier.

Where it goes next

The hiring bottleneck in the problem statement is the same architecture pointed at a different queue. Applications arrive in the same mailbox, the attachment tool already reaches the documents, and the pattern that classifies unread mail is the one that would evaluate a CV.

It screens and shortlists; it does not reject. Under the EU AI Act and comparable regimes, software that automatically rejects candidates is a high-risk use with obligations attached — so the human stays in by design, not by retrofit.

Status and provenance

A working prototype, not a delivered engagement. It runs end to end against a real Gmail account — my own — and the behaviour above was verified against live mail: replies confirmed to land in the right conversation, rejections confirmed to send nothing, archiving confirmed against Gmail rather than trusted from the interface. The screenshots use invented correspondents.

It began as a teaching example whose architecture was sound and whose implementation was entirely stubbed — the inbox returned a hardcoded string, sending returned a formatted string, authentication compared a password to a constant. Keeping that architecture while replacing every input and output with a real mailbox is the work described here.

Adapting it to a business would mean its mail conventions, its rules on who may be written to, its retention and residency requirements, and deployment in its own cloud account. The architecture anticipates that: the credential is server-side, the tool surface is scoped at launch, and the approval rules are derived rather than hand-maintained.