Deliveries
Every send attempt writes a delivery record: sent → delivered / bounced / complained (status precedence means a late delivered never overwrites bounced, and a complaint outranks a bounce), failed, a typed skipped_*, or a dry-run would_*. A webhook’s 2xx is its delivery confirmation, so it is terminal at delivered. The public endpoints are what SNS (carrying SES’s event stream) and mail clients (RFC 8058 one-click unsubscribe) call: they verify signatures/tokens instead of sessions and answer per the caller’s convention, not the envelope. The address-verification pages are on the Emails page.
GET /v1/deliveries
Section titled “GET /v1/deliveries”List deliveries, newest first
Auth: Clerk session token, or an org deploy key.
Query parameters
{ "cursor": "string", "limit": 50, "direction": "desc", "status": "sent", "journey": "string", "channel": "email", "appId": "string", "profileId": "string", "since": "2026-01-15T09:30:00.000Z"}| Field | Type | Required | Constraints |
|---|---|---|---|
cursor |
string | no | |
limit |
integer | no | default 50; 1 ≤ x ≤ 100 |
direction |
“asc” | “desc” | no | default "desc" |
status |
“sent” | “delivered” | “bounced” | “complained” | “failed” | “skipped_disabled” | “skipped_paused” | “skipped_suppressed” | “skipped_quota” | “skipped_frequency_cap” | “skipped_consent” | “skipped_domain” | “skipped_ssrf” | “would_send” | “would_skip_disabled” | “would_skip_paused” | “would_skip_suppressed” | “would_skip_quota” | “would_skip_frequency_cap” | “would_skip_consent” | “would_skip_domain” | “would_skip_ssrf” | no | |
journey |
string | no | length 1–200 |
channel |
“email” | “webhook” | no | |
appId |
string | no | length 1–200 |
profileId |
string | no | length 1–200 |
since |
string (date-time) | no |
Response 200
{ "data": [ { "id": "string", "orgId": "string", "environment": "development", "profileId": "string", "journey": "string", "step": "string", "channel": "email", "status": "sent", "dryRun": true, "recipient": "string", "providerId": "string", "error": "string", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z", "sentAt": "2026-01-15T09:30:00.000Z", "settledAt": "2026-01-15T09:30:00.000Z" } ], "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object[] | yes | |
data[] |
object | yes | |
data[].id |
string | yes | |
data[].orgId |
string | yes | |
data[].environment |
“development” | “production” | yes | |
data[].profileId |
string | yes | |
data[].journey |
string | yes | |
data[].step |
string | yes | |
data[].channel |
“email” | “webhook” | yes | |
data[].status |
“sent” | “delivered” | “bounced” | “complained” | “failed” | “skipped_disabled” | “skipped_paused” | “skipped_suppressed” | “skipped_quota” | “skipped_frequency_cap” | “skipped_consent” | “skipped_domain” | “skipped_ssrf” | “would_send” | “would_skip_disabled” | “would_skip_paused” | “would_skip_suppressed” | “would_skip_quota” | “would_skip_frequency_cap” | “would_skip_consent” | “would_skip_domain” | “would_skip_ssrf” | yes | |
data[].dryRun |
boolean | yes | |
data[].recipient |
string | null | yes | |
data[].providerId |
string | null | yes | |
data[].error |
string | null | yes | |
data[].createdAt |
string (date-time) | yes | |
data[].updatedAt |
string (date-time) | yes | |
data[].sentAt |
string (date-time) | null | yes | |
data[].settledAt |
string (date-time) | null | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
GET /v1/deliveries/{id}
Section titled “GET /v1/deliveries/{id}”Fetch one delivery with its payload
Auth: Clerk session token, or an org deploy key.
Response 200
{ "data": { "id": "string", "orgId": "string", "environment": "development", "profileId": "string", "journey": "string", "step": "string", "channel": "email", "status": "sent", "dryRun": true, "recipient": "string", "providerId": "string", "error": "string", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z", "sentAt": "2026-01-15T09:30:00.000Z", "settledAt": "2026-01-15T09:30:00.000Z", "payload": { "rendered": { "subject": "string", "html": "string", "text": "string" } } }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.id |
string | yes | |
data.orgId |
string | yes | |
data.environment |
“development” | “production” | yes | |
data.profileId |
string | yes | |
data.journey |
string | yes | |
data.step |
string | yes | |
data.channel |
“email” | “webhook” | yes | |
data.status |
“sent” | “delivered” | “bounced” | “complained” | “failed” | “skipped_disabled” | “skipped_paused” | “skipped_suppressed” | “skipped_quota” | “skipped_frequency_cap” | “skipped_consent” | “skipped_domain” | “skipped_ssrf” | “would_send” | “would_skip_disabled” | “would_skip_paused” | “would_skip_suppressed” | “would_skip_quota” | “would_skip_frequency_cap” | “would_skip_consent” | “would_skip_domain” | “would_skip_ssrf” | yes | |
data.dryRun |
boolean | yes | |
data.recipient |
string | null | yes | |
data.providerId |
string | null | yes | |
data.error |
string | null | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
data.sentAt |
string (date-time) | null | yes | |
data.settledAt |
string (date-time) | null | yes | |
data.payload |
record<string, any> | yes | |
data.payload.rendered |
object | no | |
data.payload.rendered.subject |
string | yes | |
data.payload.rendered.html |
string | yes | |
data.payload.rendered.text |
string | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
POST /v1/feedback/ses
Section titled “POST /v1/feedback/ses”SES delivery feedback via SNS (public)
Auth: None: verifies the caller’s own signature/token.
Errors follow the shared envelope and code table.
POST /v1/unsubscribe
Section titled “POST /v1/unsubscribe”One-click unsubscribe (public)
Auth: None: verifies the caller’s own signature/token.
Query parameters
{ "token": "string"}| Field | Type | Required | Constraints |
|---|---|---|---|
token |
string | no |
Errors follow the shared envelope and code table.
GET /v1/unsubscribe
Section titled “GET /v1/unsubscribe”Unsubscribe confirmation page (public)
Auth: None: verifies the caller’s own signature/token.
Query parameters
{ "token": "string"}| Field | Type | Required | Constraints |
|---|---|---|---|
token |
string | no |
Errors follow the shared envelope and code table.