Skip to content

Mailboxes ​

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

List my mailboxes

The mailboxes connected by the calling member, with their status, message count, date of the last synchronisation and of the last concluded analysis.

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

Responses

200 — The mailboxes.

FieldTypeRequired
mailboxesobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "mailboxes": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "provider": { "type": "string", "enum": [ "gmail", "msgraph", "imap" ] },
          "address": { "type": "string" },
          "status": { "type": "string", "enum": [ "active", "disconnected", "error" ] },
          "syncMode": { "type": "string", "enum": [ "full", "processing_only" ] },
          "connectedAt": { "type": "string" },
          "backfill": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [ "pending", "running", "done", "failed" ]
                  },
                  "progressPct": {
                    "anyOf": [
                      { "type": "number", "minimum": 0, "maximum": 100 },
                      { "type": "null" }
                    ]
                  },
                  "oldestSyncedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
                },
                "required": [ "status", "progressPct", "oldestSyncedAt" ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "lastDeltaAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "lastSuccessfulSyncAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "lastError": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "kind": { "type": "string" },
                  "code": { "type": "string" },
                  "message": { "type": "string" },
                  "at": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
                  },
                  "operation": { "type": "string" },
                  "httpStatus": {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  }
                },
                "required": [ "kind" ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "messageCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "analyzedAt": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
        },
        "required": [
          "id",
          "provider",
          "address",
          "status",
          "syncMode",
          "connectedAt",
          "backfill",
          "lastDeltaAt",
          "lastError",
          "messageCount"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "mailboxes" ],
  "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.

POST /api/v1/mailboxes/imap ​

Connect an IMAP/SMTP mailbox

The only provider without an OAuth flow. Both servers are probed before anything is stored, and the failure names which one refused. Reconnecting an address already known to the member replaces its credentials instead of creating a second mailbox (created: false). The password never appears in any response or log.

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

Request body (application/json)

FieldTypeRequired
addressstringyes
imapHoststringyes
imapPortintegerno
imapSecurebooleanno
smtpHoststringyes
smtpPortintegerno
smtpSecurebooleanno
usernamestringno
passwordstringyes
allowInvalidCertificatebooleanno
JSON Schema
json
{
  "type": "object",
  "properties": {
    "address": { "type": "string" },
    "imapHost": { "type": "string", "minLength": 1, "maxLength": 255 },
    "imapPort": { "default": 993, "type": "integer", "minimum": 1, "maximum": 65535 },
    "imapSecure": { "default": true, "type": "boolean" },
    "smtpHost": { "type": "string", "minLength": 1, "maxLength": 255 },
    "smtpPort": { "default": 465, "type": "integer", "minimum": 1, "maximum": 65535 },
    "smtpSecure": { "default": true, "type": "boolean" },
    "username": { "type": "string", "minLength": 1, "maxLength": 255 },
    "password": { "type": "string", "minLength": 1, "maxLength": 1024 },
    "allowInvalidCertificate": { "type": "boolean" }
  },
  "required": [ "address", "imapHost", "smtpHost", "password" ]
}

Responses

201 — The mailbox, connected or reconnected.

FieldTypeRequired
mailboxIdstringyes
addressstringyes
createdbooleanyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "mailboxId": { "type": "string" },
    "address": { "type": "string" },
    "created": { "type": "boolean" }
  },
  "required": [ "mailboxId", "address", "created" ],
  "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 — request.bad_request, credentials.encryption_disabled, imap.host_not_found, imap.unreachable, imap.tls_failed, imap.auth_failed, smtp.host_not_found, smtp.unreachable, smtp.tls_failed, smtp.auth_failed. 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
{
  "address": "alice@example.test",
  "password": "••••••••",
  "imapHost": "imap.example.test",
  "imapPort": 993,
  "imapSecure": true,
  "smtpHost": "smtp.example.test",
  "smtpPort": 465,
  "smtpSecure": true
}

Example response (201)

json
{
  "mailboxId": "0192f1c2-3b4d-7e8f-9a0b-1c2d3e4f5a6b",
  "address": "alice@example.test",
  "created": true
}

POST /api/v1/mailboxes/{id}/sync ​

Synchronise a mailbox now

Requests a delta from the provider. enqueued: false is not a failure: a delta was already pending for this mailbox. A mailbox that is not active is refused with 409 rather than silently queued.

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

Parameters

NameInTypeRequired
idpathstringyes

Responses

202 — Synchronisation requested.

FieldTypeRequired
enqueuedbooleanyes
JSON Schema
json
{
  "type": "object",
  "properties": { "enqueued": { "type": "boolean" } },
  "required": [ "enqueued" ],
  "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.

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 — mailbox.not_found, mailbox.not_active. 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/mailboxes/{id}/resync ​

Catch up a period

Re-reads the mailbox from since to now, in slices. Idempotent (known messages are not re-downloaded) and silent: nothing is journaled, no workflow is dispatched, so three weeks can be replayed without three weeks of automatic replies. since must be in the past and within 92 days.

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

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
sincestring (date-time)yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "since": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    }
  },
  "required": [ "since" ]
}

Responses

202 — Catch-up requested.

FieldTypeRequired
enqueuedbooleanyes

Same schema as POST /api/v1/mailboxes/{id}/sync.

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 — mailbox.not_found, mailbox.resync_window_invalid, mailbox.not_active. 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/mailboxes/{id}/removal-impact ​

