Skip to content

Messages ​

Routes are relative to <PUBLIC_BASE_URL>; request and response bodies are JSON unless stated otherwise. Authentication, scopes, pagination and the error format are described in the REST API guides.

GET /api/v1/messages ​

List mirrored messages

The flat, most-recent-first view of the mirror (not the threaded inbox), cursor-paginated. q searches subject, preview and sender, with the same grammar as GET /api/v1/threads, and never leaves the member’s mailboxes.

Access — Member session or API key with scope messages:read.

Parameters

NameInTypeRequired
mailboxIdquerystringno
cursorquerystringno
limitqueryintegerno
qquerystringno

Responses

200 — A page of messages.

FieldTypeRequired
messagesobject[]yes
nextCursorstring | nullyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "messages": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "mailboxId": { "type": "string" },
          "from": {
            "anyOf": [
              {
                "type": "object",
                "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
                "required": [ "email" ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "receivedAt": { "type": "string" },
          "folderLabels": { "type": "array", "items": { "type": "string" } },
          "flags": {
            "type": "object",
            "properties": {
              "seen": { "type": "boolean" },
              "flagged": { "type": "boolean" },
              "draft": { "type": "boolean" },
              "sent": { "type": "boolean" }
            },
            "required": [ "seen", "flagged", "draft", "sent" ],
            "additionalProperties": false
          },
          "signals": {
            "type": "object",
            "properties": {
              "isAutoReply": { "type": "boolean" },
              "isNoReply": { "type": "boolean" },
              "isMailingList": { "type": "boolean" },
              "isFromSelf": { "type": "boolean" }
            },
            "required": [ "isAutoReply", "isNoReply", "isMailingList", "isFromSelf" ],
            "additionalProperties": false
          },
          "hasAttachments": { "type": "boolean" }
        },
        "required": [
          "id",
          "mailboxId",
          "from",
          "subject",
          "snippet",
          "receivedAt",
          "folderLabels",
          "flags",
          "signals",
          "hasAttachments"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
  },
  "required": [ "messages", "nextCursor" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.bad_request. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/messages/{id} ​

Read a message

The full message: headers, recipients, attachments list and bodyHtmlSafe, sanitised on every call and never stored. Render that HTML in a sandboxed frame, never inline.

Access — Member session or API key with scope messages:read.

Parameters

NameInTypeRequired
idpathstring (uuid)yes

Responses

200 — The message.

FieldTypeRequired
idstringyes
mailboxIdstringyes
fromobject | nullyes
subjectstring | nullyes
snippetstring | nullyes
receivedAtstringyes
folderLabelsstring[]yes
flagsobjectyes
signalsobjectyes
hasAttachmentsbooleanyes
threadIdstring | nullyes
classificationobjectyes
bodyTextstring | nullyes
bodyHtmlSafestring | nullyes
hasRemoteImagesbooleanyes
bodyCompletebooleanyes
headersSubsetobjectyes
attachmentsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "id": { "type": "string" },
    "mailboxId": { "type": "string" },
    "from": {
      "anyOf": [
        {
          "type": "object",
          "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
          "required": [ "email" ],
          "additionalProperties": false
        },
        { "type": "null" }
      ]
    },
    "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "receivedAt": { "type": "string" },
    "folderLabels": { "type": "array", "items": { "type": "string" } },
    "flags": {
      "type": "object",
      "properties": {
        "seen": { "type": "boolean" },
        "flagged": { "type": "boolean" },
        "draft": { "type": "boolean" },
        "sent": { "type": "boolean" }
      },
      "required": [ "seen", "flagged", "draft", "sent" ],
      "additionalProperties": false
    },
    "signals": {
      "type": "object",
      "properties": {
        "isAutoReply": { "type": "boolean" },
        "isNoReply": { "type": "boolean" },
        "isMailingList": { "type": "boolean" },
        "isFromSelf": { "type": "boolean" }
      },
      "required": [ "isAutoReply", "isNoReply", "isMailingList", "isFromSelf" ],
      "additionalProperties": false
    },
    "hasAttachments": { "type": "boolean" },
    "threadId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "classification": {
      "type": "object",
      "propertyNames": { "type": "string" },
      "additionalProperties": {}
    },
    "bodyText": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "bodyHtmlSafe": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "hasRemoteImages": { "type": "boolean" },
    "bodyComplete": { "type": "boolean" },
    "headersSubset": {
      "type": "object",
      "properties": {
        "to": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
            "required": [ "email" ],
            "additionalProperties": false
          }
        },
        "cc": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
            "required": [ "email" ],
            "additionalProperties": false
          }
        },
        "replyTo": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
            "required": [ "email" ],
            "additionalProperties": false
          }
        },
        "date": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "messageIdHeader": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
      },
      "required": [ "to", "cc", "replyTo", "date", "messageIdHeader" ],
      "additionalProperties": false
    },
    "attachments": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "position": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "filename": { "type": "string" },
          "mime": { "type": "string" },
          "size": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "disposition": { "type": "string", "enum": [ "attachment", "inline" ] },
          "contentId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "downloadUrl": { "type": "string" }
        },
        "required": [
          "id",
          "position",
          "filename",
          "mime",
          "size",
          "disposition",
          "contentId",
          "downloadUrl"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "id",
    "mailboxId",
    "from",
    "subject",
    "snippet",
    "receivedAt",
    "folderLabels",
    "flags",
    "signals",
    "hasAttachments",
    "threadId",
    "classification",
    "bodyText",
    "bodyHtmlSafe",
    "hasRemoteImages",
    "bodyComplete",
    "headersSubset",
    "attachments"
  ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/messages/{id}/attachments/{position} ​

Download an attachment

The raw bytes of the attachment at this MIME position, served as a download (Content-Disposition: attachment, nosniff, no-store). Active types (HTML, SVG, scripts) are forced to application/octet-stream. Out of scope, unknown position or purged blob: one and the same 404.

Access — Member session or API key with scope messages:read.

Parameters

NameInTypeRequired
idpathstring (uuid)yes
positionpathintegeryes

Responses

200 — The file.

Content type : application/octet-stream

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/messages/{id}/journal ​

Why did this message trigger what it did

The journal entry of one message and the executions it started.

Access — Member session or API key with scope messages:read.

Parameters

NameInTypeRequired
idpathstring (uuid)yes

Responses

200 — The entry and its executions.

FieldTypeRequired
entryobject | nullyes
executionsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "entry": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "id": { "type": "string" },
            "outcome": {
              "type": "string",
              "enum": [
                "excluded_org",
                "excluded_member",
                "guardrail",
                "unmatched",
                "dispatched"
              ]
            },
            "ruleRef": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
            "createdAt": { "type": "string" }
          },
          "required": [ "id", "outcome", "ruleRef", "createdAt" ],
          "additionalProperties": false
        },
        { "type": "null" }
      ]
    },
    "executions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "workflowId": { "type": "string" },
          "workflowName": { "type": "string" },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "waiting",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "simulated": { "type": "boolean" },
          "createdAt": { "type": "string" },
          "finishedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
        },
        "required": [
          "id",
          "workflowId",
          "workflowName",
          "status",
          "simulated",
          "createdAt",
          "finishedAt"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "entry", "executions" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/messages/send ​

Send a message

Composes and queues a message from one of the member’s mailboxes. Nothing leaves synchronously: the response carries the opId of the outbound operation and the mirror reports the real state on the next delta. clientToken is mandatory and deduplicates: a replay returns 202 { duplicate: true } with the same opId. Attachments are references uploaded beforehand. Subject to the sending kill switch (403) and the per-mailbox rate limit (429, with Retry-After and retryAfterMs).

Access — Member session or API key with scope messages:write.

Request body (application/json)

FieldTypeRequired
mailboxIdstringyes
tostring (email)[]yes
ccstring (email)[]no
bccstring (email)[]no
subjectstringyes
bodyTextstringyes
bodyHtmlstringno
replyToMessageIdstringno
forwardOfMessageIdstringno
includeOriginalAttachmentsbooleanno
quoteOriginalbooleanno
attachmentsobject[]no
clientTokenstringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "mailboxId": { "type": "string", "minLength": 1 },
    "to": {
      "minItems": 1,
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "cc": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "bcc": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "subject": { "type": "string", "maxLength": 512 },
    "bodyText": { "type": "string", "maxLength": 200000 },
    "bodyHtml": { "type": "string", "maxLength": 500000 },
    "replyToMessageId": { "type": "string", "minLength": 1 },
    "forwardOfMessageId": { "type": "string", "minLength": 1 },
    "includeOriginalAttachments": { "type": "boolean" },
    "quoteOriginal": { "type": "boolean" },
    "attachments": {
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "attachmentId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._-]+$"
          }
        },
        "required": [ "attachmentId" ]
      }
    },
    "clientToken": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9._:-]+$"
    }
  },
  "required": [ "mailboxId", "to", "subject", "bodyText", "clientToken" ]
}

