import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const result = await client.drafts.update(
"dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
{
subject: "Quick follow-up on your order #10473",
},
);
console.log(result);from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
result = client.update_draft(
"dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
subject="Quick follow-up on your order #10473",
)
print(result)curl -X PATCH 'https://api.mails.ai/v1/drafts/dft_01JZX8K3M9Q4P7VN2YB6RTDC0E' \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"subject": "Quick follow-up on your order #10473"
}'mails drafts update dft_01JABC \
--subject "Quick follow-up"{
"name": "mails_drafts_update",
"arguments": {
"draft_id": "dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"subject": "Quick follow-up on your order #10473"
}
}{
"id": "dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"agent_id": "agt_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"to": ["jordan@example.com"],
"cc": ["jordan@example.com"],
"bcc": ["jordan@example.com"],
"subject": "<subject>",
"body_text": "<body_text>",
"body_html": "<body_html>",
"reply_to": "jordan@example.com",
"in_reply_to_message_id": "msg_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"thread_id": "thrd_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"tags": [{ "name": "<name>", "value": "<value>" }],
"send_at": "2026-10-04T12:00:00.000Z",
"list_unsubscribe": false,
"status": "draft",
"sent_message_id": "msg_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"error_message": "<error_message>",
"created_at": "2026-10-04T12:00:00.000Z",
"updated_at": "2026-10-04T12:00:00.000Z"
}200 The updated draft.
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.Update a draft
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const result = await client.drafts.update(
"dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
{
subject: "Quick follow-up on your order #10473",
},
);
console.log(result);from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
result = client.update_draft(
"dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
subject="Quick follow-up on your order #10473",
)
print(result)curl -X PATCH 'https://api.mails.ai/v1/drafts/dft_01JZX8K3M9Q4P7VN2YB6RTDC0E' \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"subject": "Quick follow-up on your order #10473"
}'mails drafts update dft_01JABC \
--subject "Quick follow-up"{
"name": "mails_drafts_update",
"arguments": {
"draft_id": "dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"subject": "Quick follow-up on your order #10473"
}
}{
"id": "dft_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"agent_id": "agt_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"to": ["jordan@example.com"],
"cc": ["jordan@example.com"],
"bcc": ["jordan@example.com"],
"subject": "<subject>",
"body_text": "<body_text>",
"body_html": "<body_html>",
"reply_to": "jordan@example.com",
"in_reply_to_message_id": "msg_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"thread_id": "thrd_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"tags": [{ "name": "<name>", "value": "<value>" }],
"send_at": "2026-10-04T12:00:00.000Z",
"list_unsubscribe": false,
"status": "draft",
"sent_message_id": "msg_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"error_message": "<error_message>",
"created_at": "2026-10-04T12:00:00.000Z",
"updated_at": "2026-10-04T12:00:00.000Z"
}200 The updated draft.
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.Path Parameters
Draft id.
Body Parameters
Replaces the recipients: one address, or a list of up to 50. Left out, they stay as they are.
Replaces the Cc list, up to 50 addresses; [] empties it. Left out, it stays as it is.
Replaces the Bcc list, up to 50 addresses; [] empties it. Left out, it stays as it is.
Replaces the subject, 1 to 998 characters. Left out, it stays as it is.
Replaces the plain-text body, up to 1,000,000 characters. Left out, it stays as it is.
Replaces the HTML body, up to 1,000,000 characters. Left out, it stays as it is.
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.
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.
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).
Response
Draft id (dft_…).
Id (agt_…) of the agent the draft is sent from, chosen when it was created.
The draft's recipients, always as a list, even when one address was given.
The draft's Cc addresses. Absent when cc was never given; [] when it was given empty or a PATCH emptied it.
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.
The draft's subject. Absent when none was given; the draft is then sent with an empty subject.
The draft's plain-text body. Absent when none was given.
The draft's HTML body. Absent when none was given.
The address replies should go to, as given on create; absent when none was.
Id of the message this draft answers (msg_… or rcv_…), as given on create; absent when none was.
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.
Name-value labels saved with the draft and copied onto the message when it is sent. Absent when none were given.
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.
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.
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.
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.
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.
When the draft was created (UTC).
When the draft last changed: an edit, a new schedule, or a status change as it is sent.
Was this page helpful?