---
title: "Drafts"
description: "Create, update, send and delete drafts: unsent messages a person or agent can review before they go out, including sends scheduled for later with send_at."
canonical: "https://machinemail.dev/docs/reference/api/drafts"
---

# Drafts

> Create, update, send and delete drafts: unsent messages a person or agent can review before they go out, including sends scheduled for later with send_at.

Source: https://machinemail.dev/docs/reference/api/drafts

| Method   | Path                                                                                          | Summary                                                               |
| -------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `GET`    | `/v0/drafts`                                                                                  | [List Drafts](#list-drafts)                                           |
| `GET`    | `/v0/drafts/{draft_id}`                                                                       | [Get Draft](#get-draft)                                               |
| `GET`    | `/v0/inboxes/{inbox_id}/drafts`                                                               | [List Drafts](#list-drafts)                                           |
| `POST`   | `/v0/inboxes/{inbox_id}/drafts`                                                               | [Create Draft](#create-draft)                                         |
| `GET`    | `/v0/inboxes/{inbox_id}/drafts/{draft_id}`                                                    | [Get Draft](#get-draft)                                               |
| `PATCH`  | `/v0/inboxes/{inbox_id}/drafts/{draft_id}`                                                    | [Update Draft](#update-draft)                                         |
| `DELETE` | `/v0/inboxes/{inbox_id}/drafts/{draft_id}`                                                    | [Delete Draft](#delete-draft)                                         |
| `GET`    | `/v0/inboxes/{inbox_id}/drafts/{draft_id}/attachments/{attachment_id}/download`               | [Download Draft Attachment](#download-draft-attachment)               |
| `POST`   | `/v0/inboxes/{inbox_id}/drafts/{draft_id}/send`                                               | [Send Draft](#send-draft)                                             |
| `GET`    | `/v0/pods/{pod_id}/drafts`                                                                    | [List Drafts in Pod](#list-drafts-in-pod)                             |
| `GET`    | `/v0/pods/{pod_id}/drafts/{draft_id}`                                                         | [Get Draft in Pod](#get-draft-in-pod)                                 |
| `GET`    | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts`                                                 | [List Drafts in Pod](#list-drafts-in-pod)                             |
| `POST`   | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts`                                                 | [Create Draft in Pod](#create-draft-in-pod)                           |
| `GET`    | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}`                                      | [Get Draft in Pod](#get-draft-in-pod)                                 |
| `PATCH`  | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}`                                      | [Update Draft in Pod](#update-draft-in-pod)                           |
| `DELETE` | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}`                                      | [Delete Draft in Pod](#delete-draft-in-pod)                           |
| `GET`    | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}/attachments/{attachment_id}/download` | [Download Draft Attachment in Pod](#download-draft-attachment-in-pod) |
| `POST`   | `/v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}/send`                                 | [Send Draft in Pod](#send-draft-in-pod)                               |

## List Drafts

```http
GET /v0/drafts
```

Drafts of every inbox in scope, newest first. The `labels` filter is not supported.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `before` | `string (date-time)` |  | Only drafts created before this time. |
| `after` | `string (date-time)` |  | Only drafts created after this time. |
| `ascending` | `boolean` |  | Sort oldest first. |

#### Response

**200** A page of drafts (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `drafts` | `DraftItem[]` | required | Ordered by created_at, newest first unless ascending is set. |

Each item of `drafts` is a [DraftItem](https://machinemail.dev/docs/reference/api/objects#draftitem):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation. |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references. |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/drafts" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "drafts": [
    {
      "inbox_id": "agent@machinemail.dev",
      "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
      "labels": ["UNREAD"],
      "to": ["customer@example.com"],
      "cc": ["team@example.com"],
      "bcc": ["audit@example.com"],
      "subject": "Order #1042",
      "preview": "Thanks for the quick reply.",
      "attachments": [
        {
          "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
          "filename": "invoice.pdf",
          "size": 2048,
          "content_type": "application/pdf",
          "content_disposition": "string",
          "content_id": "string"
        }
      ],
      "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
      "send_status": "scheduled",
      "send_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "reply_to": ["agent@machinemail.dev"],
      "text": "Thanks for the quick reply.",
      "html": "<p>Thanks for the quick reply.</p>",
      "references": ["string"],
      "created_at": "2026-10-01T12:00:00.000Z",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
    }
  ]
}
```

## Get Draft

```http
GET /v0/drafts/{draft_id}
```

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `draft_id` | `string` | required | ID of the draft. |

#### Response

**200** The draft (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## List Drafts

```http
GET /v0/inboxes/{inbox_id}/drafts
```

Drafts of the inbox, newest first. The `labels` filter is not supported.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `before` | `string (date-time)` |  | Only drafts created before this time. |
| `after` | `string (date-time)` |  | Only drafts created after this time. |
| `ascending` | `boolean` |  | Sort oldest first. |

#### Response

**200** A page of drafts (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `drafts` | `DraftItem[]` | required | Ordered by created_at, newest first unless ascending is set. |

Each item of `drafts` is a [DraftItem](https://machinemail.dev/docs/reference/api/objects#draftitem):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation. |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references. |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "drafts": [
    {
      "inbox_id": "agent@machinemail.dev",
      "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
      "labels": ["UNREAD"],
      "to": ["customer@example.com"],
      "cc": ["team@example.com"],
      "bcc": ["audit@example.com"],
      "subject": "Order #1042",
      "preview": "Thanks for the quick reply.",
      "attachments": [
        {
          "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
          "filename": "invoice.pdf",
          "size": 2048,
          "content_type": "application/pdf",
          "content_disposition": "string",
          "content_id": "string"
        }
      ],
      "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
      "send_status": "scheduled",
      "send_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "reply_to": ["agent@machinemail.dev"],
      "text": "Thanks for the quick reply.",
      "html": "<p>Thanks for the quick reply.</p>",
      "references": ["string"],
      "created_at": "2026-10-01T12:00:00.000Z",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
    }
  ]
}
```

## Create Draft

```http
POST /v0/inboxes/{inbox_id}/drafts
```

With `send_at` the draft is scheduled: a background worker sends it at that time (`send_status: scheduled`). A scheduled send that fails sets `send_status: failed` and is not retried, except when the email provider could not take mail for now (throttled, sending paused, daily quota used up, or unavailable): the draft stays scheduled and a later pass sends it.

Reply drafts: set `in_reply_to` to a message ID in this inbox; recipients are not filled in for you. Forward drafts (`forward_of`) and `reply_all` are not supported.

A message can have at most 50 recipients: `to`, `cc` and `bcc` together, each address counted once. An unclaimed sandbox inbox can have at most 5. More is a `400` `validation` error with `param: "to"`. The cap is checked when the draft is created or updated, and again when it is sent: a scheduled draft over the cap at send time gets `send_status: failed`.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_create`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |

#### Request body

Content type: `application/json`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `labels` | `string[]` |  | Labels of the draft. |
| `reply_to` | `string \| string[]` |  | Reply-to addresses: one address or a list. |
| `to` | `string \| string[]` |  | Recipient addresses: one address or a list. |
| `cc` | `string \| string[]` |  | CC addresses: one address or a list. |
| `bcc` | `string \| string[]` |  | BCC addresses: one address or a list. |
| `subject` | `string` |  | Subject. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `attachments` | `SendAttachment[]` |  | Attachments, sent with the draft. Each needs content or url; a url is fetched when the draft is sent. |
| `in_reply_to` | `string` |  | ID of a message in this inbox to reply to: the sent message gets its In-Reply-To and References headers. |
| `send_at` | `string (date-time) \| null` |  | Schedule the draft: it is sent automatically at this time (send_status: scheduled). null or absent: not scheduled. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that draft with 200, or 409 when that draft is outside the pod or inbox of the API key or route. |

Each item of `attachments` is a [SendAttachment](https://machinemail.dev/docs/reference/api/objects#sendattachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `filename` | `string` |  | Filename of the attachment. Defaults to the last segment of url. |
| `content_type` | `string` |  | Content type of the attachment. Defaults to the Content-Type of the url response. |
| `content_disposition` | `"inline" \| "attachment"` |  | Content disposition of the attachment. |
| `content_id` | `string` |  | Content ID, for inline images referenced as cid: in the HTML body. |
| `content` | `string` |  | Base64 encoded content of the attachment. |
| `url` | `string` |  | URL the API downloads the attachment from: public http(s) only, no redirects, at most 10 MB, 20 second timeout. |

#### Response

**200** A draft with this client\_id already exists; it is returned unchanged. (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

**201** Created (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `402`  | Free plan over its storage (1 GB: messages, drafts and held mail): a draft cannot grow past it (limit\_key: "storage\_bytes"). Delete mail or drafts to make room. Paid plans are never refused.                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `409`  | Conflicts with an existing resource.                                                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |
| `503`  | The draft sets or moves send\_at and the scheduler is unavailable for a moment: nothing was saved. Retry with backoff.                                                                                                                 |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "customer@example.com",
  "subject": "Order #1042",
  "text": "Thanks for the quick reply."
}'
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## Get Draft

```http
GET /v0/inboxes/{inbox_id}/drafts/{draft_id}
```

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Response

**200** The draft (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## Update Draft

```http
PATCH /v0/inboxes/{inbox_id}/drafts/{draft_id}
```

Fields you omit are unchanged; `null` clears a field. `in_reply_to` cannot be changed.

A message can have at most 50 recipients: `to`, `cc` and `bcc` together, each address counted once. An unclaimed sandbox inbox can have at most 5. More is a `400` `validation` error with `param: "to"`.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_update`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Request body

Content type: `application/json`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `reply_to` | `string \| string[] \| null` |  | Reply-to addresses. null clears them. |
| `to` | `string \| string[] \| null` |  | Recipient addresses. null clears them. |
| `cc` | `string \| string[] \| null` |  | CC addresses. null clears them. |
| `bcc` | `string \| string[] \| null` |  | BCC addresses. null clears them. |
| `subject` | `string \| null` |  | Subject. null clears it. |
| `text` | `string \| null` |  | Plain text body. null clears it. |
| `html` | `string \| null` |  | HTML body. null clears it. |
| `add_attachments` | `SendAttachment[]` |  | Attachments to add. Each needs content or url. |
| `remove_attachments` | `string[]` |  | IDs of attachments to remove. Unknown IDs are ignored. |
| `add_labels` | `string[]` |  | Labels to add. |
| `remove_labels` | `string[]` |  | Labels to remove (applied after add_labels). |
| `send_at` | `string (date-time) \| null` |  | Schedule (or reschedule) the draft. null unschedules it. |

Each item of `add_attachments` is a [SendAttachment](https://machinemail.dev/docs/reference/api/objects#sendattachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `filename` | `string` |  | Filename of the attachment. Defaults to the last segment of url. |
| `content_type` | `string` |  | Content type of the attachment. Defaults to the Content-Type of the url response. |
| `content_disposition` | `"inline" \| "attachment"` |  | Content disposition of the attachment. |
| `content_id` | `string` |  | Content ID, for inline images referenced as cid: in the HTML body. |
| `content` | `string` |  | Base64 encoded content of the attachment. |
| `url` | `string` |  | URL the API downloads the attachment from: public http(s) only, no redirects, at most 10 MB, 20 second timeout. |

#### Response

**200** The updated draft (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `402`  | Free plan over its storage (1 GB: messages, drafts and held mail): a draft cannot grow past it (limit\_key: "storage\_bytes"). Delete mail or drafts to make room. Paid plans are never refused.                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |
| `503`  | The draft sets or moves send\_at and the scheduler is unavailable for a moment: nothing was saved. Retry with backoff.                                                                                                                 |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X PATCH "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "customer@example.com",
  "subject": "Order #1042",
  "text": "Thanks for the quick reply.",
  "add_labels": [
    "string"
  ]
}'
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## Delete Draft

```http
DELETE /v0/inboxes/{inbox_id}/drafts/{draft_id}
```

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_delete`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Response

**200** Deleted (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X DELETE "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "ok": true
}
```

## Download Draft Attachment

```http
GET /v0/inboxes/{inbox_id}/drafts/{draft_id}/attachments/{attachment_id}/download
```

The bytes of a base64 attachment on a draft, served like a message attachment download (`nosniff`, RFC 6266 `Content-Disposition`, `disposition=inline` previews for images, PDF and plain text only). `url` attachments are fetched when the draft is sent, so they return `404` until then.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |
| `attachment_id` | `string` | required | ID of the attachment. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `disposition` | `"attachment" \| "inline"` |  | inline previews raster images, PDF and plain text in the browser; every other type downloads regardless. Default: `"attachment"`. |

#### Response

**200** The attachment bytes (`application/octet-stream`)

Headers:

| Field | Type | | Description |
| --- | --- | --- | --- |
| `Content-Disposition` | `string` |  | attachment; filename="<ascii name>"; filename*=UTF-8''<encoded name> (or inline; … for a preview). |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j/attachments/att_01k6x3h7qv0000m7v3xb9f2p4r/download" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

## Send Draft

```http
POST /v0/inboxes/{inbox_id}/drafts/{draft_id}/send
```

Sends the draft now and deletes it; a `draft.sent` and a `message.sent` event are emitted. The sent message gets the draft labels plus `add_labels`, minus `remove_labels`.

Suppressed recipients and recipients on a send block list return `403`, a paused inbox `403`. A draft already being sent returns `409`.

A message can have at most 50 recipients: `to`, `cc` and `bcc` together, each address counted once. An unclaimed sandbox inbox can have at most 5. More is a `400` `validation` error with `param: "to"`.

An unclaimed sandbox agent can send at most 10 messages to at most 10 different recipients per rolling 24 hours: `429` with `Retry-After`.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_send`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Request body

Content type: `application/json`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `add_labels` | `string \| string[]` |  | Label or labels to add. |
| `remove_labels` | `string \| string[]` |  | Label or labels to remove. |

#### Response

**201** Sent (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `message_id` | `string` | required | ID of the message. |
| `thread_id` | `string` | required | ID of the thread. |
| `timestamp` | `string (date-time)` | required | When the message was sent. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `402`  | Free plan over its messages cap: 3,000 a month or 100 a day, inbound messages and outbound recipients together (each recipient counts once). error.limit\_key is messages\_per\_month or messages\_per\_day; resets\_at and Retry-After say when the allowance resets (absent when this message has more recipients than the cap itself). Paid plans are never refused.                                                                                                                                                              |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register).                                                                                                                                                                                                                                                                                               |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `409`  | Conflicts with an existing resource.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `429`  | Unclaimed sandbox agent over a send cap: at most 10 messages to at most 10 different recipients per rolling 24 hours. Retry-After says when the next send fits. Claiming the inbox lifts the caps. Also returned when a Free organization goes over its daily number of different recipients.                                                                                                                                                                                                                                        |
| `500`  | Unexpected server error.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `501`  | Not implemented.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `502`  | The email provider refused or failed the send (error.type is upstream\_error). Nothing was sent and no message was stored. error.message says why: a rejected message or recipient is final, and so is a used-up sending quota until it resets; a throttled or unavailable provider can be retried later. A scheduled draft whose send fails this way is marked send\_status: failed, unless the provider could not take mail for now (throttled, sending paused, quota used up, or unavailable): it stays scheduled and is retried. |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j/send" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "add_labels": "string"
}'
```

```json title="Response 201"
{
  "message_id": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "thread_id": "thd_01k6x3a1fz0000c9k5ns1x4e6g",
  "timestamp": "2026-10-01T12:00:00.000Z"
}
```

## List Drafts in Pod

```http
GET /v0/pods/{pod_id}/drafts
```

Same as List Drafts (`GET /v0/drafts`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `before` | `string (date-time)` |  | Only drafts created before this time. |
| `after` | `string (date-time)` |  | Only drafts created after this time. |
| `ascending` | `boolean` |  | Sort oldest first. |

#### Response

**200** A page of drafts (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `drafts` | `DraftItem[]` | required | Ordered by created_at, newest first unless ascending is set. |

Each item of `drafts` is a [DraftItem](https://machinemail.dev/docs/reference/api/objects#draftitem):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation. |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references. |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/drafts" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "drafts": [
    {
      "inbox_id": "agent@machinemail.dev",
      "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
      "labels": ["UNREAD"],
      "to": ["customer@example.com"],
      "cc": ["team@example.com"],
      "bcc": ["audit@example.com"],
      "subject": "Order #1042",
      "preview": "Thanks for the quick reply.",
      "attachments": [
        {
          "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
          "filename": "invoice.pdf",
          "size": 2048,
          "content_type": "application/pdf",
          "content_disposition": "string",
          "content_id": "string"
        }
      ],
      "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
      "send_status": "scheduled",
      "send_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "reply_to": ["agent@machinemail.dev"],
      "text": "Thanks for the quick reply.",
      "html": "<p>Thanks for the quick reply.</p>",
      "references": ["string"],
      "created_at": "2026-10-01T12:00:00.000Z",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
    }
  ]
}
```

## Get Draft in Pod

```http
GET /v0/pods/{pod_id}/drafts/{draft_id}
```

Same as Get Draft (`GET /v0/drafts/{draft_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `draft_id` | `string` | required | ID of the draft. |

#### Response

**200** The draft (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## List Drafts in Pod

```http
GET /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts
```

Same as List Drafts (`GET /v0/inboxes/{inbox_id}/drafts`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `limit` | `integer` |  | Maximum number of items to return. Default 50, maximum 500; larger values are capped. |
| `page_token` | `string` |  | next_page_token from the previous page. |
| `before` | `string (date-time)` |  | Only drafts created before this time. |
| `after` | `string (date-time)` |  | Only drafts created after this time. |
| `ascending` | `boolean` |  | Sort oldest first. |

#### Response

**200** A page of drafts (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `count` | `integer` | required | Number of items returned. |
| `limit` | `integer` |  | Maximum number of items per page. Default 50, maximum 500; larger values are capped. |
| `next_page_token` | `string` |  | Page token for pagination. Absent on the last page. |
| `drafts` | `DraftItem[]` | required | Ordered by created_at, newest first unless ascending is set. |

Each item of `drafts` is a [DraftItem](https://machinemail.dev/docs/reference/api/objects#draftitem):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation. |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references. |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "drafts": [
    {
      "inbox_id": "agent@machinemail.dev",
      "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
      "labels": ["UNREAD"],
      "to": ["customer@example.com"],
      "cc": ["team@example.com"],
      "bcc": ["audit@example.com"],
      "subject": "Order #1042",
      "preview": "Thanks for the quick reply.",
      "attachments": [
        {
          "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
          "filename": "invoice.pdf",
          "size": 2048,
          "content_type": "application/pdf",
          "content_disposition": "string",
          "content_id": "string"
        }
      ],
      "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
      "send_status": "scheduled",
      "send_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "reply_to": ["agent@machinemail.dev"],
      "text": "Thanks for the quick reply.",
      "html": "<p>Thanks for the quick reply.</p>",
      "references": ["string"],
      "created_at": "2026-10-01T12:00:00.000Z",
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
    }
  ]
}
```

## Create Draft in Pod

```http
POST /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts
```

Same as Create Draft (`POST /v0/inboxes/{inbox_id}/drafts`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_create`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |

#### Request body

Content type: `application/json`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `labels` | `string[]` |  | Labels of the draft. |
| `reply_to` | `string \| string[]` |  | Reply-to addresses: one address or a list. |
| `to` | `string \| string[]` |  | Recipient addresses: one address or a list. |
| `cc` | `string \| string[]` |  | CC addresses: one address or a list. |
| `bcc` | `string \| string[]` |  | BCC addresses: one address or a list. |
| `subject` | `string` |  | Subject. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `attachments` | `SendAttachment[]` |  | Attachments, sent with the draft. Each needs content or url; a url is fetched when the draft is sent. |
| `in_reply_to` | `string` |  | ID of a message in this inbox to reply to: the sent message gets its In-Reply-To and References headers. |
| `send_at` | `string (date-time) \| null` |  | Schedule the draft: it is sent automatically at this time (send_status: scheduled). null or absent: not scheduled. |
| `client_id` | `string` |  | Idempotency key: a create with a client_id already used in the organization returns that draft with 200, or 409 when that draft is outside the pod or inbox of the API key or route. |

Each item of `attachments` is a [SendAttachment](https://machinemail.dev/docs/reference/api/objects#sendattachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `filename` | `string` |  | Filename of the attachment. Defaults to the last segment of url. |
| `content_type` | `string` |  | Content type of the attachment. Defaults to the Content-Type of the url response. |
| `content_disposition` | `"inline" \| "attachment"` |  | Content disposition of the attachment. |
| `content_id` | `string` |  | Content ID, for inline images referenced as cid: in the HTML body. |
| `content` | `string` |  | Base64 encoded content of the attachment. |
| `url` | `string` |  | URL the API downloads the attachment from: public http(s) only, no redirects, at most 10 MB, 20 second timeout. |

#### Response

**200** A draft with this client\_id already exists; it is returned unchanged. (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

**201** Created (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `402`  | Free plan over its storage (1 GB: messages, drafts and held mail): a draft cannot grow past it (limit\_key: "storage\_bytes"). Delete mail or drafts to make room. Paid plans are never refused.                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `409`  | Conflicts with an existing resource.                                                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |
| `503`  | The draft sets or moves send\_at and the scheduler is unavailable for a moment: nothing was saved. Retry with backoff.                                                                                                                 |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "customer@example.com",
  "subject": "Order #1042",
  "text": "Thanks for the quick reply."
}'
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## Get Draft in Pod

```http
GET /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}
```

Same as Get Draft (`GET /v0/inboxes/{inbox_id}/drafts/{draft_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Response

**200** The draft (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## Update Draft in Pod

```http
PATCH /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}
```

Same as Update Draft (`PATCH /v0/inboxes/{inbox_id}/drafts/{draft_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_update`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Request body

Content type: `application/json`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `reply_to` | `string \| string[] \| null` |  | Reply-to addresses. null clears them. |
| `to` | `string \| string[] \| null` |  | Recipient addresses. null clears them. |
| `cc` | `string \| string[] \| null` |  | CC addresses. null clears them. |
| `bcc` | `string \| string[] \| null` |  | BCC addresses. null clears them. |
| `subject` | `string \| null` |  | Subject. null clears it. |
| `text` | `string \| null` |  | Plain text body. null clears it. |
| `html` | `string \| null` |  | HTML body. null clears it. |
| `add_attachments` | `SendAttachment[]` |  | Attachments to add. Each needs content or url. |
| `remove_attachments` | `string[]` |  | IDs of attachments to remove. Unknown IDs are ignored. |
| `add_labels` | `string[]` |  | Labels to add. |
| `remove_labels` | `string[]` |  | Labels to remove (applied after add_labels). |
| `send_at` | `string (date-time) \| null` |  | Schedule (or reschedule) the draft. null unschedules it. |

Each item of `add_attachments` is a [SendAttachment](https://machinemail.dev/docs/reference/api/objects#sendattachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `filename` | `string` |  | Filename of the attachment. Defaults to the last segment of url. |
| `content_type` | `string` |  | Content type of the attachment. Defaults to the Content-Type of the url response. |
| `content_disposition` | `"inline" \| "attachment"` |  | Content disposition of the attachment. |
| `content_id` | `string` |  | Content ID, for inline images referenced as cid: in the HTML body. |
| `content` | `string` |  | Base64 encoded content of the attachment. |
| `url` | `string` |  | URL the API downloads the attachment from: public http(s) only, no redirects, at most 10 MB, 20 second timeout. |

#### Response

**200** The updated draft (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `inbox_id` | `string` | required | ID of the inbox. |
| `draft_id` | `string` | required | ID of the draft. |
| `labels` | `string[]` | required | Labels of the draft. The sent message gets them. |
| `to` | `string[]` |  | Recipient addresses. |
| `cc` | `string[]` |  | CC addresses. |
| `bcc` | `string[]` |  | BCC addresses. |
| `subject` | `string` |  | Subject of the draft. |
| `preview` | `string` |  | Text preview of the draft. |
| `attachments` | `Attachment[]` |  | Attachments of the draft. size is 0 for attachments given by url until the draft is sent (the URL is fetched then). |
| `in_reply_to` | `string` |  | ID of the message being replied to. |
| `send_status` | `"scheduled" \| "sending" \| "failed"` |  | scheduled while waiting for send_at, sending while a sender holds it, failed when a scheduled send failed (it is not retried). Absent on drafts without send_at. |
| `send_at` | `string (date-time)` |  | Time at which the draft is sent automatically. |
| `updated_at` | `string (date-time)` | required | Time at which the draft was last updated. |
| `client_id` | `string` |  | Client ID given at creation (idempotency key). |
| `reply_to` | `string[]` |  | Reply-to addresses. |
| `text` | `string` |  | Plain text body. |
| `html` | `string` |  | HTML body. |
| `references` | `string[]` |  | Message IDs the draft references (set from in_reply_to). |
| `created_at` | `string (date-time)` | required | Time at which the draft was created. |
| `pod_id` | `string` | required | ID of the pod. |

Each item of `attachments` is a [Attachment](https://machinemail.dev/docs/reference/api/objects#attachment):

| Field | Type | | Description |
| --- | --- | --- | --- |
| `attachment_id` | `string` | required | ID of the attachment. |
| `filename` | `string` |  | Filename of the attachment. |
| `size` | `integer` | required | Size of the attachment in bytes. |
| `content_type` | `string` |  | Content type of the attachment. |
| `content_disposition` | `string` |  | Content disposition of the attachment (inline or attachment). |
| `content_id` | `string` |  | Content ID of the attachment. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `402`  | Free plan over its storage (1 GB: messages, drafts and held mail): a draft cannot grow past it (limit\_key: "storage\_bytes"). Delete mail or drafts to make room. Paid plans are never refused.                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |
| `503`  | The draft sets or moves send\_at and the scheduler is unavailable for a moment: nothing was saved. Retry with backoff.                                                                                                                 |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X PATCH "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "customer@example.com",
  "subject": "Order #1042",
  "text": "Thanks for the quick reply.",
  "add_labels": [
    "string"
  ]
}'
```

```json title="Response 200"
{
  "inbox_id": "agent@machinemail.dev",
  "draft_id": "drf_01k6x3b7hn0000e1n7qv3z6g8j",
  "labels": ["UNREAD"],
  "to": ["customer@example.com"],
  "cc": ["team@example.com"],
  "bcc": ["audit@example.com"],
  "subject": "Order #1042",
  "preview": "Thanks for the quick reply.",
  "attachments": [
    {
      "attachment_id": "att_01k6x3h7qv0000m7v3xb9f2p4r",
      "filename": "invoice.pdf",
      "size": 2048,
      "content_type": "application/pdf",
      "content_disposition": "string",
      "content_id": "string"
    }
  ],
  "in_reply_to": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "send_status": "scheduled",
  "send_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "reply_to": ["agent@machinemail.dev"],
  "text": "Thanks for the quick reply.",
  "html": "<p>Thanks for the quick reply.</p>",
  "references": ["string"],
  "created_at": "2026-10-01T12:00:00.000Z",
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f"
}
```

## Delete Draft in Pod

```http
DELETE /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}
```

Same as Delete Draft (`DELETE /v0/inboxes/{inbox_id}/drafts/{draft_id}`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_delete`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Response

**200** Deleted (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `ok` | `true` | required |  |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X DELETE "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response 200"
{
  "ok": true
}
```

## Download Draft Attachment in Pod

```http
GET /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}/attachments/{attachment_id}/download
```

Same as Download Draft Attachment (`GET /v0/inboxes/{inbox_id}/drafts/{draft_id}/attachments/{attachment_id}/download`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_read`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |
| `attachment_id` | `string` | required | ID of the attachment. |

#### Query parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `disposition` | `"attachment" \| "inline"` |  | inline previews raster images, PDF and plain text in the browser; every other type downloads regardless. Default: `"attachment"`. |

#### Response

**200** The attachment bytes (`application/octet-stream`)

Headers:

| Field | Type | | Description |
| --- | --- | --- | --- |
| `Content-Disposition` | `string` |  | attachment; filename="<ascii name>"; filename*=UTF-8''<encoded name> (or inline; … for a preview). |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                             |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                       |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register). |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                               |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j/attachments/att_01k6x3h7qv0000m7v3xb9f2p4r/download" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

## Send Draft in Pod

```http
POST /v0/pods/{pod_id}/inboxes/{inbox_id}/drafts/{draft_id}/send
```

Same as Send Draft (`POST /v0/inboxes/{inbox_id}/drafts/{draft_id}/send`), limited to the pod.

**Authentication:** `Authorization: Bearer <api key>` or the dashboard `mm_session` cookie. Permission: `draft_send`.

#### Path parameters

| Field | Type | | Description |
| --- | --- | --- | --- |
| `pod_id` | `string` | required | ID of the pod. |
| `inbox_id` | `string` | required | ID of the inbox: its email address, as in AgentMail (inbox_id == email). |
| `draft_id` | `string` | required | ID of the draft. |

#### Request body

Content type: `application/json`.

| Field | Type | | Description |
| --- | --- | --- | --- |
| `add_labels` | `string \| string[]` |  | Label or labels to add. |
| `remove_labels` | `string \| string[]` |  | Label or labels to remove. |

#### Response

**201** Sent (`application/json`)

| Field | Type | | Description |
| --- | --- | --- | --- |
| `message_id` | `string` | required | ID of the message. |
| `thread_id` | `string` | required | ID of the thread. |
| `timestamp` | `string (date-time)` | required | When the message was sent. |

#### Errors

| Status | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | Invalid request: error.type is validation and error.param names the field.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `401`  | Missing, invalid, expired or revoked credential.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `402`  | Free plan over its messages cap: 3,000 a month or 100 a day, inbound messages and outbound recipients together (each recipient counts once). error.limit\_key is messages\_per\_month or messages\_per\_day; resets\_at and Retry-After say when the allowance resets (absent when this message has more recipients than the cap itself). Paid plans are never refused.                                                                                                                                                              |
| `403`  | The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account\_suspended: error.message says what still works; do not retry or re-register).                                                                                                                                                                                                                                                                                               |
| `404`  | Not found. Also returned for resources outside the credential scope.                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `409`  | Conflicts with an existing resource.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `429`  | Unclaimed sandbox agent over a send cap: at most 10 messages to at most 10 different recipients per rolling 24 hours. Retry-After says when the next send fits. Claiming the inbox lifts the caps. Also returned when a Free organization goes over its daily number of different recipients.                                                                                                                                                                                                                                        |
| `500`  | Unexpected server error.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `501`  | Not implemented.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `502`  | The email provider refused or failed the send (error.type is upstream\_error). Nothing was sent and no message was stored. error.message says why: a rejected message or recipient is final, and so is a used-up sending quota until it resets; a throttled or unavailable provider can be retried later. A scheduled draft whose send fails this way is marked send\_status: failed, unless the provider could not take mail for now (throttled, sending paused, quota used up, or unavailable): it stays scheduled and is retried. |

Error bodies use the [error envelope](https://machinemail.dev/docs/reference/errors).

#### Example

```bash title="Request"
curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/inboxes/agent@machinemail.dev/drafts/drf_01k6x3b7hn0000e1n7qv3z6g8j/send" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "add_labels": "string"
}'
```

```json title="Response 201"
{
  "message_id": "msg_01k6x3a1g00000d0m6pt2y5f7h",
  "thread_id": "thd_01k6x3a1fz0000c9k5ns1x4e6g",
  "timestamp": "2026-10-01T12:00:00.000Z"
}
```