Responses

202 — Queued.

FieldTypeRequired
opIdstringyes
kind"send" | "draft"yes
duplicatebooleanyes
threadIdstring | nullyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "opId": { "type": "string" },
    "kind": { "type": "string", "enum": [ "send", "draft" ] },
    "duplicate": { "type": "boolean" },
    "threadId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
  },
  "required": [ "opId", "kind", "duplicate", "threadId" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.mailbox_not_found, webmail.message_not_found, webmail.attachment_not_found, webmail.message_too_large, webmail.sending_disabled, webmail.rate_limited. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

Example request

json
{
  "mailboxId": "0192f1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6b",
  "to": [ "bob@example.test" ],
  "subject": "Quote #1042",
  "bodyText": "Hello Bob,\n\nPlease find the quote attached.\n\nAlice",
  "attachments": [ { "attachmentId": "0192f1c2-0000-7000-8000-00000000a7a7" } ],
  "clientToken": "b2c7e4a0-9f3d-4c1e-8a6b-5d2f1e0c9b8a"
}

Example response (202)

json
{
  "opId": "0192f1c2-0000-7000-8000-0000000000aa",
  "kind": "send",
  "duplicate": false,
  "threadId": null
}

POST /api/v1/messages/drafts ​

Create a draft

The same body as /send, saved as a draft in the provider. Neither the kill switch nor the rate limit applies: a draft does not leave. A replayed clientToken is a duplicate, not an overwrite — use PUT to overwrite.

Access — Member session or API key with scope messages:write.

Request body (application/json)

FieldTypeRequired
mailboxIdstringyes
tostring (email)[]yes
ccstring (email)[]no
bccstring (email)[]no
subjectstringyes
bodyTextstringyes
bodyHtmlstringno
replyToMessageIdstringno
forwardOfMessageIdstringno
includeOriginalAttachmentsbooleanno
quoteOriginalbooleanno
attachmentsobject[]no
clientTokenstringyes

Same schema as POST /api/v1/messages/send.

Responses

202 — Queued.

FieldTypeRequired
opIdstringyes
kind"send" | "draft"yes
duplicatebooleanyes
threadIdstring | nullyes

Same schema as POST /api/v1/messages/send.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.mailbox_not_found, webmail.message_not_found, webmail.attachment_not_found, webmail.message_too_large. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

PUT /api/v1/messages/drafts ​

Save a draft (autosave)

Upserts the draft identified by clientToken: the content already saved under this token is rewritten, so one composing session yields one draft however many saves happen. savedAt is the time of this save. Neither the kill switch nor the rate limit applies.

Access — Member session or API key with scope messages:write.

Request body (application/json)

FieldTypeRequired
mailboxIdstringyes
tostring (email)[]yes
ccstring (email)[]no
bccstring (email)[]no
subjectstringyes
bodyTextstringyes
bodyHtmlstringno
replyToMessageIdstringno
forwardOfMessageIdstringno
includeOriginalAttachmentsbooleanno
quoteOriginalbooleanno
attachmentsobject[]no
clientTokenstringyes

Same schema as POST /api/v1/messages/send.

Responses

202 — Queued.

FieldTypeRequired
opIdstringyes
kind"send" | "draft"yes
duplicatebooleanyes
threadIdstring | nullyes
savedAtstringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "opId": { "type": "string" },
    "kind": { "type": "string", "enum": [ "send", "draft" ] },
    "duplicate": { "type": "boolean" },
    "threadId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "savedAt": { "type": "string" }
  },
  "required": [ "opId", "kind", "duplicate", "threadId", "savedAt" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — webmail.bad_request, webmail.mailbox_not_found, webmail.message_not_found, webmail.attachment_not_found, webmail.message_too_large. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/messages/attachments ​

Upload an attachment

One file per request, as multipart, before composing: the response gives the attachmentId to reference in attachments. Returns what the instance observed (cleaned name, normalised type, measured size). Files over 25 MB are refused with 413; executables and scripts with 400.

Access — Member session or API key with scope messages:write.

Request body (multipart/form-data)

FieldTypeRequiredDescription
filestringyesThe file, as a binary multipart part.
JSON Schema
json
{
  "type": "object",
  "properties": {
    "file": { "type": "string", "description": "The file, as a binary multipart part." }
  },
  "required": [ "file" ]
}

Responses

201 — The stored attachment.

FieldTypeRequired
attachmentIdstringyes
filenamestringyes
sizeintegeryes
mimestringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "attachmentId": { "type": "string" },
    "filename": { "type": "string" },
    "size": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
    "mime": { "type": "string" }
  },
  "required": [ "attachmentId", "filename", "size", "mime" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.attachment_too_large, webmail.attachment_rejected. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/messages/{id}/forward ​

Forward a message

A shortcut over /send: the original attachments are re-referenced by the instance (no re-upload), the subject gets Fwd:, the References chain is kept without In-Reply-To. Same kill switch and rate limit as a send.

Access — Member session or API key with scope messages:write.

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
tostring (email)[]yes
ccstring (email)[]no
bccstring (email)[]no
subjectstringno
bodyTextstringyes
bodyHtmlstringno
includeOriginalAttachmentsbooleanno
quoteOriginalbooleanno
attachmentsobject[]no
asDraftbooleanno
clientTokenstringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "to": {
      "minItems": 1,
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "cc": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "bcc": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "subject": { "type": "string", "maxLength": 512 },
    "bodyText": { "type": "string", "maxLength": 200000 },
    "bodyHtml": { "type": "string", "maxLength": 500000 },
    "includeOriginalAttachments": { "type": "boolean" },
    "quoteOriginal": { "type": "boolean" },
    "attachments": {
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "attachmentId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._-]+$"
          }
        },
        "required": [ "attachmentId" ]
      }
    },
    "asDraft": { "type": "boolean" },
    "clientToken": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9._:-]+$"
    }
  },
  "required": [ "to", "bodyText", "clientToken" ]
}

