---
title: "Update a draft"
method: PATCH
path: "/v1/drafts/{id}"
tags: ["Drafts"]
---

# Update a draft

`PATCH /v1/drafts/{id}`

## Request

- Base URL: `https://api.mails.ai`
- URL: `https://api.mails.ai/v1/drafts/{id}`
- Auth: HTTP bearer

**Requires scope:** `manage` or `draft`. Edits a draft or scheduled draft. Setting a future `send_at` schedules it; clearing it returns to `draft`. A draft that starts sending while it is being changed answers 409 `duplicate_resource`. Without the `send` scope a key can neither schedule a draft nor change a scheduled one, except to unschedule it with `send_at: null` (403 `insufficient_scope`). A draft that failed only because its agent was paused or archived when it came due can be changed too: a new `send_at` schedules it again once a paused agent is resumed (422 `agent_paused` until then; 404 `agent_archived` for an archived agent, as archiving cannot be undone), and any other change makes it a draft again. A draft cannot carry `attachments` (422 `invalid_field`, param `attachments`). A test key cannot change a draft a live key made (403 `live_mode_required`).

## Path parameters

- `id` string, required

## Request body

- UpdateDraftRequest
  - `to` union: Replaces the recipients: one address, or a list of up to 50. Left out, they stay as they are.
    - string, email
    - string[]
  - `cc` string[]: Replaces the Cc list, up to 50 addresses; `[]` empties it. Left out, it stays as it is.
  - `bcc` string[]: Replaces the Bcc list, up to 50 addresses; `[]` empties it. Left out, it stays as it is.
  - `subject` string: Replaces the subject, 1 to 998 characters. Left out, it stays as it is.
  - `body_text` string: Replaces the plain-text body, up to 1,000,000 characters. Left out, it stays as it is.
  - `body_html` string: Replaces the HTML body, up to 1,000,000 characters. Left out, it stays as it is.
  - `send_at` string, date-time, nullable: A future time (ISO 8601, UTC, ending in `Z`) schedules the draft, and `null` unschedules it back to `draft`; left out, the schedule stays as it is. Setting a time needs the `send` scope, and a time not in the future is a 400.
  - `list_unsubscribe` boolean: Opt in to RFC 8058 one-click unsubscribe: the message carries `List-Unsubscribe` and `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers, so the recipient's mail app shows an Unsubscribe button. One click suppresses the address for your whole workspace: every later send to it from any of your agents is refused with `recipient_suppressed` until you allowlist it (`POST /v1/suppression/allow`). Turn it on for newsletters, digests and other recurring mail. Leave it off (the default) for password resets, sign-in links, receipts and anything else the recipient must keep receiving. Needs exactly one `to` recipient and no `cc` or `bcc`.

## Response `200`

The updated draft.

- Draft
  - `id` string, required: Draft id (`dft_…`).
  - `agent_id` string, required: Id (`agt_…`) of the agent the draft is sent from, chosen when it was created.
  - `to` string[]: The draft's recipients, always as a list, even when one address was given.
  - `cc` string[]: The draft's Cc addresses. Absent when `cc` was never given; `[]` when it was given empty or a PATCH emptied it.
  - `bcc` string[]: The draft's Bcc addresses, who receive it but appear in none of its headers. Absent when `bcc` was never given; `[]` when it was given empty or a PATCH emptied it.
  - `subject` string: The draft's subject. Absent when none was given; the draft is then sent with an empty subject.
  - `body_text` string: The draft's plain-text body. Absent when none was given.
  - `body_html` string: The draft's HTML body. Absent when none was given.
  - `reply_to` string, email: The address replies should go to, as given on create; absent when none was.
  - `in_reply_to_message_id` string: Id of the message this draft answers (`msg_…` or `rcv_…`), as given on create; absent when none was.
  - `thread_id` string: The thread the draft's message joined (`thrd_…`), written when the draft is sent: the thread of the message named by `sent_message_id`, also when the send could not be fully recorded after that message was stored (see `error_message`). Absent until it is sent, when a send could not be recorded far enough to store its message (no `sent_message_id`), once that thread is deleted, and on drafts sent before this field was filled in.
  - `tags` Tag[]: Name-value labels saved with the draft and copied onto the message when it is sent. Absent when none were given.
    - `name` string, required: The tag's name, as given on the send, reply or draft.
    - `value` string, required: The tag's value, as given; it may be an empty string.
  - `send_at` string, date-time: The time the draft is scheduled for (UTC); it stays after the draft is sent. Absent when it was never scheduled or was unscheduled with `send_at: null`.
  - `list_unsubscribe` boolean: Whether the draft is sent with RFC 8058 one-click unsubscribe headers (`list_unsubscribe` on create or update; off by default). One click suppresses the recipient for the whole workspace.
  - `status` 'draft' | 'scheduled' | 'sending' | 'sent' | 'failed' | 'canceled', required: `draft`: saved, not scheduled. `scheduled`: waiting for `send_at`. `sending`: being sent now. `sent`: see `sent_message_id`. `failed`: the send failed, see `error_message`. `canceled`: a scheduled reply whose conversation was deleted before it went out. Only `draft` and `scheduled` drafts can be edited or sent.
  - `sent_message_id` string: Id (`msg_…`) of the message the draft became when it was sent. Absent on a draft not yet sent; a sent draft whose send could not be fully recorded (see `error_message`) can lack it too.
  - `error_message` string: Why the draft is `failed` or `canceled`. On a `sent` draft it reports a send that went out but could not be fully recorded. Absent otherwise.
  - `created_at` string, date-time, required: When the draft was created (UTC).
  - `updated_at` string, date-time, required: When the draft last changed: an edit, a new schedule, or a status change as it is sent.

## Other responses

- `400`: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
- `401`: Missing or invalid API key.
- `403`: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
- `404`: Resource not found in this workspace, or the agent a send names is archived (`agent_archived`).
- `409`: Conflict — duplicate resource, an in-progress idempotent request, a draft or scheduled message that started sending while it was being changed, or a thread deleted for good while one of its messages is being sent (`duplicate_resource`); or an agent name more than one agent has (`agent_name_conflict`: use the agent's id); or a status change on an archived agent, since archiving cannot be undone (`agent_archived`).
- `422`: Unprocessable — agent paused, recipient suppressed, a recipient at test.mails.ai other than reply@test.mails.ai (`unknown_test_address`), or abuse guard.
- `500`: Internal server error.

---

[API](https://skmtc.dev/mails/apis/mails-ai-api.md) · [All operations](https://skmtc.dev/mails/apis/mails-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mails/apis/mails-ai-api/revisions/4d4930a8c33b?raw)
