drafts.md

Drafts

Drafts let an agent prepare a message without sending it, for human review or for scheduled sending with send_at.

A draft is an unsent message stored in an inbox. Use drafts when an agent should prepare a reply but a person (or another check) should review it first, or when a message should go out later.

Sending a draft turns it into a normal outbound message, deletes the draft and emits draft.sent.

Creating a draft

curl

curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/drafts \ 
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \ 
  -H "Content-Type: application/json" \ 
  -d '{\n    "to": ["ada@example.com"],\n    "subject": "Your refund",\n    "text": "Hi Ada, the refund is on its way.",\n    "in_reply_to": "msg_01m4fy8han0000y3ge3m73vw3w"\n  }'

TypeScript

const inbox = encodeURIComponent('support-agent@machinemail.dev');
const res = await fetch(`https://api.machinemail.dev/v0/inboxes/${inbox}/drafts`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MACHINEMAIL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: ['ada@example.com'],
    subject: 'Your refund',
    text: 'Hi Ada, the refund is on its way.',
    in_reply_to: 'msg_01m4fy8han0000y3ge3m73vw3w',
  }),
});
const draft = await res.json();

Python

import os, requests

draft = requests.post(
    "https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/drafts",
    headers={"Authorization": f"Bearer {os.environ['MACHINEMAIL_API_KEY']}"},
    json={
        "to": ["ada@example.com"],
        "subject": "Your refund",
        "text": "Hi Ada, the refund is on its way.",
        "in_reply_to": "msg_01m4fy8han0000y3ge3m73vw3w",
    },
).json()
Field Notes
to, cc, bcc Recipients. At most 50 across the three, checked on create and update; addresses themselves are not checked until the draft is sent.
subject, text, html Content.
reply_to Reply-To addresses.
labels Labels for the message once sent.
attachments Each { filename, content_type, content } with base64 content, or { url }, fetched when the draft is sent (its size is 0 until then).
in_reply_to The message_id of a message in this inbox. Sending then sets In-Reply-To and References, so the message lands in that thread.
send_at ISO 8601 time to send automatically.
client_id Idempotency key. The same client_id returns the existing draft with 200.
{
  "draft_id": "drf_01m4fy8hmj00004yb08pa27yt2",
  "inbox_id": "support-agent@machinemail.dev",
  "pod_id": "pod_01m4fy8h150000w4xty1ehfnqa",
  "labels": [],
  "to": ["ada@example.com"],
  "subject": "Your refund",
  "preview": "Hi Ada, the refund is on its way.",
  "text": "Hi Ada, the refund is on its way.",
  "in_reply_to": "msg_01m4fy8han0000y3ge3m73vw3w",
  "references": ["abc123@example.com"],
  "created_at": "2026-10-09T09:00:16.402Z",
  "updated_at": "2026-10-09T09:00:16.402Z"
}

Reviewing, editing and sending

Method Path Permission
GET /v0/inboxes/{inbox_id}/drafts draft_read
GET /v0/drafts draft_read (all inboxes in scope)
GET /v0/inboxes/{inbox_id}/drafts/{draft_id} draft_read
GET /v0/drafts/{draft_id} draft_read
PATCH /v0/inboxes/{inbox_id}/drafts/{draft_id} draft_update
DELETE /v0/inboxes/{inbox_id}/drafts/{draft_id} draft_delete
POST /v0/inboxes/{inbox_id}/drafts/{draft_id}/send draft_send

PATCH accepts to, cc, bcc, subject, text, html, reply_to and send_at, plus add_labels / remove_labels and add_attachments / remove_attachments (attachment IDs). in_reply_to is fixed at creation.

POST .../send sends immediately and returns the same { message_id, thread_id, timestamp } as a normal send. The draft is deleted.

A human-in-the-loop setup gives the agent a key with draft_create but not message_send, message_reply or draft_send, and lets a person review drafts in the dashboard.

Scheduled sending

Set send_at to send the draft automatically. The draft gets send_status: "scheduled", and MachineMail sends it within a few seconds after send_at passes.

{
  "draft_id": "drf_01m4fy8hmj00004yb08pa27yt2",
  "subject": "Later",
  "send_status": "scheduled",
  "send_at": "2026-10-10T09:00:00.000Z"
}
send_status Meaning
absent Not scheduled. Sent only when you call send.
scheduled Waiting for send_at.
sending Being sent now.
failed The send failed, for example because a recipient is suppressed. The draft is kept so you can fix and resend it.

To cancel a schedule, PATCH with "send_at": null. To reschedule, PATCH a new send_at. An edit or a reschedule that lands while the draft is about to go out wins: the draft goes out as edited, or at the new time. A failed draft is not retried automatically.