cherami.
API referenceSending and allowance

Send a message

POST /v1/inboxes/{inbox_id}/sent

Read as Markdown ↗

POST /v1/inboxes/{inbox_id}/sent

Requires a Claim-issued API key: Authorization: Bearer YOUR_CREDENTIAL.

Follow permitted sending.

Inspect the inbox’s sending rules before preparing a message. All send, reply, reply-all and forward paths enforce every To/Cc/Bcc destination. A new blocked attempt returns 403 recipient_not_allowed without submission or quota use. An existing keyed attempt remains replayable after a policy edit, without resubmission.

For saved preparation and later submission, use the draft API. Draft sending uses the outcomes below, but the draft ID itself prevents another submission without expiry.

Request fields

Recipient inputs are named mailbox objects, never bare strings or assembled header syntax. Names are display metadata, not identity verification; addresses determine delivery.

The owned inbox supplies From and its configured sender name. Bcc addresses and names remain in the sender’s private saved copy but are not exposed in delivered recipient headers.

At most 50 combined To/Cc/Bcc entries are accepted. Unknown top-level and attachment fields are rejected. You cannot override From or supply arbitrary headers, remote attachment URLs, inline attachments, or raw MIME.

JSON is limited to 8 MiB. The total email must fit the provider's 5 MiB limit, including generated MIME and attachments. Local size checks do not guarantee that generated MIME will fit.

Cherami appends “Sent via Cherami” to plain text and supplied HTML, after your body including quoted history. Do not add it yourself. Returned sent bodies include the attribution.

Reply targets

in_reply_to is a resource ID, not an RFC Message-ID or thread ID. Received parents must be ready; sent parents must be accepted. Both need a usable Message-ID and must belong to the sending inbox. Cherami sets In-Reply-To and accumulated References, shortening long ancestry as needed.

On this explicit-send endpoint, supply recipients and subject yourself. For derived recipients and subject, use reply or reply-all. There is no implicit latest-message selection. Unready, unaccepted, or headerless targets return 409; missing, deleted, other-account, or other-inbox targets return 404.

Response and outcomes

A created sent resource returns 201 and Location: /v1/sent/{id}.

Inspect message.status, not just HTTP status: accepted means provider acceptance, not delivery; rejected means explicit pre-acceptance rejection; unknown means acceptance could not be confirmed. Accepted and unknown attempts retain their sending charge. A retry recovers the reserved attempt without resubmitting it.

provider_message_id, error_code, thread_id, and in_reply_to can be null. in_reply_to identifies the Cherami parent resource when available.

When outcome_persisted is false, the response reports a known provider outcome that could not be saved. Later reads may still say unknown; do not resend because of that mismatch. There is no automatic provider-submission retry or delivery/bounce tracking.

If a response is lost or an infrastructure error reports uncertainty, follow the same-key recovery contract below. Without a key, inspect sent messages before considering another send. Repeating an unkeyed POST can send a duplicate. An absent match in a short list alone is not proof that retrying is safe.

Provider errors such as E_RECIPIENT_SUPPRESSED, E_RATE_LIMIT_EXCEEDED, or E_DAILY_LIMIT_EXCEEDED are reported as rejected sent outcomes, not necessarily HTTP errors. A suppressed recipient rejects the whole submission.

Retry a send with an idempotency key

Keys are scoped to the authenticated account across HTTP and MCP, not to a connection or inbox. Protection lasts 24 hours from the first reserved attempt, without renewal on retries. Keyed results include replayed and idempotency_expires_at (ISO timestamp). A new attempt returns 201 with replayed: false; a matching retry returns 200 with replayed: true, the original message.id and its current saved outcome. Both include Location. A replay adds no submission or quota charge.

Use the same key, inbox and message fields for a retry. JSON property order does not matter; omitted and empty optional recipient/attachment arrays are equivalent. Recipient addresses, names and ordering, attachment ordering, body text, HTML, subject and reply target do matter. Address spelling is retained; changing its case changes the request fingerprint. Display names are trimmed, with omitted and blank names equivalent. Changes to the inbox’s configured sender name do not change a replay: the original submission retains the identity used at that time. Initial labels also matter, but their order and duplicates do not; omitted and empty label arrays are equivalent. Later label edits do not change the original retry input, and a replay does not reapply initial labels. Reusing an active key for different input returns 409 idempotency_conflict without sending. Deleting the sent copy does not free its active key: a matching retry returns 409 idempotency_result_unavailable. Ordinary access and inbox-ownership checks still apply.

The first request may still be running when a retry returns unknown. It may also have stopped before submitting or lost the provider's response. Cherami never resumes that reserved attempt on a retry. This prevents a second application submission but may leave an email unsent; it does not guarantee delivery or resolve uncertainty. Use GET /v1/sent/{message_id} to inspect it later. outcome_persisted: true on a replay describes the saved state being returned, not proof that it captures the provider's final outcome.

Validation, authorization and quota failures before reservation do not consume the key. An uncertain infrastructure failure may have reserved it, so reuse the original key and unchanged payload to recover. Never generate a replacement key to bypass uncertainty. After expiry, the same key can create a new send: do not retry an uncertain email after the window. If the initial response was lost, measure the window conservatively from your first request time. A deliberately new email needs a new key.

Requests without a key retain their existing behavior: every POST can create a separate send.

Parameters

ParameterLocationRequiredTypeMeaning
inbox_idpathYesstringOwned Cherami resource ID returned by the API.

Request body

JSON object. Total UTF-8 request body limit: 8388608 bytes.

Schema: SendInput

At most 50 combined To/Cc/Bcc entries. Local body/encoded-attachment sum is bounded to 5 MiB; generated MIME must also fit the provider's 5 MiB limit. No From override, arbitrary headers or inline attachment inputs.

