List undeliverable recipients (permanent failures + exclusions)

Search Documentation

Search across all developer documentation

undeliverable

List undeliverable recipients (permanent failures + exclusions)

GET /v1/undeliverable
Auth required undeliverable.view

Returns recipients with permanent-failure events (Permanent bounces, Complaints, or Rejects) in your retention window — plus operator-cleared addresses as tombstones (status: excluded) so polling callers can keep their own suppression list in sync.

Classification rules (stable across v1)

A recipient appears on the list with status: "listed" when SendOps has ingested any of the following events for that address in the org's retention window AND no operator exclusion with excluded_at greater than the address's last_seen_at is in effect:

Event Condition Reason
Bounce bounceType = Permanent (any subtype) permanent_bounce
Complaint any complaint
Reject any rejected

Transient bounces, DeliveryDelay, Send/Delivery/Open/Click events never qualify. Refining this classification is a v2 break — callers may write their suppression logic against the rules above and rely on them.

Polling with ?since=

Callers maintaining their own suppression list should poll with ?since=<RFC 3339>, anchored on the largest last_changed_at from their most-recent processed batch. The endpoint returns every row whose last_changed_at >= since, including:

  • newly-listed addresses (recent permanent failures)
  • re-listed addresses (operator-cleared but bounced again)
  • excluded tombstones (operator decided to allow this address)

Apply rows by status: add listed rows to your local suppression list, remove excluded rows from it. The since boundary is inclusive on the second so a one-row overlap is possible — operations are idempotent so this is safe.

Snapshot vs sync default

With no since, the response defaults to status: "listed" only — a clean current-state snapshot. Pass ?since=... to receive both listed and excluded rows. The discrimination is keyed off whether since is set; explicit ?status= overrides the default.

Retention caveat

The listed portion is bounded by your plan's retention window. Exclusion tombstones live in durable storage and surface regardless of how long ago the address last bounced — a caller skipping a poll window will still see the tombstone when they next call with ?since=....

Cursors are opaque base64 — stale or malformed cursors fall back to page 1 silently instead of erroring. The cursor paginates one snapshot; ?since= is the real polling mechanism.

Query parameters

limit integer optional

Page size (1–200). Default 50.

cursor string optional

Opaque cursor returned from the previous page.

since string<date-time> optional

RFC 3339 timestamp. Returns only rows whose last_changed_at >= since. Use the largest last_changed_at from your most-recent processed batch as the next value. The boundary is inclusive on the second.

status string enum optional

Filter by row status. Default depends on ?since=: when since is set, both listed and excluded are returned (so callers see operator-clearing tombstones); when since is absent, only listed rows are returned (snapshot mode). Pass all to override the snapshot default.

reason string enum optional

Filter listed rows by classification reason. Has no effect on excluded tombstones. Accepted values (all six are valid query inputs regardless of whether the corresponding rule is currently enabled for the org — filtering on a disabled rule simply returns no rows): - permanent_bounce — locked, always on - complaint — locked, always on - rejected — locked, always on - repeated_transient — configurable (SND-713) - undetermined — configurable (SND-713) - soft_bounce_accumulation — configurable (SND-713) Callers filtering on a configurable reason should confirm via GET /v1/undeliverable/rules that the rule is enabled before treating an empty page as "no matches" rather than "rule off".

email string optional

Case-insensitive substring match on the recipient address.

min_events integer optional

Only return listed rows whose event_count >= min_events. Useful for callers who want "address that bounced ≥ N times" policies. Default 1 (everything qualifying). Excluded tombstones are dropped when min_events > 1.

Responses

200 Paginated list of undeliverable rows application/json
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