English
Morning review and sending journal
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/review
List the drafts to review
The member’s queue, cursor-paginated, with the count of drafts truly pending for the menu badge. Filterable by mailbox and workflow.
Access — Member session or API key with scope review:read.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
mailboxId | query | string | no |
workflowId | query | string | no |
cursor | query | string | no |
limit | query | integer | no |
Responses
200 — A page of drafts.
| Field | Type | Required |
|---|---|---|
items | object[] | yes |
nextCursor | string | null | yes |
pendingCount | integer | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"mailboxAddress": { "type": "string" },
"mode": { "type": "string", "enum": [ "draft", "send" ] },
"origin": { "type": "string", "enum": [ "workflow", "webmail", "approval" ] },
"recipients": { "type": "array", "items": { "type": "string" } },
"subject": { "type": "string" },
"createdAt": { "type": "string" },
"reviewStatus": {
"type": "string",
"enum": [ "none", "pending", "sent", "rejected", "snoozed", "gone" ]
},
"reviewReason": { "type": "string" },
"reviewAt": { "type": "string" },
"snoozeUntil": { "type": "string" },
"sendStatus": {
"type": "string",
"enum": [
"pending",
"held",
"running",
"done",
"failed",
"cancelled",
"unknown"
]
},
"sendErrorKind": { "type": "string" },
"executionId": { "type": "string" },
"workflowId": { "type": "string" },
"workflowName": { "type": "string" },
"nodeId": { "type": "string" },
"nodeType": { "type": "string" },
"messageIdHeader": { "type": "string" },
"messageId": { "type": "string" },
"threadId": { "type": "string" }
},
"required": [
"id",
"mailboxId",
"mailboxAddress",
"mode",
"origin",
"recipients",
"subject",
"createdAt",
"reviewStatus",
"sendStatus"
],
"additionalProperties": false
}
},
"nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"pendingCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }
},
"required": [ "items", "nextCursor", "pendingCount" ],
"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 — review.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/review/{id}
Preview a draft
The only place the body leaves, sanitised. Another member’s draft is a 404.
Access — Member session or API key with scope review:read.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Responses
200 — The draft and its body.
| Field | Type | Required |
|---|---|---|
message | object | yes |
cc | string[] | yes |
bcc | string[] | yes |
bodyText | string | no |
bodyHtml | string | no |
hasRemoteImages | boolean | yes |
attachments | object[] | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"message": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"mailboxAddress": { "type": "string" },
"mode": { "type": "string", "enum": [ "draft", "send" ] },
"origin": { "type": "string", "enum": [ "workflow", "webmail", "approval" ] },
"recipients": { "type": "array", "items": { "type": "string" } },
"subject": { "type": "string" },
"createdAt": { "type": "string" },
"reviewStatus": {
"type": "string",
"enum": [ "none", "pending", "sent", "rejected", "snoozed", "gone" ]
},
"reviewReason": { "type": "string" },
"reviewAt": { "type": "string" },
"snoozeUntil": { "type": "string" },
"sendStatus": {
"type": "string",
"enum": [
"pending",
"held",
"running",
"done",
"failed",
"cancelled",
"unknown"
]
},
"sendErrorKind": { "type": "string" },
"executionId": { "type": "string" },
"workflowId": { "type": "string" },
"workflowName": { "type": "string" },
"nodeId": { "type": "string" },
"nodeType": { "type": "string" },
"messageIdHeader": { "type": "string" },
"messageId": { "type": "string" },
"threadId": { "type": "string" }
},
"required": [
"id",
"mailboxId",
"mailboxAddress",
"mode",
"origin",
"recipients",
"subject",
"createdAt",
"reviewStatus",
"sendStatus"
],
"additionalProperties": false
},
"cc": { "type": "array", "items": { "type": "string" } },
"bcc": { "type": "array", "items": { "type": "string" } },
"bodyText": { "type": "string" },
"bodyHtml": { "type": "string" },
"hasRemoteImages": { "type": "boolean" },
"attachments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"filename": { "type": "string" },
"mime": { "type": "string" },
"size": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }
},
"required": [ "filename", "mime", "size" ],
"additionalProperties": false
}
}
},
"required": [ "message", "cc", "bcc", "hasRemoteImages", "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 — review.not_found, review.content_gone. 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/review/{id}/send
Send a reviewed draft
Optionally with corrections. 202: the send is recorded, not delivered; nothing leaves synchronously. A draft already sent or rejected is a 409.
Access — Member session or API key with scope review:write.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Request body (application/json)
| Field | Type | Required |
|---|---|---|
clientToken | string | yes |
subject | string | no |
bodyText | string | no |
bodyHtml | string | no |
to | string[] | no |
cc | string[] | no |
bcc | string[] | no |
JSON Schema
json
{
"type": "object",
"properties": {
"clientToken": { "type": "string", "minLength": 8, "maxLength": 200 },
"subject": { "type": "string", "maxLength": 500 },
"bodyText": { "type": "string", "maxLength": 200000 },
"bodyHtml": { "type": "string", "maxLength": 400000 },
"to": { "maxItems": 100, "type": "array", "items": { "type": "string" } },
"cc": { "maxItems": 100, "type": "array", "items": { "type": "string" } },
"bcc": { "maxItems": 100, "type": "array", "items": { "type": "string" } }
},
"required": [ "clientToken" ]
}Responses
202 — The message, queued.
| Field | Type | Required |
|---|---|---|
message | object | yes |
opId | string | no |
JSON Schema
json
{
"type": "object",
"properties": {
"message": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"mailboxAddress": { "type": "string" },
"mode": { "type": "string", "enum": [ "draft", "send" ] },
"origin": { "type": "string", "enum": [ "workflow", "webmail", "approval" ] },
"recipients": { "type": "array", "items": { "type": "string" } },
"subject": { "type": "string" },
"createdAt": { "type": "string" },
"reviewStatus": {
"type": "string",
"enum": [ "none", "pending", "sent", "rejected", "snoozed", "gone" ]
},
"reviewReason": { "type": "string" },
"reviewAt": { "type": "string" },
"snoozeUntil": { "type": "string" },
"sendStatus": {
"type": "string",
"enum": [
"pending",
"held",
"running",
"done",
"failed",
"cancelled",
"unknown"
]
},
"sendErrorKind": { "type": "string" },
"executionId": { "type": "string" },
"workflowId": { "type": "string" },
"workflowName": { "type": "string" },
"nodeId": { "type": "string" },
"nodeType": { "type": "string" },
"messageIdHeader": { "type": "string" },
"messageId": { "type": "string" },
"threadId": { "type": "string" }
},
"required": [
"id",
"mailboxId",
"mailboxAddress",
"mode",
"origin",
"recipients",
"subject",
"createdAt",
"reviewStatus",
"sendStatus"
],
"additionalProperties": false
},
"opId": { "type": "string" }
},
"required": [ "message" ],
"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 — review.bad_request, review.not_found, review.already_resolved, review.content_gone. 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/review/send
Send a selection of drafts
Up to fifty ids. 202, with a verdict per draft: one that cannot be sent does not block the others.
Access — Member session or API key with scope review:write.
Request body (application/json)
| Field | Type | Required |
|---|---|---|
ids | string[] | yes |
clientToken | string | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"ids": {
"minItems": 1,
"maxItems": 50,
"type": "array",
"items": { "type": "string" }
},
"clientToken": { "type": "string", "minLength": 8, "maxLength": 200 }
},
"required": [ "ids", "clientToken" ]
}Responses
202 — The verdicts.
| Field | Type | Required |
|---|---|---|
sent | integer | yes |
skipped | object[] | yes |
pendingCount | integer | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"sent": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"skipped": {
"type": "array",
"items": {
"type": "object",
"properties": { "id": { "type": "string" }, "reason": { "type": "string" } },
"required": [ "id", "reason" ],
"additionalProperties": false
}
},
"pendingCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }
},
"required": [ "sent", "skipped", "pendingCount" ],
"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 — review.bad_request. 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/review/{id}/reject
Reject a draft
With an optional reason, kept in the sending journal.
Access — Member session or API key with scope review:write.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Request body (application/json)
| Field | Type | Required |
|---|---|---|
reason | string | no |
JSON Schema
json
{
"type": "object",
"properties": { "reason": { "type": "string", "maxLength": 500 } }
}Responses
200 — The message, rejected.
| Field | Type | Required |
|---|---|---|
message | object | yes |
opId | string | no |
Same schema as POST /api/v1/review/{id}/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.
Error codes — review.bad_request, review.not_found, review.already_resolved. 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/review/{id}/snooze
Snooze a draft
The draft leaves the queue until until, then comes back.
Access — Member session or API key with scope review:write.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Request body (application/json)
| Field | Type | Required |
|---|---|---|
until | string (date-time) | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"until": {
"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": [ "until" ]
}Responses
200 — The message, snoozed.
| Field | Type | Required |
|---|---|---|
message | object | yes |
opId | string | no |
Same schema as POST /api/v1/review/{id}/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.
Error codes — review.bad_request, review.not_found, review.already_resolved. 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/sending/journal
Read the sending journal
Every outbound message of the member (workflows, webmail, approvals), cursor-paginated. Filters: mailbox, workflow, mode, status, text search on subject and recipients, date range.
Access — Member session or API key with scope review:read.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
mailboxId | query | string | no |
workflowId | query | string | no |
mode | query | "draft" | "send" | no |
status | query | "none" | "pending" | "sent" | "rejected" | "snoozed" | "gone" | no |
q | query | string | no |
since | query | string (date-time) | no |
until | query | string (date-time) | no |
cursor | query | string | no |
limit | query | integer | no |
Responses
200 — A page of the journal.
| Field | Type | Required |
|---|---|---|
items | object[] | yes |
nextCursor | string | null | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"mailboxAddress": { "type": "string" },
"mode": { "type": "string", "enum": [ "draft", "send" ] },
"origin": { "type": "string", "enum": [ "workflow", "webmail", "approval" ] },
"recipients": { "type": "array", "items": { "type": "string" } },
"subject": { "type": "string" },
"createdAt": { "type": "string" },
"reviewStatus": {
"type": "string",
"enum": [ "none", "pending", "sent", "rejected", "snoozed", "gone" ]
},
"reviewReason": { "type": "string" },
"reviewAt": { "type": "string" },
"snoozeUntil": { "type": "string" },
"sendStatus": {
"type": "string",
"enum": [
"pending",
"held",
"running",
"done",
"failed",
"cancelled",
"unknown"
]
},
"sendErrorKind": { "type": "string" },
"executionId": { "type": "string" },
"workflowId": { "type": "string" },
"workflowName": { "type": "string" },
"nodeId": { "type": "string" },
"nodeType": { "type": "string" },
"messageIdHeader": { "type": "string" },
"messageId": { "type": "string" },
"threadId": { "type": "string" }
},
"required": [
"id",
"mailboxId",
"mailboxAddress",
"mode",
"origin",
"recipients",
"subject",
"createdAt",
"reviewStatus",
"sendStatus"
],
"additionalProperties": false
}
},
"nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
},
"required": [ "items", "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 — review.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/sending/journal.csv
Export the sending journal as CSV
The same filters, without cursor or limit: the whole filtered result, streamed and capped at fifty thousand rows. Served as an attachment.
Access — Member session or API key with scope review:read.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
mailboxId | query | string | no |
workflowId | query | string | no |
mode | query | "draft" | "send" | no |
status | query | "none" | "pending" | "sent" | "rejected" | "snoozed" | "gone" | no |
q | query | string | no |
since | query | string (date-time) | no |
until | query | string (date-time) | no |
Responses
200 — The CSV file.
Content type : text/csv
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 — review.bad_request. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.