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.

View as Markdown

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.

POST /v1/inboxes/:id/drafts
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

StatusMeansYou can
draftWaiting for review.Edit, send, schedule, cancel, delete.
scheduledApproved: will be sent at send_at.Send now, reschedule, unschedule (send_at: null), cancel, delete. Changing what it sends puts it back to draft, to approve again.
sendingBeing handed to the outbound queue right now.Wait a moment.
sentApproved and handed to the outbound queue as a message: sent_message_id points at it, and the message’s status says where it is now. The draft’s text and files are cleared; the message keeps them.Delete.
failedA scheduled send was refused; error says why.Send again, schedule again, cancel, delete.
cancelledWill 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

In the dashboard, Approvals lists the drafts of every inbox that wait for a person (GET /v1/drafts?status=draft,failed), with a count in the sidebar. Each one opens as a card: the message it answers, quoted, then the reply, then Approve & send, Edit and Reject (cancel). Cc, Bcc, labels and sending later are under More options. To get an email for each new draft, turn on the draft_created trigger in Human on call.

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.

#Who approved it

Every draft that goes out records who approved it: approved_by ({ type, id, label }), approved_at, and approved_content_sha256, a hash of exactly what it approved. It shows in the dashboard, in GET /v1/drafts and in the draft.sent event. The message it becomes keeps the same record in approval ({ draft_id, by, at, content_sha256 }), which stays after the draft is deleted.

`type`Who`label`
userA person in the dashboard.Their email.
api_keyAn API key.The key’s name.
connectorThe hosted MCP connector, for example Claude.The client’s name.
policyAn approval rule in our code, not a person or a key. Only the Inbox Heist vault uses one today.The policy’s name, for example Inbox Heist vault policy.
  • Sending a draft records who sent it. Scheduling one records who scheduled it.
  • An approval covers exactly the content it saw. Changing what a scheduled draft sends (recipients, subject, body or files) puts it back to draft, so it needs a new approval. Labels and metadata don’t count as changes.
  • Unscheduling clears the approval.
  • If a send is refused (a limit, a list, the quota), the draft keeps the approval it had before.
  • A scheduled draft goes out only while whoever approved it still can: the person is still in the workspace, the API key isn’t revoked or expired, the connection hasn’t ended. Otherwise it fails with approver_gone and a draft.failed event, and someone has to send or schedule it again. A key whose creator left the workspace still counts. While the inbox is paused, the workspace is stopped or sending is paused, the draft waits as usual, and this is checked after the resume.
  • The label is a copy, so the record stays after the key or the account is deleted.

To approve only the version you reviewed, pass the draft’s content_sha256 (a hash of what it sends now) as expected_content_sha256 to send or schedule. If the draft changed since, for example because an agent edited it, you get 409 draft_changed and nothing happens. Read the draft again and review the new version. The dashboard always does this.

Approval is not delivery. After it, the message’s status tells you where it is:

StageWhere to see it
ApprovedThe draft’s approved_at, and the message’s approval.at.
QueuedMessage status: queued: waiting in our outbound queue (an emergency stop or a paused inbox holds it here), or the receiving server asked us to try again later: we retry for about 48 hours (how).
Left our serverMessage status: sent, and the message.sent event.
DeliveredMessage status: delivered, and the message.delivered event: every recipient is an Agentboxd inbox and got it, or the recipient’s server confirmed it where the transport reports it. With one recipient elsewhere it stays sent. Nothing tells us more (for example whether it reached the inbox or the spam folder).

#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, a file no longer stored, whoever approved it no longer able to) 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. Draft limits apply: text and html are at most 1,000,000 characters each.

schedule a send
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"}'

#Meeting drafts

The meeting calls (answer an invite, send, change or cancel a meeting) take draft: true: then they send nothing and save the message as a draft, with the calendar file it will carry. Approve it like any draft. Its calendar says what sending it does: { action: "respond" | "create" | "update" | "cancel", method, event_id, removed_attendees } (for a change, the attendees taken off, who get a cancellation with it); other drafts have calendar: null. The meeting changes only when the draft is sent, in the same step that stores the message: a new meeting exists from then on, under event_id.

  • The approval covers the calendar file too (content_sha256). You can edit the subject and the text; the recipients come from the meeting, so changing to, cc, bcc, type or data is 422 calendar_draft_fixed. To change the meeting, cancel the draft and call the meeting API again.
  • If the meeting changed after the draft was made (the organizer sent a new version, or another change went out first), the send is refused with 409 calendar_event_changed: nothing goes out and the draft keeps the error. Make a new one.
  • Making one needs calendar:write and drafts:write; sending it needs messages:send, as for every draft.

#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).