Skip to content

Threads ​

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/threads ​

List threads

The inbox, by conversation, cursor-paginated. Two orthogonal axes combine: folder (default inbox; all excludes trash and spam) and filter (unread, flagged, with attachments). q searches the current folder; searchEverywhere widens it to every folder, trash and spam included. An unknown folder key is a 400.

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

Parameters

NameInTypeRequired
mailboxIdquerystringno
cursorquerystringno
limitqueryintegerno
qquerystringno
filterquery"all" | "unread" | "flagged" | "attachments"no
folderquerystringno
searchEverywherequerybooleanno

Responses

200 — A page of threads.

FieldTypeRequired
threadsobject[]yes
nextCursorstring | nullyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "threads": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "mailboxId": { "type": "string" },
          "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "lastMessageAt": { "type": "string" },
          "messageCount": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "unreadCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "participants": {
            "maxItems": 3,
            "type": "array",
            "items": {
              "type": "object",
              "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
              "required": [ "email" ],
              "additionalProperties": false
            }
          },
          "participantCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "hasAttachments": { "type": "boolean" },
          "flags": {
            "type": "object",
            "properties": {
              "seen": { "type": "boolean" },
              "flagged": { "type": "boolean" },
              "draft": { "type": "boolean" },
              "sent": { "type": "boolean" }
            },
            "required": [ "seen", "flagged", "draft", "sent" ],
            "additionalProperties": false
          },
          "classification": {
            "type": "object",
            "propertyNames": { "type": "string" },
            "additionalProperties": {}
          }
        },
        "required": [
          "id",
          "mailboxId",
          "subject",
          "lastMessageAt",
          "messageCount",
          "unreadCount",
          "participants",
          "participantCount",
          "snippet",
          "hasAttachments",
          "flags",
          "classification"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
  },
  "required": [ "threads", "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/threads/{id} ​

Read a thread

The whole conversation in reading order, with each message’s sanitised body. A thread outside the member’s mailboxes is the same 404 as a missing one.

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

Parameters

NameInTypeRequired
idpathstring (uuid)yes

Responses

200 — The thread.

FieldTypeRequired
threadobjectyes
messagesobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "thread": {
      "type": "object",
      "properties": {
        "id": { "type": "string" },
        "mailboxId": { "type": "string" },
        "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "lastMessageAt": { "type": "string" },
        "messageCount": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "unreadCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "participants": {
          "maxItems": 3,
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
            "required": [ "email" ],
            "additionalProperties": false
          }
        },
        "participantCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "hasAttachments": { "type": "boolean" },
        "flags": {
          "type": "object",
          "properties": {
            "seen": { "type": "boolean" },
            "flagged": { "type": "boolean" },
            "draft": { "type": "boolean" },
            "sent": { "type": "boolean" }
          },
          "required": [ "seen", "flagged", "draft", "sent" ],
          "additionalProperties": false
        },
        "classification": {
          "type": "object",
          "propertyNames": { "type": "string" },
          "additionalProperties": {}
        }
      },
      "required": [
        "id",
        "mailboxId",
        "subject",
        "lastMessageAt",
        "messageCount",
        "unreadCount",
        "participants",
        "participantCount",
        "snippet",
        "hasAttachments",
        "flags",
        "classification"
      ],
      "additionalProperties": false
    },
    "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
      }
    }
  },
  "required": [ "thread", "messages" ],
  "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/threads/{id}/actions ​

Act on a whole thread

The messages of the thread are resolved by the instance at the time of the call, so a message that arrived after the screen was drawn is included. One operation per message. A thread of 1000 messages or more is refused rather than truncated.

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
threadIdstringyes
opsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "threadId": { "type": "string" },
    "ops": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "messageId": { "type": "string" },
          "opId": { "type": "string" },
          "duplicate": { "type": "boolean" }
        },
        "required": [ "messageId", "opId", "duplicate" ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "threadId", "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.thread_not_found, webmail.thread_too_large, 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.