Skip to content
Docs

For your customers

Retries and the DLQ

How failed deliveries are retried, how long they wait in the dead letter queue, and how to send them again.

Servers go down and deploys happen. A message your endpoint couldn't take is retried automatically, then kept for 7 days so nothing is lost while you fix things.

The retry schedule

Each endpoint gets up to three attempts per message:

AttemptWhen
1As soon as the message arrives
23 seconds after the first failure
310 seconds after the second failure

If the third attempt fails too, the message moves to the dead letter queue (DLQ) for that endpoint. Endpoints are independent: one failing endpoint never holds back the others.

What counts as a failure

Any answer other than 2xx, including a redirect, or no complete answer within 10 seconds. Each failed attempt records why:

error_codeMeaning
http_<status>Your server answered that status, e.g. http_500 or http_302.
timeoutNo complete answer within 10 seconds.
connection_refusedNothing accepted the connection.
dns_errorThe host name didn't resolve.
tls_errorThe TLS handshake or certificate check failed.
blocked_targetThe host resolved to a private or reserved address.
otherAny other error.

Delivery statuses

Each message's delivery_status sums up its deliveries to all your endpoints:

StatusMeaning
pendingQueued, no failure yet.
retryingAn attempt failed and attempts remain.
deliveredEvery endpoint answered 2xx.
dead_letteredAt least one endpoint failed all its attempts. The message is in the DLQ.
no_endpointThere was no enabled endpoint to deliver to.

The dead letter queue

Messages in the DLQ keep their payload for 7 days from when they arrived there (expires_at), then are deleted. List them, and their count, with filter[dlq]=true:

Your DLQ
curl -g "https://api.houndsat.com/api/json/inbound_messages?filter[dlq]=true&page[count]=true&fields[inbound_message]=device_id,received_at,dead_lettered_at,expires_at" \
  -H "Authorization: Bearer $TOKEN"

Send a message again

Once your endpoint is fixed, an Owner or Admin retries messages from the portal or the API. A retry sends the message again, with three fresh attempts, to each endpoint that failed it and is still enabled; an endpoint that already accepted the message never gets it twice. Retry up to 500 at once in bulk.

Retry one message
curl -X PATCH https://api.houndsat.com/api/json/inbound_messages/3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2/retry \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{"data": {"type": "inbound_message", "id": "3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2", "attributes": {}}}'
Retry several
curl -X POST https://api.houndsat.com/api/json/inbound_messages/bulk/retry \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{"data": {"ids": ["3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2", "8e4a2c1f-0b7d-4f39-a6e5-c2d1b9f0e3a7"]}}'

A retry is refused with 422 retry_refused, and meta.reason says why:

meta.reasonWhy
expiredThe 7 days have passed.
not_in_dlqThe message isn't in the DLQ (it was delivered, or is still being retried).
no_enabled_endpointThe endpoint that failed it is disabled or deleted. Enable it, then retry.
still_runningIts last delivery is still finishing. Try again in a moment.
Refused retry422
{
  "errors": [
    {
      "status": "422",
      "code": "retry_refused",
      "detail": "This message expired on Oct 12, 2026 14:03 UTC and can no longer be retried.",
      "source": { "pointer": "/data/attributes/expires_at" },
      "meta": { "reason": "expired" }
    }
  ]
}

Attempt history

Every attempt is recorded with its status code, error and duration, which is the quickest way to see why an endpoint is failing:

A message's attempts
curl -g "https://api.houndsat.com/api/json/delivery_attempts?filter[inbound_message_id]=3d9f1a7c-5e2b-4c80-9f64-a1b8e3d0c5f2" \
  -H "Authorization: Bearer $TOKEN"
One attempt
{
  "type": "delivery_attempt",
  "id": "…",
  "attributes": {
    "attempt_number": 2,
    "attempted_at": "2026-10-12T14:03:27Z",
    "succeeded": false,
    "http_status": 503,
    "error_code": "http_503",
    "duration_ms": 412,
    "webhook_endpoint_id": "e2b7d9f0-4c1a-4e63-b8a5-7d0f2c9e1b36",
    …
  }
}

Attempt numbers continue across retries, so a message retried from the DLQ shows attempts 4, 5 and 6.