Skip to content
InfiQ glossary · Reference sheetReliability & scale

Dead-Letter Queue(DLQ)

A dead-letter queue (DLQ) holds messages that exhausted their retries or failed validation, so they can be inspected, fixed and redriven later.

Properties

Term
Dead-Letter Queue
Abbreviation
DLQ
Level
Practitioner
Audience
Developers, Support teams
Reviewed
Also called
Dead letter queue · Dead-letter exchange · Error queue · Parking-lot queue

01 · Definition

What is a dead-letter queue (DLQ)?

A dead-letter queue (DLQ) is a separate queue where a messaging system puts the messages it has given up on: jobs that failed every retry, payloads that failed validation, or messages that expired or exceeded a delivery limit. Moving them aside keeps the main Message Queue flowing — a broken message no longer sits at the head blocking workers or looping through retries — while keeping the message itself, so nothing is silently lost.

Most brokers support it natively. A queue can carry a redrive policy (move a message after N receives) or a dead-letter exchange (route messages that are rejected without requeue, expire or overflow), and job frameworks keep a failed set with the last error. In a WhatsApp pipeline the typical residents are sends that failed with permanent errors such as Error 132000 (Parameter Count Mismatch) or 132001 (template does not exist), sends whose retry budget ran out during a long throughput incident, and webhook events your code could not parse.

The DLQ is the parking lot; a Poison Message is the kind of payload that typically ends up there, failing its consumer every time. A DLQ is only useful with a process around it: an alert when it fills, enough context stored with each message to diagnose it, retention long enough to act, and a redrive path that sends fixed messages back to the main queue — at a controlled rate and only into consumers protected by an Idempotency Key, so replays do not double-send.

Key takeaways

  • A dead-letter queue holds messages that cannot be processed — retries exhausted or permanently invalid — so they stop blocking the main queue.
  • Send permanent errors straight to the DLQ: a 132000 parameter mismatch will fail on every retry.
  • Store the original payload with the error code, attempt count, timestamps and trace ID, or triage becomes guesswork.
  • Alert when DLQ depth rises above zero, give every DLQ an owner, and keep messages longer than your triage takes.
  • Redrive only after fixing the cause, at a controlled rate, into idempotent consumers so replays cannot double-send.

See InfiQ pricing

02 · Specifications

A dead-letter queue (DLQ): key facts and limits

PurposePark unprocessable messages without blocking the main queue
Typical delivery limit3–10 attempts (illustrative)
Dead-letter at once132000, 132001, 131051, malformed payloads
Retry firstTimeouts, HTTP 5xx, 130429, 131000
RetentionLonger than the source queue and your triage time
AlertDepth above zero, or a sudden rise
RedriveAfter the fix, rate-limited, into idempotent consumers

03 · Code samples

A dead-letter queue (DLQ) in code

import amqp from "amqplib";
const conn = await amqp.connect(process.env.AMQP_URL);
const ch = await conn.createChannel();
// The dead-letter exchange and the DLQ it routes to
await ch.assertExchange("wa.dlx", "direct", { durable: true });
await ch.assertQueue("wa.send.dlq", { durable: true });
await ch.bindQueue("wa.send.dlq", "wa.dlx", "wa.send");
// Main queue: messages over the delivery limit are dead-lettered to wa.dlx
await ch.assertQueue("wa.send", {
durable: true,
deadLetterExchange: "wa.dlx",
deadLetterRoutingKey: "wa.send",
arguments: { "x-queue-type": "quorum", "x-delivery-limit": 5 },
});
const PERMANENT = new Set([100, 131051, 132000, 132001]); // fail identically on every retry
ch.consume("wa.send", async (msg) => {
try {
await sendTemplate(JSON.parse(msg.content.toString()));
ch.ack(msg);
} catch (err) {
if (PERMANENT.has(err.code)) {
// Dead-letter now, with the context triage needs, then ack the original
ch.publish("wa.dlx", "wa.send", msg.content, {
persistent: true,
headers: { "x-error-code": err.code, "x-error": err.message, "x-failed-at": new Date().toISOString() },
});
ch.ack(msg);
} else {
ch.nack(msg, false, true); // transient: requeue (add a delay in production);
} // after 5 deliveries the broker dead-letters it
}
});

Permanent errors are dead-lettered on the first failure with context; transient ones retry until the delivery limit moves them.

04Playground

A working model of DLQ. Use its controls to replay it step by step.

Follow a failing send into the DLQ and back

11 / 11
Order serviceenqueues sends
wa.sendmain queue
Send workerclassifies errors
Cloud APIPOST /messages
Retry with backofftransient errors
Dead-letter queuewa.send.dlq
Alert & triagegroup by error code
Fix & redriverate-limited replay
  • Order service to wa.send: enqueue
  • wa.send to Send worker: consume
  • Send worker to Cloud API: send
  • Send worker to Retry with backoff: 5xx / timeout
  • Retry with backoff to wa.send: attempt + 1
  • Send worker to Dead-letter queue: 5th failure or 132000
  • Dead-letter queue to Alert & triage: depth > 0 alert
  • Alert & triage to Fix & redrive: fix template
  • Fix & redrive to wa.send: redrive

