Docs · Guides
Drafts and scheduled send
Let an agent write email that a person approves before it goes out, and schedule sends for later with send_at.
A draft is an email an agent (or you) wrote that hasn’t been sent. It waits in the inbox’s Drafts tab in the dashboard until someone edits it, approves it, schedules it or cancels it. Use drafts for a human in the loop: the agent can write, but only a person, or a supervising agent with messages:send, can send.
curl -s -X POST https://api.agentboxd.com/v1/inboxes/$INBOX_ID/drafts \
-H "Authorization: Bearer $AGENTBOXD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reply_to_message_id":"'$MESSAGE_ID'","text":"Hi Dana, the duplicate charge is refunded."}'
# approve it
curl -s -X POST https://api.agentboxd.com/v1/inboxes/$INBOX_ID/drafts/$DRAFT_ID/send \
-H "Authorization: Bearer $AGENTBOXD_API_KEY"#Statuses
| Status | Means | You can |
|---|---|---|
draft | Waiting for review. | Edit, send, schedule, cancel, delete. |
scheduled | Will be sent at send_at. | Edit, send now, reschedule, unschedule (send_at: null), cancel, delete. |
sending | Being handed to the outbound queue right now. | Wait a moment. |
sent | Queued as a message: sent_message_id points at it. The draft’s text and files are cleared; the message keeps them. | Delete. |
failed | A scheduled send was refused; error says why. | Send again, schedule again, cancel, delete. |
cancelled | Will never be sent. | Delete. |
#Replies
Create a draft with reply_to_message_id (or thread_id, which replies to the thread’s latest message) and it is filled in like a reply: the recipients (reply_all like the reply call), the Re: subject, and In-Reply-To and References when it is sent. Anything you pass explicitly wins. A draft may also start incomplete; send checks that it has a recipient and some text (400 draft_incomplete).
Reply drafts from the model can go straight into a draft: POST /v1/messages/:id/draft-reply with { "save": true }. See Reply drafts.
#Approving a draft
POST /v1/inboxes/:id/drafts/:draftId/send sends it through exactly the same path as a normal send: allow/block lists, suppressions, the plan’s monthly emails, the burst limit and the daily caps are all checked, and it honours Idempotency-Key. It answers 202 { draft, message }. If a check refuses it, you get that error and the draft stays as it was, with the reason in error.
#Scheduled send
Set send_at (ISO 8601, at least 1 minute and at most 30 days ahead) when you create or update a draft, or call POST /v1/inboxes/:id/drafts/:draftId/schedule { send_at }. The checks run again when it is sent, so a send that became impossible meanwhile (quota used up, recipient blocked) ends failed with a draft.failed webhook. A send that hits the burst limit is retried a few minutes later. POST …/cancel stops it.
Plain sends can be scheduled too: send_at on POST /v1/inboxes/:id/messages/send or …/reply creates a scheduled draft and returns it (202) instead of a message.
curl -s -X POST https://api.agentboxd.com/v1/inboxes/$INBOX_ID/messages/send \
-H "Authorization: Bearer $AGENTBOXD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"dana@example.com","subject":"Your weekly report","text":"…","send_at":"2026-10-01T08:00:00Z"}'#Webhooks and permissions
Drafts emit draft.created, draft.updated, draft.sent, draft.failed and draft.cancelled (Webhooks). Reading drafts needs drafts:read; creating, editing, cancelling and deleting need drafts:write; sending or scheduling one also needs messages:send. An inbox-scoped key sees only its inbox’s drafts.
MCP clients have create_draft, list_drafts, get_draft, send_draft, schedule_draft and cancel_draft (MCP server).