Responses

202 — Queued.

FieldTypeRequired
opIdstringyes
kind"send" | "draft"yes
duplicatebooleanyes
threadIdstring | nullyes

Same schema as POST /api/v1/messages/send.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.mailbox_not_found, webmail.message_not_found, webmail.attachment_not_found, webmail.message_too_large, webmail.sending_disabled, webmail.rate_limited. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/messages/{id}/reply ​

Reply to a message

A shortcut over /send: mailbox, recipients, subject and thread are pre-resolved from the original, then exactly the same path as a send — no reply bypasses the kill switch or the rate limit.

Access — Member session or API key with scope messages:write.

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
tostring (email)[]no
ccstring (email)[]no
bccstring (email)[]no
subjectstringno
bodyTextstringyes
bodyHtmlstringno
asDraftbooleanno
attachmentsobject[]no
clientTokenstringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "to": {
      "minItems": 1,
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "cc": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "bcc": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 320,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "subject": { "type": "string", "maxLength": 512 },
    "bodyText": { "type": "string", "maxLength": 200000 },
    "bodyHtml": { "type": "string", "maxLength": 500000 },
    "asDraft": { "type": "boolean" },
    "attachments": {
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "attachmentId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._-]+$"
          }
        },
        "required": [ "attachmentId" ]
      }
    },
    "clientToken": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9._:-]+$"
    }
  },
  "required": [ "bodyText", "clientToken" ]
}

