Send a draft
POST /v1/drafts/{draft_id}/send
Read as Markdown ↗POST /v1/drafts/{draft_id}/send
Requires a Claim-issued API key: Authorization: Bearer YOUR_CREDENTIAL.
POST /v1/drafts/{draft_id}/send with {} or {"idempotency_key":"YOUR_SEND_KEY"}.
Sending takes the current saved content, not a previously retrieved copy. The draft freezes as submitted when an outgoing attempt is reserved, with sent_message_id identifying that attempt. An edit that wins before reservation must be included or cause 409 draft_busy; a send never silently submits an older saved copy. Concurrent sends cannot reserve multiple submissions for the same draft.
Validation, ownership, permission, recipient-policy or quota failures before reservation leave it editable. After reservation it remains submitted for every provider outcome, including rejection and uncertainty. There is no automatic retry or return-to-draft operation. A deliberately new attempt requires a new draft; do not create one merely to resolve an unknown outcome.
The response uses the ordinary send receipt and outcomes: 201 for a new attempt, 200 with replayed: true when recovering. Location points to /v1/sent/{sent_message_id}. Acceptance is not proof of delivery. If outcome_persisted is false, retain the stronger immediate outcome even if later reads lag.
The draft ID itself prevents another submission, without expiry. Repeat the same send request to recover an uncertain attempt, not to restart it. This can preserve a possibly unsent attempt rather than risk a duplicate. Deleting the sent copy does not unlock the draft; recovery then returns 409 draft_result_unavailable (or idempotency_result_unavailable for an active sending key).
Optional sending keys share the ordinary account-scoped sending namespace and 24-hour lifetime. Their intent identifies this draft, not its mutable fields. Changing the draft ID or reusing a key from an ordinary send conflicts. Key expiry does not remove the draft's permanent submitted state. A replay recovered through the draft association need not include idempotency_expires_at; it does not allocate or renew a key.
Parameters
| Parameter | Location | Required | Type | Meaning |
|---|---|---|---|---|
draft_id | path | Yes | string | Owned Cherami resource ID returned by the API. |
Request body
JSON object. Total UTF-8 request body limit: 4096 bytes.
Schema: SendDraft
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
idempotency_key | No | string | Retain a unique key, exact payload and first request time for this intended operation. Account-scoped protection lasts 24 hours without renewal. pattern: ^[A-Za-z0-9_-]{1,128}$ |
Unknown fields are rejected.
curl example
Replace resource-ID placeholders with returned IDs. Supply CHERAMI_API_KEY through your private shell environment.
Save the intended payload as a private request.json file and replace illustrative values. For an uncertain response, follow the operation-specific recovery rules.
{}curl --silent --show-error --include --request POST \
"https://cherami.to/v1/drafts/DRAFT_ID/send" \
--header "Authorization: Bearer $CHERAMI_API_KEY" \
--header 'Content-Type: application/json' \
--data-binary @request.jsonResponses
HTTP 200
Matching replay: current resource or saved attempt, without another allocation or provider submission.
X-Request-ID: Support correlation ID, not an idempotency key.Location: Relative URL of the resulting resource.
Content type: application/json.
All of these schemas apply.
Part 1
Part 2
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
replayed | Yes | true |
{
"limited": false,
"message": {
"id": "33333333-3333-4333-8333-333333333333",
"inbox_id": "11111111-1111-4111-8111-111111111111",
"created_at": "2026-10-01T00:00:00.000Z",
"recipient_count": 1,
"status": "unknown",
"provider_message_id": null,
"error_code": null,
"thread_id": "44444444-4444-4444-8444-444444444444",
"in_reply_to": null,
"labels": []
},
"outcome_persisted": true,
"replayed": true
}HTTP 201
Successful operation; inspect resource state and outcome fields.
X-Request-ID: Support correlation ID, not an idempotency key.Location: Relative URL of the resulting resource.
Content type: application/json.
All of these schemas apply.
Part 1
Part 2
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
replayed | No | false |
HTTP 400
invalid_json: Send a valid UTF-8 JSON object, not an array or scalar.
invalid_message: Correct the send fields, recipients, reply ID, or attachments using the message's guidance.
invalid_name: Correct the inbox, sender or recipient display name using the returned guidance.
invalid_labels: Correct label names, changes, filter groups, or discovery prefix.
invalid_idempotency_key: Use 1–128 ASCII letters, digits, hyphens or underscores.
invalid_draft: Use supported draft fields, source preparation or listing parameters. Draft cursors that are malformed or do not match the inbox/state also use this code.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
HTTP 401
unauthorized: Provide a valid bearer credential. Use human-approved recovery if access is lost.
X-Request-ID: Support correlation ID, not an idempotency key.WWW-Authenticate:"Bearer"
Content type: application/json.
HTTP 403
operation_not_allowed: This account cannot send mail, delete mail, or delete inboxes. Contact support if unexpected.
recipient_not_allowed: The inbox’s sending rules block one or more recipients. Nothing was submitted or charged. Use allowed recipients or ask the human to review Sending rules; do not bypass them through another inbox.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
HTTP 404
not_found: Resource is absent or inaccessible to this account. Reply targets must be in the sending inbox.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
HTTP 409
reply_not_ready: A received reply or forward source must be ready; a sent source must have confirmed provider acceptance.
reply_headers_unavailable: The target has no usable RFC Message-ID. Send a new message without in_reply_to.
idempotency_conflict: The key belongs to different input. Recover with the original inbox and payload, not a replacement key.
idempotency_result_unavailable: The key was used but its inbox, draft or sent copy is unavailable. No replacement was created or submitted; do not bypass protection with a new key.
draft_busy: An edit/send raced other changes. Retrieve current content before trying again.
draft_result_unavailable: The draft was already submitted but its sent copy is unavailable. Nothing was resubmitted.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
HTTP 413
body_too_large: Reduce the JSON request to the endpoint's body limit.
message_too_large: Reduce message content and attachments. Passing local checks does not guarantee generated MIME fits the provider limit.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
HTTP 415
unsupported_media_type: Send Content-Type: application/json.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
HTTP 429
outbound_limit_reached: Read quota, reason and sufficient_capacity_at. Waiting cannot fix message_exceeds_allowance; see sending recovery.
X-Request-ID: Support correlation ID, not an idempotency key.Retry-After: Delay in seconds when supplied. Quota uses sufficient_capacity_at; absent when waiting cannot make the message fit.
Content type: application/json.
HTTP 503
content_unavailable: Expected stored content is unavailable. Retry the read later.
draft_unavailable: Draft operation is uncertain. Follow draft-specific recovery; do not blindly create a replacement.
X-Request-ID: Support correlation ID, not an idempotency key.
Content type: application/json.
Schema: SendReceipt
Inspect message.status even on HTTP 201. accepted is provider acceptance, not delivery. Preserve a known outcome when outcome_persisted is false; later reads may lag. Keyed receipts add replayed and expiry. Draft-association recovery adds replayed without requiring an expiry.
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
limited | Yes | false | |
message | Yes | SentMetadata | |
outcome_persisted | Yes | boolean | |
replayed | No | boolean | |
idempotency_expires_at | No | string | UTC service instant with milliseconds and Z suffix. format: date-time pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ |
Schema: SentMetadata
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
id | Yes | string | Cherami resource ID, distinct from the RFC Message-ID. Use the returned value. |
inbox_id | Yes | string | Cherami resource ID, distinct from the RFC Message-ID. Use the returned value. |
created_at | Yes | string | UTC service instant with milliseconds and Z suffix. format: date-time pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ |
recipient_count | Yes | integer | minimum: 0 |
status | Yes | "accepted" or "rejected" or "unknown" | |
provider_message_id | Yes | string or null | |
error_code | Yes | string or null | |
thread_id | Yes | string or null | |
in_reply_to | Yes | string or null | |
labels | Yes | array of string |
Schema: Error
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
error | Yes | object |
error fields
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
code | Yes | string | Programmatic error code. Handle unrecognized codes by status and operation-specific recovery. |
message | Yes | string | Human-readable context, not a stable string to match. |
Schema: QuotaError
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
error | Yes | object | |
quota | Yes | Quota | |
requested_recipients | Yes | integer | minimum: 0 |
reason | Yes | "temporary_exhaustion" or "message_exceeds_allowance" | |
sufficient_capacity_at | Yes | string or null | |
guidance | Yes | string |
error fields
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
code | Yes | "outbound_limit_reached" | |
message | Yes | string |
Schema: Quota
| Field | Required | Type | Meaning and constraints |
|---|---|---|---|
allowance | Yes | integer | minimum: 0 |
used | Yes | integer | minimum: 0 |
remaining | Yes | integer | minimum: 0 |
next_capacity_at | Yes | string or null | |
next_capacity_amount | Yes | integer | minimum: 0 |
window_hours | Yes | 24 | |
unit | Yes | "recipient_deliveries" | |
increase_request | Yes | string | |
policy_url | Yes | string |
HTTP conventions, errors and pagination · Download OpenAPI 3.1