---
title: "Read send status"
method: GET
path: "/products/notify/sends/{sendId}/status"
tags: ["notify"]
---

# Read send status

`GET /products/notify/sends/{sendId}/status`

Per-recipient delivery state for one send. `delivered` means a channel transport accepted the message; proof a human acted is a receipt.

## Path parameters

- `sendId` string, required

## Response `200`

Send status.

- object
  - `data` NotifySendStatus, required
    - `send_id` string, required
    - `status` 'unknown' | 'pending' | 'sending' | 'delivered' | 'partial' | 'failed', required — DERIVED, never stored — and derived by the SAME function the send-history list and the status rollup both call, so the two surfaces can never disagree about what a state means. unknown = no audience denominator (a row written before the counters existed); pending = accepted, nothing handed to delivery yet; sending = in flight; delivered = every recipient reached a channel that accepted; partial = every recipient resolved, some delivered and some not; failed = every recipient resolved and none delivered. The list feeds it the send row's best-effort counters; the status view recomputes them from the authoritative per-recipient rows, so the list can lag but cannot contradict. Use the per-recipient `state` for why a given recipient did not get it.
    - `channels` string[]
    - `created_at` integer
    - `title` string, nullable
    - `category` string, nullable
    - `type` string, nullable
    - `recipients` NotifyStatusRecipient[], required
      - `recipient_id` string, required
      - `state` 'pending' | 'delivered' | 'undeliverable' | 'failed' | 'suppressed' | 'rate_limited', required — The one answer for this recipient, derived from the row. pending = still in flight. delivered = a channel accepted it and no provider has repudiated that. undeliverable = every channel attempted declined (no handle, bounced, not implemented). failed = a channel exhausted its retries; our transport gave up, the address may be fine. suppressed = the PERSON chose silence (global off, category mute, every offered channel opted out, or they unsubscribed the address the send was made to) — not a fault. rate_limited = skipped by the per-recipient abuse ceiling for that minute.
      - `enqueued` boolean, required — Fan-out accepted this recipient and enqueued the first delivery step.
      - `seen_at` integer, nullable
      - `read_at` integer, nullable
      - `archived_at` integer, nullable
      - `suppressed_at` integer, nullable — When the recipient was taken out of the ladder. NOT always an opt-out — read `suppressed_reason` to tell an opt-out from an unreachable address, or read `state`, which already separates them.
      - `suppressed_reason` 'no_channels' | 'unreachable' | 'global_off' | 'category_muted' | 'all_channels_opted_out' | 'identifier_unsubscribed' | 'null', nullable — Why, as a fixed token (never a transport string, so nothing recipient-identifying rides it). `no_channels` (the send offered no channel at all) and `unreachable` (every offered channel lacks a handle for them) both roll up to state "undeliverable". `global_off`, `category_muted`, `all_channels_opted_out` and `identifier_unsubscribed` all roll up to state "suppressed".
      - `rate_limited_at` integer, nullable — When the recipient was skipped by the per-recipient ceiling. Cleared if a later fan-out drives them.
      - `channel_outcomes` object, required — Latest outcome per channel. A channel is keyed once: a later attempt overwrites the earlier one. Provider detail strings are deliberately withheld — they can echo the recipient contact.
      - `delivered` boolean, required — At least one channel transport ACCEPTED the message. It is not proof a human saw it — that is seen_at/read_at, and for a ceremony, a receipt.
      - `undeliverable` boolean, required — Something was attempted, nothing delivered, and every recorded outcome declined (skipped, not_implemented, or bounced). A still-retrying recipient is neither delivered nor undeliverable.
      - `failed` boolean, required — Every attempt is terminal, nothing delivered, and at least one channel exhausted its retries.
    - `counts` object, required — The six state counters PARTITION the audience: pending + delivered + undeliverable + failed + suppressed + rate_limited === total. enqueued/seen/read/archived are independent progress markers and do not partition anything.
      - `total` integer, required — Recipients on the send, not rows returned.
      - `enqueued` integer, required
      - `seen` integer, required
      - `read` integer, required
      - `archived` integer, required
      - `pending` integer, required
      - `delivered` integer, required
      - `undeliverable` integer, required
      - `failed` integer, required
      - `suppressed` integer, required
      - `rate_limited` integer, required
  - `meta` ResponseMeta — Envelope metadata present on every successful response.
    - `api_version` string
    - `request_id` string
    - `response_ms` integer
    - `timestamp` integer — Unix seconds.
    - `pools` object — Storage and bandwidth capacity snapshot for the account.
    - `billing` object
      - `past_due` boolean
    - `quota` object — This minute of the account request budget. Present on authenticated success responses that passed the throttle; absent otherwise.
      - `limit` integer
      - `used` integer
      - `remaining` integer
      - `resets_at` integer — Unix seconds.

## Other responses

- `400` — VALIDATION_ERROR — the message names the offending field.
- `401` — AUTHENTICATION_ERROR — missing or invalid credentials.
- `403` — FORBIDDEN — the caller may not act on this account.
- `404` — NOT_FOUND — no such send on this account.
- `429` — RATE_LIMIT_EXCEEDED — the account exceeded 300 requests this minute. `Retry-After` carries the seconds to wait, and `details` carries limit/resets_at, plus `used` whenever the request was counted. A request refused because the limiter itself was unavailable reports no `used`.

---

[API](https://skmtc.dev/sparkvault/apis/sparkvault-api.md) · [All operations](https://skmtc.dev/sparkvault/apis/sparkvault-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/sparkvault/sparkvault-api/revisions/aaf3fd4981d9/schema)
