Messages
Messages represent individual emails in a mailbox. List and detail endpoints return message metadata; full body content (text/HTML) is fetched from storage on detail requests. All list endpoints use cursor-based pagination.
List messages GET
GET /mailboxes/{email_address}/messagesList messages in a mailbox, newest first, with cursor pagination.
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the mailbox |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | — | Opaque cursor from a previous response's next_cursor |
limit | integer | 50 | Results per page (1–100) |
direction | string | — | Filter by "inbound" or "outbound". Omit for all messages |
start_datetime | string | — | Only messages created on or after this date/time |
end_datetime | string | — | Only messages created on or before this date/time; a bare date includes the whole day |
tz | string | UTC | IANA zone for interpreting zone-less start_datetime/end_datetime values. Defaults to UTC |
start_datetime, end_datetime, and tz filter on created_at. Both bounds are optional and non-breaking — omit them for unchanged behavior. See Filtering by date.
Response (200)
Code examples
Get message GET
GET /mailboxes/{email_address}/messages/{message_id}Get a single message with its full body content (plain text and HTML).
The body returned is the body Inkbox stored, which is not always the body the caller submitted: while the branding footer applies, outbound body_text/body_html come back with the "Sent via Inkbox" footer appended — see Branding footer under Send message.
Fetching marks inbound mail read. A
GEThere with an API key marks an unread inbound message read —is_readcomes backtruein the same response. Human (Console) reads don't, and neither do the list, thread, or attachment routes, so an agent that only reads through those still needs the flags PATCH to mark read. This is distinct fromfirst_opened_at, which reflects the recipient opening a message you sent (see Open tracking).
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the mailbox |
message_id | UUID | Unique identifier of the message |
Response (200)
Code examples
Send message POST
POST /mailboxes/{email_address}/messagesCompose and send an email from a mailbox. Threading is automatically resolved from in_reply_to_message_id.
Storage limits
Every inbox has a storage cap set by your plan. A send that would push the inbox above its cap is refused with a 402 — from this endpoint, reply-all, and forward alike — until space is freed (delete messages or threads) or the plan is upgraded. See pricing for per-plan caps.
Receiving is never blocked. Inbound mail is always accepted, and still counts toward the inbox's usage. Current usage and the cap are on the mailbox object as storage_used_bytes and storage_limit_bytes.
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the sending mailbox |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
recipients.to | string[] | Yes | Primary recipients (at least one) |
recipients.cc | string[] | No | CC recipients |
recipients.bcc | string[] | No | BCC recipients (excluded from headers, delivered separately) |
subject | string | Yes | Email subject line (max 998 chars) |
body_text | string | No | Plain-text body |
body_html | string | No | HTML body |
in_reply_to_message_id | string | No | RFC 5322 Message-ID of the email being replied to |
reply_to | string | No | Optional Reply-To address. If set, recipients' mail clients route replies here instead of the sending mailbox. |
attachments | object[] | No | File attachments (see below). Max total 25 MB. Some file types are rejected; the API returns a 400 with a descriptive error if one is blocked. |
track_opens | boolean | No | Embed an open-tracking pixel in the HTML body. Requires body_html — a plain-text-only send with track_opens returns 422. Default false. See Open tracking. |
AttachmentUpload object:
| Field | Type | Description |
|---|---|---|
filename | string | File name (max 255 chars) |
content_type | string | MIME type (e.g. application/pdf) |
content_base64 | string | Base64-encoded file content |
Request example
Response (201)
Returns the created message metadata (same as a list item — body content is not included).
Branding footer
By default, outbound mail carries a short "Sent via Inkbox" footer, appended to the text and HTML parts of every send, reply-all, and forward. Organizations on a paid plan can remove it — see Branding footer.
While it applies, the footer is written into the stored message, so it changes what the API gives back: a later GET /messages/{message_id} returns body_text and body_html with the footer appended — not the exact bytes you submitted. A send with no body at all comes back with a body containing only the footer. Snippets and search text are computed from the body you submitted, so snippet and search are unaffected.
Open tracking
Set track_opens on a send or forward to embed an invisible tracking pixel in the HTML body. When the recipient's mail client loads it, the message reports two fields (present on every message response):
| Field | Type | Description |
|---|---|---|
first_opened_at | string | null | ISO 8601 time of the first observed open, or null if none seen yet. This is the reliable signal. |
open_count | integer | Observed opens (default 0). Approximate, not an exact tally: image proxies such as Apple Mail Privacy Protection and Gmail preload the pixel and inflate it, while rapid repeat opens are collapsed into one and can deflate it. |
track_opens requires an HTML part — a plain-text-only send returns 422. Tracking is per-message ("at least one observed open"), not per-recipient, and the pixel can raise spam scores, so a borderline message may be more likely to be filtered. first_opened_at (the recipient loaded the pixel) is distinct from is_read (your agent consumed the message via the API — see Get message).
Error responses
| Status | Description |
|---|---|
| 402 | Sending the message would exceed the inbox storage limit — delete messages or upgrade the plan |
| 429 | Mailbox has exceeded 600 sends in the last hour. Response includes Retry-After (seconds), X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers |
Code examples
POST /mailboxes/{email_address}/messages/{message_id}/reply-allReply to every visible participant on a stored message. Recipients are resolved server-side from the original — you don't pass them. The reply stays in the original thread and counts against the same send rate limits as POST /messages.
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the replying mailbox |
message_id | UUID | UUID of the message being replied to |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
subject | string | No | Override; defaults to "Re: " + original.subject (idempotent — won't double-prefix). Max 998 chars. |
body_text | string | No | Plain-text reply body. |
body_html | string | No | HTML reply body. |
attachments | object[] | No | Base64-encoded attachments. Same AttachmentUpload shape and limits as Send. |
reply_to | string | No | Optional Reply-To header on the reply. |
Recipient resolution
- Inbound original —
Tois the originalReply-Toif present, otherwise the original sender.Ccis the originalTo+Cc. - Outbound original —
ToandCcreuse the originalToandCc. - In both cases the replying mailbox, duplicates, and the original BCC recipients are removed.
The message detail (GET .../messages/{message_id}) returns the resolved reply_all_recipients ({ to, cc }) so UIs can prefill editable fields.
Request example
Response (201)
Returns a MessageResponse — the brand-new message representing the reply (same shape as a list item; body content is not included).
Error responses
| Status | Description |
|---|---|
| 400 | Attachment blocked/oversize |
| 402 | Sending the message would exceed the inbox storage limit — delete messages or upgrade the plan |
| 403 | Organization mismatch, contact-rule block, or unclaimed-agent recipient restriction |
| 404 | Mailbox or original message not found |
| 422 | No reply-all recipients could be resolved, or the message was rejected at send time |
| 429 | Send rate limit exceeded (same quota as POST /messages) |
Code examples
Forward message POST
POST /mailboxes/{email_address}/messages/{message_id}/forwardForward a previously stored message out from this mailbox. A forward intentionally creates a new thread — it does not chain into the original conversation, matching the default behavior of Gmail, Outlook, and Apple Mail.
Forwards count against the same send rate limits as POST /messages and fire a message.forwarded webhook event on success (not message.sent).
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the forwarding mailbox |
message_id | UUID | UUID of the original message to forward |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
recipients.to | string[] | — | Primary recipients |
recipients.cc | string[] | — | CC recipients |
recipients.bcc | string[] | — | BCC recipients (excluded from headers, delivered separately) |
mode | string | No | "inline" (default) or "wrapped". See Modes below. |
subject | string | No | Override; defaults to "Fwd: " + original.subject (idempotent — won't double-prefix if the original already starts with Fwd: / Fw:). An empty or missing original subject becomes "Fwd: (no subject)". Max 998 chars. |
body_text | string | No | Plain-text caller note prepended above the original body (inline) or included as a top-level note (wrapped). |
body_html | string | No | HTML caller note. |
additional_attachments | object[] | No | Caller-authored attachments that ride alongside the forwarded content. Same AttachmentUpload shape, rejection behaviour, and 25 MB caller cap as Send. |
include_original_attachments | boolean | No | inline mode only. Default true — original attachments are re-attached as direct outbound parts. Ignored in wrapped mode (originals live inside the wrapped .eml). |
reply_to | string | No | Optional Reply-To header on the forward's outer envelope. The original's Reply-To (if any) is preserved inside the wrapped MIME but is not auto-inherited. |
track_opens | boolean | No | Embed an open-tracking pixel; requires an HTML part on the outgoing forward. Inline mode inherits the original's HTML; wrapped mode needs a body_html. A no-HTML forward returns 422. Default false. See Open tracking. |
At least one address is required across recipients.to, recipients.cc, and recipients.bcc.
Modes
inline(default) — Renders the original body inline below a Gmail-style preamble that includesFrom:,Date:,Subject:,To:, andCc:from the original. Original attachments are re-attached as direct outbound parts. Lossy: inlinecid:images may break, and non-trivial multipart structure is flattened.wrapped— Attaches the original raw MIME as a singlemessage/rfc822part, semantically preserving all headers, body parts, attachments, and nested structure. Recipient clients either expand it inline (Gmail) or show it as aforwarded.emlattachment (Outlook). Use this for originals with inline images, calendar invites, or complex multipart. The result is semantically equivalent to the original, not byte-for-byte equivalent; DKIM-preserving forwarding is not supported in v1.
Forwarding behaviour
- Attachment type rejection applies only to caller-authored attachments, not to content carried over from the original message.
- Original-attachment bytes are not counted against the 25 MB caller cap. Only the 10 MB outbound limit on the final composed MIME applies to the combined output.
- The forward gets a brand-new
Message-ID. The original is referenced via anX-Forwarded-Message-Idheader. - Filename collisions across original attachments and caller
additional_attachmentsare deduplicated automatically (caller wins; originals get aforwarded-prefix and a numeric suffix).
Request example
Response (201)
Returns a MessageResponse — the brand-new message representing the forward (same shape as a list item; body content is not included).
Error responses
| Status | Description |
|---|---|
| 400 | Caller attachment blocked/oversize, or final composed MIME exceeds the 10 MB outbound limit |
| 402 | Sending the message would exceed the inbox storage limit — delete messages or upgrade the plan |
| 403 | Organization mismatch, contact-rule block, or unclaimed-agent recipient restriction |
| 404 | Mailbox or original message not found |
| 422 | Original message cannot be forwarded — raw content was not retained (applies only to historical inbound messages ingested before raw-MIME retention was enabled) |
| 429 | Send rate limit exceeded (same quota as POST /messages) |
| 502 | Failed to load original body or attachments from storage |
Code examples
Update message flags PATCH
PATCH /mailboxes/{email_address}/messages/{message_id}Update is_read or is_starred on a message. Omit a field to leave it unchanged.
This is how to mark mail read in bulk, to mark a message unread, or to mark read when you process from the list or thread routes (which never mark read on their own). Fetching a single inbound message with an API key marks it read automatically — see Get message.
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the mailbox |
message_id | UUID | Unique identifier of the message |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
is_read | boolean | No | Mark as read (true) or unread (false) |
is_starred | boolean | No | Star or unstar the message |
Code examples
Delete message DELETE
DELETE /mailboxes/{email_address}/messages/{message_id}Delete a message. Returns 204 No Content on success.
Deleting a message frees its storage immediately — the message and its associated storage overhead stop counting toward the inbox's storage cap right away.
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the mailbox |
message_id | UUID | Unique identifier of the message |
Code examples
Download attachment GET
GET /mailboxes/{email_address}/messages/{message_id}/attachments/{filename}Download an attachment from a message. By default, responds with a 302 redirect to a temporary download URL. Set redirect=false to get the URL as JSON instead.
Path parameters
| Parameter | Type | Description |
|---|---|---|
email_address | string | Email address of the mailbox |
message_id | UUID | Unique identifier of the message |
filename | string | Attachment filename (must match exactly with attachment_metadata[].filename) |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
redirect | boolean | true | true returns a 302 redirect to the download URL. false returns the URL as JSON. |
Response (302) — redirect mode (default)
Redirects to a temporary download URL with a Content-Disposition: attachment header. The URL is valid for 15 minutes.
Response (200) — JSON mode
| Field | Type | Description |
|---|---|---|
url | string | Temporary download URL (valid for 15 minutes) |
filename | string | Attachment filename |
expires_in | integer | URL expiration time in seconds |
Error responses
| Status | Description |
|---|---|
| 403 | Mailbox does not belong to your organization |
| 404 | Mailbox, message, or attachment not found |
Code examples
Message object
MessageResponse (list items)
| Field | Type | Description |
|---|---|---|
id | UUID | Unique message identifier |
mailbox_id | UUID | Owning mailbox |
thread_id | UUID | null | Conversation thread, if resolved |
message_id | string | RFC 5322 Message-ID header value |
from_address | string | Sender address |
to_addresses | string[] | Primary recipients |
cc_addresses | string[] | null | CC recipients |
subject | string | null | Email subject line |
snippet | string | null | First ~200 chars of the plain-text body |
direction | string | inbound or outbound |
status | string | queued, sent, delivered, bounced, failed, or received |
is_read | boolean | Whether the message has been read |
is_starred | boolean | Whether the message is starred |
has_attachments | boolean | Whether the message has attachments |
reply_to | string | null | Reply-To header (inbound: parsed from the original; outbound: caller-supplied override) |
created_at | string | Creation timestamp (ISO 8601) |
MessageDetailResponse (single message)
Includes all MessageResponse fields plus:
| Field | Type | Description |
|---|---|---|
body_text | string | null | Plain-text body |
body_html | string | null | HTML body |
bcc_addresses | string[] | null | BCC recipients |
in_reply_to | string | null | RFC 5322 In-Reply-To header |
references | string[] | null | RFC 5322 References headers |
attachment_metadata | object[] | null | Attachment descriptors for each file, including filename, content type, and size |
ses_message_id | string | null | Outbound provider message ID |
updated_at | string | Last update timestamp (ISO 8601) |
reply_all_recipients | object | null | Server-suggested reply-all recipients ({ to, cc }), with the mailbox itself and BCC excluded — prefill editable To/Cc fields from this |