List messages with cursor pagination
Returns one cursor-paginated page of message summaries, aggregated to
one row per message. Results are ordered newest-first by sent_at.
When from and to are omitted the window defaults to the trailing
30 days; a from older than your plan's retention is rejected with
403 plan_retention_exceeded (the response carries a retention_days
extension), it is not silently clamped. Use this for dashboards,
reconciliation, and incremental export — page forward with the
next_cursor from the response (or follow the Link: <…>; rel="next"
header) until has_more is false.
Filters compose with AND. The recipient filter is PII-gated: passing
it without the api.messages.unmask_recipients scope returns
403 invalid_scope, not a silently-dropped filter.
PII: every message in the response carries its recipient's full
email address in to. api.messages.view alone is enough to read it —
addresses are never masked or truncated, and
api.messages.unmask_recipients governs searching by a recipient,
not seeing one. Treat a page of this endpoint as personal data.
Query parameters
limit integer optional Page size (1–200). Default 50.
cursor string optional Opaque cursor returned from the previous page.
from string<date-time> optional Window start (RFC 3339). Defaults to 30 days ago.
to string<date-time> optional Window end (RFC 3339). Defaults to now.
channel string optional Filter by channel — its UUID, or the channel's name, which is the handle GET /v1/channels returns and GET /v1/channels/{name} is keyed on. A value that names no channel in the org returns 422 validation_failed rather than an empty page, so a typo reads as a typo instead of as a channel with no traffic.
status string enum optional Filter by current message status. Values are SES event-type names
(capitalized): Send, Delivery, Bounce, Complaint, Reject,
Open, Click, DeliveryDelay. Applied as an exact match on the
terminal status of each message.
template string optional Filter by exact template name (the SES template slug, not a display name).
source_type string enum optional Only messages SendOps sent from a drip workflow (workflow), a
broadcast (broadcast) or a broadcast test send
(broadcast_test), or the onboarding test email
(onboarding_test) or deliverability probe
(deliverability_probe). Mail your own application sends through
the same SES account has no source and never matches.
source_id string<uuid> optional Only messages sent by this workflow or broadcast, by id.
identity string optional Filter by sending identity. Accepts a full email address (the local-part is ignored — only the domain matches) or a bare domain.
recipient string<email> optional api.messages.unmask_recipients Filter by exact recipient address (case-sensitive). Requires the
api.messages.unmask_recipients scope — calls without that scope
return 403 invalid_scope. The scope gates looking a specific
person up; it does not change what the response contains, which
includes full recipient addresses either way.
Responses
Errors follow the RFC 7807 problem format — see the error reference.
data array<object> required Each entry in data:
id string required channel string optional template string optional The SES template name the message was sent with — the value the
template filter matches. Absent when the message was not sent
from a SendOps template.
template_id string<uuid> optional The SendOps template template belongs to. Absent when no current template has that SES name.
template_name string optional That template's display name.
source object optional What in SendOps sent the message. Absent for mail your own application sent through the same SES account, and for messages sent before SendOps recorded sources.
Fields of source:
type string enum required One of: workflow, broadcast, broadcast_test
id string<uuid> required The workflow or broadcast id.
name string | null required The workflow's or broadcast's name; null once it has been deleted.
from string required to string required The recipient's full email address, in the clear. It is never
masked or truncated, and api.messages.view alone is enough to
read it — api.messages.unmask_recipients gates searching by a
recipient, not seeing one. This field is personal data.
subject string optional sent_at string<date-time> required status string required last_event_at string<date-time> optional pagination object required Fields of pagination:
has_more boolean required next_cursor string | null optional validation_failed Problem. When the refusal is about a
reference — a segment key, template slug, topic or attribute name the
organization does not have — it additionally carries validation_code,
field, line/column, missing, candidates and next_step, so a
client can correct the call without a second round of guessing. See
ValidationProblem.
application/problem+json code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json