Sending and quota
Submission fields, reply targets, sent-message responses, uncertain outcomes, and shared allowances.
Read as Markdown ↗All routes require bearer authentication. Follow permitted sending.
Submit a message
POST /v1/inboxes/{inbox_id}/sent
{
"to": ["recipient@example.com"],
"subject": "Notes",
"text": "Here are the notes.",
"attachments": [{"filename":"notes.txt","type":"text/plain","content":"SGVsbG8="}]
}Request fields
| Field | Required | Contract |
|---|---|---|
to | Yes | Nonempty array of bare ASCII email addresses, not display names. |
subject | Yes | Nonblank string, at most 998 UTF-8 bytes, no control characters. |
text | Yes | Nonblank plain-text string. |
cc, bcc | No | Arrays of bare ASCII email addresses. |
html | No | HTML string. Plain-text-only messages stay plain text. |
attachments | No | Array of at most 32 files with filename, type, and content. |
in_reply_to | No | Cherami received or accepted sent resource ID from this same inbox. |
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.
Attachment filename is a nonempty string of at most 255 UTF-8 bytes, with no control characters, /, or \. type is a MIME type such as text/plain, without parameters, at most 127 characters. content is padded base64 without whitespace.
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.
Supply recipients and subject explicitly. There is no automatic reply-all or 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}:
{
"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": "accepted",
"provider_message_id": "<outgoing-id@example.com>",
"error_code": null,
"thread_id": "44444444-4444-4444-8444-444444444444",
"in_reply_to": null
},
"outcome_persisted": true
}The identifiers are illustrative. Inspect message.status, not just HTTP status:
| Status | Interpretation |
|---|---|
accepted | Provider accepted submission, not proof of inbox delivery. provider_message_id is the outgoing RFC Message-ID. |
rejected | Explicit pre-acceptance rejection. error_code describes the provider failure. Quota is released when this outcome is saved. |
unknown | Acceptance could not be confirmed. The sending charge remains; do not automatically retry. |
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 application retry, idempotency-key support, or delivery/bounce tracking.
If a response is lost or an infrastructure error reports uncertainty, inspect sent messages before considering another send. Repeating 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. Check the actual response rather than inferring success from 201.
List sent messages
GET /v1/inboxes/{inbox_id}/sent?limit=20 returns 200 with {"messages":[...],"next_cursor":null}.
Each entry contains the fields shown inside message above. All statuses are listed, newest-submitted first. Limit is 1–100, default 20. Pass next_cursor as a URL-encoded cursor parameter on the same inbox's sent URL. Lists do not contain bodies or attachments.
Read a sent message
GET /v1/sent/{message_id} returns 200 with those same fields plus submission:
from: the sending inbox address;to,cc, andbcc: recipient arrays.subject,text, and optionalhtml: the submitted subject and attributed bodies.attachments: files withfilename,type, base64content, anddisposition: "attachment".- Optional
headers:In-Reply-ToandReferencesfor a reply.
Missing or other-account IDs return 404; unavailable content can return 503. An available sent copy is not evidence of delivery; inspect its status.
Delete a sent copy
DELETE /v1/sent/{message_id} returns 202 with id and status: "deletion_pending". Permanently deletes the sent copy and its attachments with no trash or undo. Deletion does not refund sending quota. Repeating DELETE is safe and can return 202 or 404.
Read quota
GET /v1/outbound/quota returns 200 with:
| Field | Meaning |
|---|---|
allowance | Account's allowed recipient-deliveries per rolling window; ordinary default 25. |
used | Currently charged recipient-deliveries. |
remaining | Available recipient-deliveries. |
next_capacity_at | ISO expiry time of the oldest charged submission, or null when none. |
window_hours | 24. |
unit | "recipient_deliveries". |
increase_request | Human-readable instructions for requesting an increase. |
Every To/Cc/Bcc entry costs one, including repeats. All inboxes share this allowance. Accepted and unknown submissions count; rejected submissions do not when the outcome is saved. Later bounces and message or inbox deletion do not refund charges.
Insufficient capacity returns 429 with error.code: "outbound_limit_reached" and quota containing the fields above. Retry-After is supplied when existing charges will expire. That first expiry need not free enough capacity for your whole message. Manual increases are available through feedback or email to hello@cherami.to, not guaranteed.