Drafts API

Save an email as a draft, change it as often as you like, then send it once through the same policy as every other send.

View as MarkdownLast updated

A draft is an email that hasn't gone out yet. Nothing is checked or sent while you save it. The owner's policy applies once, when you send it. Every draft path needs the Send (messages:send) scope, including reading drafts.

Create a draft

http
POST /v1/drafts

Every field except agent_id is optional, so a draft can be incomplete.

FieldRequiredDescription
agent_idYesThe token's agent ID
fromNoA sending address of this agent, lowercase and without a +tag
toNoUp to 50 recipient addresses
subjectNoUp to 998 characters
text · htmlNoThe body. Together up to 10 MiB.
in_reply_toNoThe ID of a received email, to make this a reply draft
attachmentsNoUp to 10 uploaded attachment IDs
bash
curl -sS https://api.atmark.ai/v1/drafts \
  -H "Authorization: Bearer $ATMARK_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "7c21…f5a1",
    "from": "scout@atmark.ai",
    "to": ["partner@example.com"],
    "subject": "Weekly report",
    "text": "Draft. Numbers to follow."
  }'
Example 201 response
{
  "id": "5d1e…0b7c",
  "agent_id": "7c21…f5a1",
  "revision": 1,
  "status": "active",
  "from": "scout@atmark.ai",
  "to": ["partner@example.com"],
  "subject": "Weekly report",
  "in_reply_to": null,
  "attachments": [],
  "size_bytes": 54,
  "created_at": "2026-10-08T09:00:00.000Z",
  "updated_at": "2026-10-08T09:00:00.000Z",
  "discarded_at": null,
  "sent_at": null,
  "message_id": null,
  "text": "Draft. Numbers to follow.",
  "html": null
}
  • A reply draft (in_reply_to) must point to a received email this agent can see. Quarantined or unknown mail gives 404 message_not_found. When it's sent, empty fields default the way a reply does: to the sender, with Re: …, from the address that received it.
  • An agent can have up to 500 open drafts. Past that you get 429 draft_limit.

List and read

http
GET /v1/drafts?agent_id={agentId}
GET /v1/drafts/{draftId}

The list is newest change first, without bodies. Filter with status: active (default), sent, discarded, or all. It takes limit (1–100) and cursor. Reading one draft returns its text and html too.

statusMeaning
activeCan still change and be sent
sendingA send has started. It can't change.
sentSent. message_id is the outbound email.
discardedDiscarded. Kept for the record.

Change a draft

http
PATCH /v1/drafts/{draftId}

Send only the fields you want to change. A missing field stays as it was; null clears it. Each change raises revision by one.

To avoid overwriting someone else's change, send the revision you last read. If the draft changed since then, you get 409 draft_revision_conflict; read it again and retry. A draft that's being sent, sent, or discarded can't change.

Discard

http
POST /v1/drafts/{draftId}/discard

Nothing is deleted. The draft stays with status: "discarded" and can't be changed or sent. Discarding it again returns the same result.

Send

http
POST /v1/drafts/{draftId}/send
Idempotency-Key: 2026-10-08-weekly-report
json
{ "agent_id": "7c21…f5a1" }

The draft goes out as saved. To change it, PATCH first; the send body only takes agent_id and an optional trace_id.

  • It passes the same gate as POST /v1/messages: the policy, limits, risk checks, and the owner's send approval. The results are the same (sent, pending_approval, 403 policy_denied), with draft_id and draft_status added.
  • Idempotency-Key is required. Retrying with the same key returns the first result. A draft is sent at most once: once the gate has decided (sent, denied, awaiting approval, or failed), the draft is closed, and a different key gets 409 draft_already_sent with the message_id.
  • If the send stops before the gate, such as for a missing field or an attachment error, the draft stays active. Fix it and send again.
  • A missing from, recipient, or subject gives 400 draft_incomplete. detail says which.

Errors

StatuserrorMeaning
400draft_incompleteThe draft is missing something it needs to be sent.
400path_body_mismatchagent_id in the body isn't the token's agent.
404draft_not_foundNo such draft for this agent.
409draft_revision_conflictThe draft changed since the revision you sent. Read it again.
409draft_send_in_progressIt's being sent with another key. Retry with that key to get the result.
409draft_already_sentIt was already sent. message_id is in the body.
409draft_discardedIt was discarded.
413draft_too_largetext and html together are over 10 MiB.
429draft_limitThe agent has 500 open drafts. Send or discard some.

Feedback on this page? Write to support@atmark.ai.