Read back the emails your workspace has sent and their delivery status. These endpoints are read-only — to send, use Send email or Batch send.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/emails | List sent emails (cursor-paginated) |
GET | /api/v1/emails/:id | Get a single email by UUID |
Both endpoints return the same object shape:
| Field | Type | Description |
|---|---|---|
id | string | The email's UUID — the same id POST /api/v1/send returns. |
to | string | Recipient email address. |
subject | string | Subject line. |
status | string | Delivery status: sent, delivered, opened, clicked, failed, complained, bounced, suppressed (a send blocked by the suppression list), or held (an API email waiting for a quick security review; it becomes sent once cleared). |
type | string | Send type: campaign, automation, or transactional. |
environment | string | Which key sent it: live (delivered) or test (captured in the sandbox). See Test mode. |
sent_at | string | ISO 8601 timestamp the email was sent. |
delivered_at | string | null | When the receiving server confirmed delivery, or null. |
opened_at | string | null | When the recipient first opened the email, or null. |
clicked_at | string | null | When the recipient first clicked a tracked link, or null. |
bounced_at | string | null | When the email bounced, or null. |
complained_at | string | null | When the recipient marked the email as spam (a complaint), or null. |
open_tracking_mode | "full" | "essential" | "off" | The consent-aware open-tracking mode this message was sent under. "full" = precise opens + clicks; "essential" = opened_at is a day-granular date only (no time), clicks unaffected; "off" = opens AND clicks not tracked, so opened_at/clicked_at stay null — read them as "not tracked", not "not opened". Legacy rows report "full". See the Open tracking guide. |
error | string | null | The failure reason — present only when status is "failed", otherwise null. |
opened_at/clicked_at against open_tracking_mode. Under essential, opened_at is a day-granular date (midnight UTC) — an open still happened, it just isn't timestamped to the minute. Under off, both stay null because nothing is tracked — that is not the same as "not opened". See the Open tracking guide.Returns a cursor-paginated list of sent emails, newest first. Pass next_cursor from the response back as the cursor parameter to fetch the next page.
| Field | Type | Required | Description |
|---|---|---|---|
limit | number | optional | Max emails to return. Default 50, max 100 (over-large values are clamped). |
cursor | string | optional | Opaque pagination cursor from a previous response's next_cursor. Omit for the first page; a malformed cursor returns 400 invalid_field. |
status | string | optional | Filter by delivery status. One of: sent, delivered, opened, clicked, bounced, complained, failed, suppressed, held. |
type | string | optional | Filter by send type. One of: campaign, automation, transactional. |
environment | string | optional | Filter by send environment. One of: live, test (test-mode captured sends). |
status filters are every status an email can hold — sent, delivered, opened, clicked, bounced, complained, failed, suppressed (a send blocked by the suppression list), and held (an API email waiting for a quick security review). Any other value returns 400 invalid_field.Returns a single email wrapped in { "data": { … } }. The :id is the email's UUID — the same value POST /api/v1/send returns. Returns 404 not_found if the email doesn't exist or belongs to another workspace; a non-UUID id also returns 404.
GET /api/v1/emails/{id}/clicks — every click on a single message, newest first. Requires emails:read.
clicked_at on the email object tells you that something was pressed; this tells you what. It is also the only way to read a transactional message's clicks — transactional mail belongs to no campaign, so the campaign link ranking cannot reach it.
Unsubscribe links are never in data; they are counted in opt_out_clicks and never named, because those URLs carry a live one-click opt-out token.
reconstructed: true marks a click rebuilt from an older record that kept only the most recent click per message, so a run of those is a minimum rather than a count. truncated says the read hit its server-side bound, which no realistic message reaches.
Errors use the standard envelope — unauthorized (401), invalid_field (400, for a bad status/type/cursor), and not_found (404). See the full Error codes reference for the canonical envelope.