Hop 11 of 11

Send workerCloud APIsend

Arrives at Cloud API — POST /messages.

A send fails, retries with backoff, exhausts its attempts and lands in the dead-letter queue. Step through the alert, the fix and the rate-limited redrive that finally delivers it.

Hop 11 of 11: Send worker to Cloud API — send.

05 · Operation

How does a dead-letter queue (DLQ) work?

  1. Define what counts as dead

    Permanent errors — invalid parameters, a missing template, an unsupported message type — go to the DLQ at once. Transient errors such as timeouts, 5xx and 130429 retry under your Retry Policy with exponential backoff and Backoff Jitter, and reach the DLQ only when the retry budget is spent.

  2. Wire the DLQ into the broker

    Configure a delivery limit or redrive policy (for example 5 attempts) and a dead-letter target on the main queue, so the broker moves messages even if a worker crashes mid-job.

  3. Store context with each message

    Keep the original payload plus the last error code and message, attempt count, first and last failure time, worker version and trace ID.

  4. Alert, then triage by error code

    Wire the DLQ into your alerting: fire when its depth rises above zero or grows fast. Group entries by error code and template name to find the single bug behind hundreds of failures.

  5. Fix, then redrive

    Correct the template, data or code, then move messages back to the main queue at a limited rate. Discard, with a record, those that should no longer be sent — an OTP that expired days ago, say.

06 · Cautions

Common mistakes with a dead-letter queue (DLQ)

  • Retrying permanent errors

    A 132000 or a missing template fails identically on every attempt; retrying five times wastes throughput and delays the alert. Classify errors and dead-letter permanent ones on the first failure.

  • A DLQ nobody watches

    Messages in an unmonitored DLQ are lost with extra steps. Alert on depth, assign an owner and review it in a regular triage.

  • Retention shorter than triage

    If the DLQ keeps messages for four days and the team triages weekly, failures expire unseen. On some brokers a message keeps its original enqueue time when dead-lettered, so set DLQ retention well beyond the source queue's.

  • Redriving everything at once

    Replaying 50,000 messages in one burst hits the number's throughput, triggers 130429s and can send stale content. Redrive at a controlled rate and filter out messages that are no longer relevant.

  • Stripping the context

    A DLQ entry with only the payload forces developers to dig through logs. Attach the error code, attempt count, timestamps, worker version and trace ID when dead-lettering.

07 · Worked example: Out-for-delivery updates at a Nagpur courier

Example: Out-for-delivery updates at a Nagpur courier

A courier company in Nagpur sends out-for-delivery utility templates from a queue. After a template edit removes a variable, every send for that template fails with error 132000. The workers dead-letter these on the first failure instead of retrying, and the DLQ-depth alert opens a ticket within five minutes. The DLQ groups 1,420 messages under one error code and one template name; the developer restores the variable, the team discards messages for parcels already delivered, and redrives the remaining 1,100 at 20 per second. Customers get their updates about 40 minutes late instead of not at all.

08 · Rationale

Why does a dead-letter queue (DLQ) matter?

Without a DLQ, failed messages either loop forever, burning workers and throughput, or get dropped — and someone learns weeks later that 3,000 delivery updates never went out. With one, failures become a visible, bounded worklist: support can see which customers were not notified, developers get the exact payload and Meta error code to reproduce the bug, and once it is fixed the messages can be replayed instead of rebuilt by hand.

09 · Recommended practice

Best practices for a dead-letter queue (DLQ)

  • Keep one DLQ per main queue, so ownership and the redrive target are obvious.
  • Build a redrive tool with dry-run, filtering by error code and a rate limit before you need it at 2 am.
  • Chart DLQ arrivals per error code; a new code appearing is often the first sign of a template or API change.
  • Never redrive time-bound messages such as OTPs; record them as failed and let the user request a new one.

10 · Questions and answers

DLQ FAQs

What is the difference between a dead-letter queue and a retry queue?
A retry queue holds messages that will be tried again automatically after a delay; a dead-letter queue holds messages the system has stopped retrying. Messages move to the DLQ when retries are exhausted or the error is permanent.
When should a message go straight to the DLQ?
When the error is permanent and retrying cannot change the outcome. For WhatsApp sends that includes parameter mismatches (132000), a template that does not exist (132001) and malformed payloads; timeouts and 5xx errors should retry first.
How do I redrive messages from a DLQ safely?
Fix the cause first, then replay into the main queue at a controlled rate with idempotent consumers. Filter out messages that are no longer relevant, such as expired OTPs, and watch the main queue's error rate during the redrive.
How long should messages stay in a DLQ?
Longer than your slowest realistic triage — typically one to two weeks. Check whether your broker measures expiry from the original enqueue time, and archive anything you must keep for audit before it expires.

Put DLQ to work

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.