FieldRequiredTypeMeaning and constraints
toYesarray of MailboxmaxItems: 50 minItems: 1
ccNoarray of MailboxmaxItems: 50
bccNoarray of MailboxmaxItems: 50
subjectYesstringNo control characters; at most 998 UTF-8 bytes. Must not be blank. x-max-utf8-bytes: 998
textYesstringRequired nonblank plain text.
htmlNostringHTML alternative. Untrusted content, not sanitized markup.
attachmentsNoarray of AttachmentInput or nullOmitted or null means no attachments; an empty array also clears draft attachments.
in_reply_toNostringReceived or accepted sent resource ID in this inbox. The source must have usable reply headers. pattern: ^[a-f0-9-]{36}$
labelsNoarray of LabelTrimmed, deduplicated and case-sensitive. Order is not significant. maxItems: 32
idempotency_keyNostringRetain 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.

{
  "to": [
    {
      "address": "recipient@example.com",
      "name": "Alex"
    }
  ],
  "subject": "Notes",
  "text": "Here are the notes.",
  "idempotency_key": "RETAIN_A_UNIQUE_SEND_KEY"
}
curl --silent --show-error --include --request POST \
  "https://cherami.to/v1/inboxes/INBOX_ID/sent" \
  --header "Authorization: Bearer $CHERAMI_API_KEY" \
  --header 'Content-Type: application/json' \
  --data-binary @request.json

Responses

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

SendReceipt

Part 2

FieldRequiredTypeMeaning and constraints
replayedYestrue
idempotency_expires_atYesstringUTC 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$
{
  "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,
  "idempotency_expires_at": "2026-10-02T00:00:00.000Z"
}

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

SendReceipt

Part 2

FieldRequiredTypeMeaning and constraints
replayedNofalse

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.

  • X-Request-ID: Support correlation ID, not an idempotency key.

Content type: application/json.

Error

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.

Error

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.

Error

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.

Error

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.

  • X-Request-ID: Support correlation ID, not an idempotency key.

Content type: application/json.

Error

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.

Error

HTTP 415

unsupported_media_type: Send Content-Type: application/json.

  • X-Request-ID: Support correlation ID, not an idempotency key.

Content type: application/json.

Error

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.

QuotaError

HTTP 503

content_unavailable: Expected stored content is unavailable. Retry the read later.

outbound_unavailable: Outbound operation failed and a send's outcome may be unknown. Recover using the original key and unchanged payload within its window, or inspect sent messages.

  • X-Request-ID: Support correlation ID, not an idempotency key.

Content type: application/json.

Error

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.

FieldRequiredTypeMeaning and constraints
limitedYesfalse
messageYesSentMetadata
outcome_persistedYesboolean
replayedNoboolean
idempotency_expires_atNostringUTC 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

FieldRequiredTypeMeaning and constraints
idYesstringCherami resource ID, distinct from the RFC Message-ID. Use the returned value.
inbox_idYesstringCherami resource ID, distinct from the RFC Message-ID. Use the returned value.
created_atYesstringUTC 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_countYesintegerminimum: 0
statusYes"accepted" or "rejected" or "unknown"
provider_message_idYesstring or null
error_codeYesstring or null
thread_idYesstring or null
in_reply_toYesstring or null
labelsYesarray of string

Schema: Error

FieldRequiredTypeMeaning and constraints
errorYesobject

error fields

FieldRequiredTypeMeaning and constraints
codeYesstringProgrammatic error code. Handle unrecognized codes by status and operation-specific recovery.
messageYesstringHuman-readable context, not a stable string to match.

Schema: QuotaError

FieldRequiredTypeMeaning and constraints
errorYesobject
quotaYesQuota
requested_recipientsYesintegerminimum: 0
reasonYes"temporary_exhaustion" or "message_exceeds_allowance"
sufficient_capacity_atYesstring or null
guidanceYesstring

error fields

FieldRequiredTypeMeaning and constraints
codeYes"outbound_limit_reached"
messageYesstring

Schema: Quota

FieldRequiredTypeMeaning and constraints
allowanceYesintegerminimum: 0
usedYesintegerminimum: 0
remainingYesintegerminimum: 0
next_capacity_atYesstring or null
next_capacity_amountYesintegerminimum: 0
window_hoursYes24
unitYes"recipient_deliveries"
increase_requestYesstring
policy_urlYesstring

Schema: Mailbox

FieldRequiredTypeMeaning and constraints
addressYesstringBare ASCII address, at most 254 characters, local part at most 64. No display-name header syntax. maxLength: 254
nameNostringUnicode name, trimmed; blank means unnamed. No control characters. At most 256 UTF-8 bytes after trimming. x-max-utf8-bytes: 256

Unknown fields are rejected.

Schema: AttachmentInput

FieldRequiredTypeMeaning and constraints
filenameYesstringNonempty, no control characters, slash or backslash. At most 255 UTF-8 bytes. minLength: 1 x-max-utf8-bytes: 255
typeYesstringMIME type without parameters. maxLength: 127 pattern: ^[A-Za-z0-9!#$&^_.+-]+/[A-Za-z0-9!#$&^_.+-]+$
contentYesstringPadded base64 original bytes, no whitespace; encoded length must be a multiple of four. Empty files are accepted. contentEncoding: base64 pattern: ^[A-Za-z0-9+/]*={0,2}$

Unknown fields are rejected.

Schema: Label

1–128 UTF-8 bytes after trimming. Well-formed Unicode without control characters; case-sensitive.

string. x-max-utf8-bytes: 128

HTTP conventions, errors and pagination · Download OpenAPI 3.1

On this page