List messages with cursor pagination

Search Documentation

Search across all developer documentation

messages

List messages with cursor pagination

GET /v1/messages
Auth required api.messages.view

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<uuid> optional

Filter by channel UUID. Malformed values return 422 validation_failed.

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).

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.

200 Cursor-paginated list of messages application/json
data array<object> required

Each entry in data:

id string required
channel string optional
template string optional
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
401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
422 Query parameter or path value failed validation application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json