Skip to content

Dead-letter & replay

A delivery that runs out of retries isn’t dropped — it’s dead-lettered: kept, fully inspectable, and replayable on demand. Every send attempt is recorded with its outcome, status code, and timing, so you can see exactly why a delivery failed and re-run it once the cause is fixed.

This is the never silently lost guarantee in practice. A failure is a row you can query, not an event you missed.

List dead-lettered deliveries with the status filter:

Terminal window
curl -sS "https://api.schedstack.com/v1/deliveries?status=dead_letter" \
-H "Authorization: Bearer sk_test_…"
{
"object": "list",
"data": [
{
"id": "dlv_01J9ZK3F8Q",
"object": "delivery",
"schedule_id": "sch_01J9ZK2A7B",
"mode": "test",
"status": "dead_letter",
"scheduled_for": "2026-06-27T09:00:00Z",
"deadline": "2026-06-27T10:00:00Z",
"next_fire_at": null,
"attempt_count": 8,
"last_status_code": 500,
"idempotency_key": "occ_01J9ZK2A7B_1",
"replay_of": null,
"created_at": "2026-06-27T09:00:00Z",
"finalized_at": "2026-06-27T09:42:11Z"
}
],
"has_more": false,
"next_cursor": null
}

Deliveries are returned newest-first. Combine filters to narrow the set:

  • status= — any delivery status: dead_letter, expired, succeeded, scheduled, and so on.
  • schedule_id=sch_… — only deliveries from one schedule.
  • created_after= / created_before= — RFC3339 timestamps to bound the window.
  • cursor= / limit= — cursor pagination. Follow next_cursor while has_more is true (limit defaults to 20; keep it at 100 or below).
Terminal window
curl -sS "https://api.schedstack.com/v1/deliveries?status=dead_letter\
&schedule_id=sch_01J9ZK2A7B\
&created_after=2026-06-27T00:00:00Z" \
-H "Authorization: Bearer sk_test_…"

Fetch a single delivery by id to see its final state and last response code:

Terminal window
curl -sS https://api.schedstack.com/v1/deliveries/dlv_01J9ZK3F8Q \
-H "Authorization: Bearer sk_test_…"

attempt_count tells you how many times SchedStack tried; last_status_code is the code from the final attempt. To see the full trail, list the attempts.

Each delivery records every HTTP attempt. List them oldest-first:

Terminal window
curl -sS https://api.schedstack.com/v1/deliveries/dlv_01J9ZK3F8Q/attempts \
-H "Authorization: Bearer sk_test_…"
{
"object": "list",
"data": [
{
"id": "att_01J9ZK3G10",
"object": "attempt",
"delivery_id": "dlv_01J9ZK3F8Q",
"attempt_no": 1,
"outcome": "retryable",
"status_code": 503,
"fired_at": "2026-06-27T09:00:00Z",
"finished_at": "2026-06-27T09:00:00Z",
"egress_ms": 214,
"error": null
},
{
"id": "att_01J9ZK9H44",
"object": "attempt",
"delivery_id": "dlv_01J9ZK3F8Q",
"attempt_no": 8,
"outcome": "terminal",
"status_code": 500,
"fired_at": "2026-06-27T09:42:10Z",
"finished_at": "2026-06-27T09:42:11Z",
"egress_ms": 1180,
"error": "upstream returned 500"
}
],
"has_more": false,
"next_cursor": null
}

Each attempt’s outcome tells you what SchedStack did next:

  • success — your endpoint returned 2xx. The delivery is marked succeeded; no more attempts.
  • retryable — a 408, 429, or 5xx response, or a transport fault (timeout, connection refused, DNS/TLS error), which SchedStack retried per the schedule’s backoff policy.
  • terminal — no further attempt. Either a non-retryable response — any other 4xx, a 3xx (redirects are disabled), or a blocked/invalid target — which fails immediately on the first attempt, or a retryable failure that ran out of retries or budget (or whose TTL deadline passed). The delivery moved to dead_letter / expired.

status_code is the HTTP status your endpoint returned (null if the request never completed — a timeout or connection failure, with the cause in error). egress_ms is the time SchedStack spent waiting on your endpoint for that attempt.

Replaying creates a new delivery that re-sends the same occurrence, due immediately:

Terminal window
curl -sS -X POST \
https://api.schedstack.com/v1/deliveries/dlv_01J9ZK3F8Q/replay \
-H "Authorization: Bearer sk_test_…" \
-H "Idempotency-Key: $(uuidgen)"

You get back a fresh delivery (201 Created) whose replay_of points at the original. The original is left untouched as the audit record:

{
"id": "dlv_01J9ZM0XYZ",
"object": "delivery",
"schedule_id": "sch_01J9ZK2A7B",
"mode": "test",
"status": "scheduled",
"scheduled_for": "2026-06-27T11:05:00Z",
"next_fire_at": "2026-06-27T11:05:00Z",
"attempt_count": 0,
"last_status_code": null,
"idempotency_key": null,
"replay_of": "dlv_01J9ZK3F8Q",
"created_at": "2026-06-27T11:05:00Z",
"finalized_at": null
}

Replay is allowed only when the original is in a terminal state — dead_letter, expired, succeeded, or canceled. A delivery that is still in flight (scheduled, claimed, retry_scheduled, paused) returns 409 Conflict:

{
"error": {
"type": "invalid_request_error",
"code": "not_replayable",
"message": "Delivery is not in a replayable (terminal) state.",
"request_id": "req_01J9ZM10AB"
}
}
Status Terminal? Replayable? Meaning
scheduled no no Waiting for its fire time.
claimed no no A worker is sending it now.
retry_scheduled no no A previous attempt failed; the next retry is queued.
paused no no Held by a pause; not claimable.
succeeded yes yes Your endpoint returned 2xx.
dead_letter yes yes SchedStack gave up: retries exhausted, retry budget exhausted, or an immediate terminal failure (4xx/3xx/blocked target on attempt 1).
expired yes yes Passed its ttl deadline before landing.
canceled yes yes Canceled before it landed.
  • Retries & backoff — how retry_policy (max_attempts, backoff, ttl) decides when a delivery dead-letters.
  • MCP server — agents can list, inspect, and replay deliveries with the replay_delivery tool, no glue code required.
  • Idempotency — how the outbound Idempotency-Key works, and why a replay (which carries no idempotency_key) is delivered as a new logical request.