BROKABROKA
Sign inDownload CommunityRequest a demo
Documentation112 pages
Guides

Dead-letter flows

Updated 29 September 2026 · applies to 1.0.0 · Commercial

Two different things happen to a message nobody wanted, and conflating them is how an incident goes looking in the wrong queue.

Unroutable is not dead-lettered

A message that matches no binding was never in a queue. It is discarded at the exchange — unless that exchange names an alternate exchange, which catches it.

A message that is dead-lettered was in a queue and left it: rejected, expired, or dropped because the queue was full or a delivery limit was reached. It goes to the queue's dead-letter exchange.

Different mechanism, different configuration, different place to look. The console keeps the vocabulary apart everywhere it appears: a publish that matched nothing is reported as discarded, never as dead-lettered, and a purge says outright that purged messages are not dead-lettered — they do not reach the dead-letter exchange, they are simply gone.

Where a queue's dead-letter target comes from

A dead-letter exchange can be set in three places: in the queue's own arguments when it is declared, centrally by a policy that matches it, or by an operator policy.

A queue's Overview tab for the classic queue orders.audit. The dead-letter exchange row reads "orders.dlx (from policy audit-retention)", the Policy row below it names audit-retention as a link, and Operator policy, Delivery limit, Leader and Members read as em-dashes.
The value states where it came from. A dead-letter target set by a policy and one set by the queue's own arguments look identical until something has to be changed — and then it matters a great deal which one you edit.

BROKA reports the target and where it came from, inline on the queue's own page and on hover in the Queues list, in the order the broker resolves it: an operator policy wins, then the queue's own argument, then a policy. Where a policy also sets it but the queue's argument outranks it, both say that too — so the value shown is the one the broker uses, not merely one that was declared somewhere.

That attribution is the point of the feature. A console that read only the queue's arguments would report "no dead-letter queue" for every centrally-managed queue in the estate, which is exactly backwards: a policy is how a well-run estate sets this, precisely so it does not have to be declared one queue at a time.

Beside it, the queue's page names the policy in force and the operator policy, so the attribution is one line away from the thing that produced it. The policy's name opens the resolver, which shows which other policies matched the queue and lost.

The alternate exchange

The other half of the pair belongs to the exchange rather than the queue, and it is on the Exchanges list.

One caution the console gives you at the moment you set it: the broker accepts an alternate-exchange name without checking that the exchange exists. If it does not, unroutable messages are still discarded — which is the exact outcome the setting was added to prevent, arrived at silently.

Reading a message onto its dead-letter path deliberately

The queue reader's fourth acknowledgement mode is dead-letter (reject), and it does what it says: the messages are dead-lettered where a dead-letter exchange exists, and dropped where it does not.

It is the one read mode that can move messages somewhere else entirely, and it is described that way rather than as a variant of "remove".

Replaying a dead-letter queue

Once whatever made the messages fail is fixed, the dead-letter queue's contents usually need to go back where they came from. Move messages, in the dead-letter queue's header, starts a one-shot shovel that moves the ready messages the queue holds when it starts to another queue or exchange — in any virtual host on the same broker — and removes itself once it has moved them.

Send them to an exchange and leave the routing key blank, and each message is republished under the routing key it carried when it was dead-lettered — so a replay through the original exchange reaches the queues it was meant for, not one fixed destination. The destination must already exist; a move does not create one. Each message is acknowledged on the dead-letter queue only once the destination has confirmed it, so a replay that stops midway leaves the rest where they were.

While it runs, the shovel is listed under Shovels and federation, where deleting it stops the move. It asks for a reason, is recorded in the audit trail, and is not offered on a stream, which has no end to drain to. Where the shovel plugin is not enabled, the action is shown greyed out, with the reason.

The Move messages dialog over the dead-letter queue payments.dlq in Production: a one-shot shovel moving its 7 ready messages into the exchange payments on the vhost broka.dev, the routing key left blank, and a reason typed.
With the routing key blank, each message goes back under the key it was dead-lettered with, so the exchange routes it where it was first meant to go.
← PreviousBindings and routingNext →Message operations