Hold an agent's email for human approval
Give an agent a key that drafts instead of sending, release its drafts as a person, and keep the conversation going with every reply held the same way.
At the end of this recipe an agent will be writing email that a person releases before it goes out: the first message, and its answer to the reply. The agent never holds a credential that can send on its own, and cannot approve its own drafts.
Before you start
- A verified sending domain with its optional MX record published, as in Send an email from your agent and read the reply.
- Access to the dashboard, where drafts are released.
1. Create a key that holds every send
In the dashboard, open API keys and create a key. Give it the scopes the agent needs, for this recipe emails:send and threads:read, and under Limits for autonomous callers:
- tick Hold every send for human approval;
- optionally set a Daily send limit, which counts held sends when they are drafted;
- optionally set Allowed recipients, such as
@example.org, so a draft to anyone else is refused outright.
Each limit is checked on every send, before anything is stored. The key cannot change its own limits.
2. Connect the agent with that key
Sign-in through OAuth carries none of the key's limits, so the agent connects with the key itself, sent as a header:
For Claude Code: claude mcp add --transport http sendraven https://mcp.sendraven.ai/mcp --header "Authorization: Bearer sk_live_…".
3. The agent drafts
Email me@example.org from hello@mail.example.com: ask whether the September invoice arrived.
send_email answers status: "pending_approval" with an approval_id. That is a success, not an error. The message is stored as a draft (queued in the log) and nothing has been sent. An agent that retries here queues a second draft; the tool description tells it not to.
4. A person releases it
Open Approvals in the dashboard, read the draft and approve or reject it. Approving sends it; rejecting cancels it.
From code, the same decision is POST /v1/approvals/{id}:
$REVIEWER_KEY must be an API key with no guardrails. The key that drafted the message, any key with a limit of its own, and any OAuth token are refused with 403 forbidden, so the agent cannot release its own mail by any route. An approval nobody decides expires after 72 hours and its message is canceled.
5. The reply comes back
Reply from me@example.org. The agent reads it the usual way, list_threads with awaiting_reply: true and then get_thread. Reading needs no approval.
6. The answer is held too
Answer that reply on the same thread: thank them and confirm the invoice is paid.
reply_to_message answers pending_approval just as the first send did, with the thread already attached. Approve it, and it goes out with the threading headers set; the thread's awaiting_reply clears when it is accepted, not when it is drafted. The whole conversation has now run with a person deciding every message that left.
Where to go from here
- Loosen the hold for mail that does not need it by giving that job its own key, with a daily limit and an allowlist but no approval hold, rather than removing the hold from this one.
- A key that holds sends cannot enrol people in automations or send campaigns (
403 forbidden): those send later with nobody to review each message. See Limits for agents. - The full rules for deciding and expiry are in Approvals and the approvals API reference.