Responses

202 — Queued.

FieldTypeRequired
opIdstringyes
kind"send" | "draft"yes
duplicatebooleanyes
threadIdstring | nullyes

Same schema as POST /api/v1/messages/send.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.mailbox_not_found, webmail.message_not_found, webmail.attachment_not_found, webmail.message_too_large, webmail.sending_disabled, webmail.rate_limited. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/messages/{id}/actions ​

Act on a message

Archive, mark read or unread, flag, move to a folder, trash… The response says the operation is recorded, not that the state changed: the mirror reports it on the next delta. Gestures the provider cannot do (permanent deletion on Gmail) are refused before anything is queued.

Access — Member session or API key with scope messages:write.

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
actionstring (enum)yes
labelsobjectno
clientTokenstringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": [
        "markRead",
        "markUnread",
        "flag",
        "unflag",
        "archive",
        "unarchive",
        "trash",
        "restore",
        "spam",
        "notSpam",
        "deletePermanently",
        "move"
      ]
    },
    "labels": {
      "type": "object",
      "properties": {
        "add": {
          "maxItems": 20,
          "type": "array",
          "items": { "type": "string", "minLength": 1, "maxLength": 200 }
        },
        "remove": {
          "maxItems": 20,
          "type": "array",
          "items": { "type": "string", "minLength": 1, "maxLength": 200 }
        }
      }
    },
    "clientToken": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9._:-]+$"
    }
  },
  "required": [ "action", "clientToken" ]
}

