# Dead-Letter Exchanges, TTL and Retry Strategies — RabbitMQ

Source: https://www.skillbyai.com/en/rabbitmq/r-dlx

> Route failed or expired messages to dead-letter exchanges and build delayed retries.

## Where failed messages go

A queue can be configured with a **dead-letter exchange (DLX)**. A message is **dead-lettered**, republished to the DLX with an optional new routing key, when it is **rejected or nacked with requeue=false**, when its **TTL expires**, when messages are dropped from the head of a queue that exceeds its **length limit** (the default `drop-head` overflow behaviour), or, for **quorum queues**, when it exceeds the **delivery limit** (a redelivery count, default 20 since RabbitMQ 4.0). Bind a **dead-letter queue** to the DLX to inspect and replay failures. **Message TTL** (set per queue with the `x-message-ttl` argument or a policy) expires messages that wait too long. A common **delayed retry** pattern combines them: on a transient failure the consumer publishes the message to a **retry queue** that has a TTL (say 30 seconds) and no consumers, then acks the original; the retry queue dead-letters expired messages back to the work queue (for example through the default exchange with the work queue's name as the dead-letter routing key). Count attempts in a header (your own, or the `x-death` header RabbitMQ adds on each dead-lettering) and after N attempts publish to a **parking-lot queue** instead. Keep a quorum delivery limit as a separate guard against messages that crash consumers before they can ack. The community **delayed message exchange** plugin is an alternative but is not recommended for very large volumes of delayed messages.

## Work queue, retry queue and dead-letter queue via policies

Policies apply DLX and TTL settings without changing application code.

```bash
# retry queue: no consumers, 30 s TTL, then back to billing.orders only
# (via the default exchange, so other subscribers of 'orders' never see retries)
rabbitmqctl set_policy -p shop retry-ttl '^billing\.orders\.retry$' \
  '{"message-ttl":30000, "dead-letter-exchange":"", "dead-letter-routing-key":"billing.orders"}' --apply-to queues

# work queue guard: messages that crash consumers repeatedly go to the parking lot
rabbitmqctl set_policy -p shop work-guard '^billing\.orders$' \
  '{"delivery-limit":5, "dead-letter-exchange":"", "dead-letter-routing-key":"billing.orders.parked"}' --apply-to queues

# consumer side (Python): retry or park, then ack the original
# attempts = int((props.headers or {}).get('x-retries', 0))
# target = 'billing.orders.retry' if attempts < 3 else 'billing.orders.parked'
# ch.basic_publish('', target, body, pika.BasicProperties(
#     message_id=props.message_id, delivery_mode=2, headers={'x-retries': attempts + 1}))
# ch.basic_ack(method.delivery_tag)
```

## Always have a parking lot

Retrying forever hides bugs and wastes capacity. After a few attempts, move the message to a parking-lot (dead-letter) queue, alert on its depth and build a simple way to replay messages after a fix.

**Quiz:** Which event does NOT cause a message to be dead-lettered?

- [x] The consumer acknowledges it successfully
- [ ] The consumer nacks it with requeue=false
- [ ] Its message TTL expires
- [ ] A quorum queue delivery limit is exceeded

*Answer:* The consumer acknowledges it successfully. Successful acknowledgement removes the message; the others trigger dead-lettering when a DLX is configured.
