BlogAgents

The approval hold: letting an agent write email without letting it send

A key that drafts instead of sends. How the hold works, what the agent sees, why two reviewers cannot release the same message twice, and the schedule trick that used to walk around it.

Daniel Sternlicht3 min read

There are two ways to give an agent email. Trust it completely, or do not give it email at all. The approval hold is the third: the agent writes, a person decides.

What it is

An API key can be created with requires_approval: true. Every send from that key is persisted, stamped with its Message-ID, and held. Nothing goes out until someone releases it.

The agent gets a normal-looking response:

{
  "id": "msg_8a1…",
  "status": "pending_approval",
  "approval_id": "apr_4c…"
}

That is a 202, not an error. There is nothing to retry, and retrying queues a second draft. The tool descriptions in our MCP server and the orientation in llms.txt say this in as many words, because the model reading them is the one that has to get it right: report that the message awaits review, with its id, and stop.

What the reviewer sees

GET /v1/approvals lists what is waiting, oldest first. Each entry carries the full message, to, subject and body, so the reviewer reads what would be sent rather than a reference to it. The queue is a list of things waiting on a human, so it is short by definition.

POST /v1/approvals/{id} takes a decision, approve or reject, a decided_by naming who authorised it, and an optional reason. Approving dispatches the message. Rejecting marks it cancelled. In the dashboard the same queue is a page with two buttons.

An approval expires after 72 hours by default. A draft nobody looked at does not go out days later, out of context, because someone finally clicked.

Once, not twice

Two reviewers see the same draft and both click approve. The decision is a conditional update: it applies only while the approval is still pending and unexpired, and the second caller gets a 409 invalid_state. The message is dispatched once. The approval is marked approved only once the send has been accepted: if the dispatch itself fails, the approval stays pending and can be approved again, and because the release runs under a short lease, two retries cannot both send.

The check runs before persistence, and before the schedule

The hold is one of three controls on the key, alongside a daily send cap and a recipient allowlist. All three are checked per send, before the message row is written. There is no window in which a refused send is half-done.

Order matters more than it looks. The hold has to run before the schedule branch. In an earlier version it did not: a send with scheduled_at was persisted and scheduled before the hold was consulted, which meant a key that required approval could send anything by asking for "in one minute." A hold that a schedule can walk around is not a hold.

Now a held message keeps its schedule, and release honours it: approve a draft due in two days and it goes to the scheduler for two days from now, not out the door at once. The same applies to the daily cap, which counts scheduled and held sends when they are asked for rather than when they leave, so a capped key cannot schedule its way past the cap either.

Why it lives on the key

A workspace quota is the wrong place for any of this. It has no opinion about the first ten thousand sends, only the ten-thousand-and-first. A per-key control has an opinion about the first one.

That is the shape of the problem with agents. A credential handed to software will be used exactly as permitted, at machine speed, until something says no. The workspace quota says no after the damage. The key says no before it.

When to use it

Not everywhere. An agent answering support threads from a support key with an allowlist of your own customers probably does not need a person in the loop for every reply, and a hold there is a queue nobody clears.

Use it where a wrong send is expensive: outreach to strangers, anything to a new domain, anything that commits your company to something, and the first week of any agent you have not watched yet. Then loosen it, key by key, as the agent earns it.

The hold has its own page, human approval for AI agents. The other two controls are described with the exact request and error shapes on the guardrails page, and the docs cover the approvals API and limits for agents.

  • agents
  • guardrails
  • approvals

Daniel Sternlicht

Co-founder, SendRaven

Try it

Give your agent an inbox, an outbox, and a seatbelt.

3,000 emails a month free, and an MCP server your agent can use today.

Start freeRead the docs