Responses

202 — The queued operations.

FieldTypeRequired
opsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "ops": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "messageId": { "type": "string" },
          "opId": { "type": "string" },
          "duplicate": { "type": "boolean" }
        },
        "required": [ "messageId", "opId", "duplicate" ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "ops" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.message_not_found, webmail.unsupported_action. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/messages/actions ​

Act on several messages

The same action on up to 100 messages, all or nothing on the perimeter: a single id outside the member’s mailboxes is a 404 and nothing is queued.

Access — Member session or API key with scope messages:write.

Request body (application/json)

FieldTypeRequired
messageIdsstring[]yes
actionstring (enum)yes
labelsobjectno
clientTokenstringyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "messageIds": {
      "minItems": 1,
      "maxItems": 100,
      "type": "array",
      "items": { "type": "string", "minLength": 1 }
    },
    "action": {
      "type": "string",
      "enum": [
        "markRead",
        "markUnread",
        "flag",
        "unflag",
        "archive",
        "unarchive",
        "trash",
        "restore",
        "spam",
        "notSpam",
        "deletePermanently",
        "move"
      ]
    },
    "labels": {
      "type": "object",
      "properties": {
        "add": {
          "maxItems": 20,
          "type": "array",
          "items": { "type": "string", "minLength": 1, "maxLength": 200 }
        },
        "remove": {
          "maxItems": 20,
          "type": "array",
          "items": { "type": "string", "minLength": 1, "maxLength": 200 }
        }
      }
    },
    "clientToken": {
      "type": "string",
      "minLength": 8,
      "maxLength": 128,
      "pattern": "^[A-Za-z0-9._:-]+$"
    }
  },
  "required": [ "messageIds", "action", "clientToken" ]
}

Responses

202 — The queued operations.

FieldTypeRequired
opsobject[]yes

Same schema as POST /api/v1/messages/{id}/actions.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — webmail.bad_request, webmail.message_not_found, webmail.unsupported_action. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.