What removing a mailbox would take away

For the confirmation dialog: messages, running executions and published workflows that trigger on or send from this mailbox.

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

Parameters

NameInTypeRequired
idpathstringyes

Responses

200 — The impact.

FieldTypeRequired
mailboxIdstringyes
addressstringyes
messagesintegeryes
pendingExecutionsintegeryes
workflowsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "mailboxId": { "type": "string" },
    "address": { "type": "string" },
    "messages": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
    "pendingExecutions": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
    "workflows": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "link": { "type": "string", "enum": [ "trigger", "sender" ] }
        },
        "required": [ "id", "name", "link" ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "mailboxId", "address", "messages", "pendingExecutions", "workflows" ],
  "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 — mailbox.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/mailboxes/{id}/disconnect ​

Disconnect a mailbox

Synchronisation stops and the secret is removed; the mirrored history stays readable. Reversible (reconnecting reactivates the same mailbox), hence no confirmation. Idempotent.

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

Parameters

NameInTypeRequired
idpathstringyes

Responses

200 — The mailbox, disconnected.

FieldTypeRequired
mailboxIdstringyes
addressstringyes
disarmedWorkflowsintegeryes
credential"deleted" | "kept" | "none"yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "mailboxId": { "type": "string" },
    "address": { "type": "string" },
    "disarmedWorkflows": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
    "credential": { "type": "string", "enum": [ "deleted", "kept", "none" ] }
  },
  "required": [ "mailboxId", "address", "disarmedWorkflows", "credential" ],
  "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 — mailbox.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

DELETE /api/v1/mailboxes/{id} ​

Delete a mailbox and its mirror

The most destructive gesture of the instance: messages, threads, attachments, journal and executions are purged, workflows sending from the mailbox are unpublished. The body must repeat the mailbox address in confirm (case-insensitive). Rows are gone when the response leaves; blobs are purged asynchronously.

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

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
confirmstringyes
JSON Schema
json
{
  "type": "object",
  "properties": { "confirm": { "type": "string", "minLength": 1, "maxLength": 320 } },
  "required": [ "confirm" ]
}

Responses

200 — What was deleted.

FieldTypeRequired
mailboxIdstringyes
addressstringyes
deletedobjectyes
unpublishedWorkflowsintegeryes
credential"deleted" | "kept" | "none"yes
blobsQueuedbooleanyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "mailboxId": { "type": "string" },
    "address": { "type": "string" },
    "deleted": {
      "type": "object",
      "properties": {
        "messages": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "threads": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "attachments": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "journalEntries": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "outboundOps": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "executions": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }
      },
      "required": [
        "messages",
        "threads",
        "attachments",
        "journalEntries",
        "outboundOps",
        "executions"
      ],
      "additionalProperties": false
    },
    "unpublishedWorkflows": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
    "credential": { "type": "string", "enum": [ "deleted", "kept", "none" ] },
    "blobsQueued": { "type": "boolean" }
  },
  "required": [
    "mailboxId",
    "address",
    "deleted",
    "unpublishedWorkflows",
    "credential",
    "blobsQueued"
  ],
  "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 — mailbox.not_found, mailbox.confirmation_mismatch. 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/journal ​

List the intake journal

Why did this message trigger nothing? One entry per received message with its outcome, newest first, cursor-paginated. mailboxId is intersected with the member’s mailboxes, never substituted; q is accepted and ignored.

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

Parameters

NameInTypeRequired
mailboxIdquerystringno
cursorquerystringno
limitqueryintegerno
qquerystringno

Responses

200 — A page of entries.

FieldTypeRequired
entriesobject[]yes
nextCursorstring | nullyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "entries": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "mailboxId": { "type": "string" },
          "providerMessageId": { "type": "string" },
          "outcome": {
            "type": "string",
            "enum": [
              "excluded_org",
              "excluded_member",
              "guardrail",
              "unmatched",
              "dispatched"
            ]
          },
          "ruleRef": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "from": {
            "anyOf": [
              {
                "type": "object",
                "properties": { "name": { "type": "string" }, "email": { "type": "string" } },
                "required": [ "email" ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "messageId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "threadId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "executions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "workflowId": { "type": "string" },
                "workflowName": { "type": "string" }
              },
              "required": [ "id", "workflowId", "workflowName" ],
              "additionalProperties": false
            }
          },
          "createdAt": { "type": "string" }
        },
        "required": [
          "id",
          "mailboxId",
          "providerMessageId",
          "outcome",
          "ruleRef",
          "subject",
          "from",
          "messageId",
          "threadId",
          "executions",
          "createdAt"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
  },
  "required": [ "entries", "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/journal/stats ​

Count journal entries by outcome

Received, excluded, processed… over the member’s mailboxes, or one of them.

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

Parameters

NameInTypeRequired
mailboxIdquerystringno
cursorquerystringno
limitqueryintegerno
qquerystringno

Responses

200 — The counters.

FieldTypeRequired
byOutcomeobjectyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "byOutcome": {
      "type": "object",
      "propertyNames": {
        "type": "string",
        "enum": [
          "excluded_org",
          "excluded_member",
          "guardrail",
          "unmatched",
          "dispatched"
        ]
      },
      "additionalProperties": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
      "required": [
        "excluded_org",
        "excluded_member",
        "guardrail",
        "unmatched",
        "dispatched"
      ]
    }
  },
  "required": [ "byOutcome" ],
  "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.