Back to blog
    Engineering Ops

    A Dead Letter Queue Needs Owners, Alerts and a Redrive Rule

    Written by:OperateOperate TeamUpdated 11 min read

    A dead letter queue only prevents data loss if someone watches it. The metrics, alert thresholds, failure triage and safe redrive steps that keep it honest.

    A Dead Letter Queue Needs Owners, Alerts and a Redrive Rule

    Quick Answer

    Monitoring, triage and a redrive rule turn a dead letter queue from a graveyard into a safety net. Alert on depth and on the age of the oldest message against the retention window. Sort failures by class before acting, fix the cause before any redrive, and replay in small, audited batches to idempotent consumers. Set retention longer than the source queue's, because some brokers keep the original enqueue time.

    A dead letter queue (DLQ) is where a message broker puts messages that could not be delivered or processed after a set number of attempts. It keeps one bad message from blocking everything behind it. We think of it as a holding area for failures that still need a decision.

    Every major broker makes a DLQ easy to switch on, and most advice on dead letter queue best practices stops at configuration. Operating one is a different job. In many systems the DLQ is write-only, so messages land, nobody looks, and the retention window quietly deletes them weeks before a customer notices.

    In this post we cover why an unwatched DLQ loses data, the four metrics that make it observable, how to triage failures by class, a redrive protocol, how retention and redrive differ across brokers, and who should own it.

    A Dead Letter Queue Without an Owner Becomes Silent Data Loss

    A DLQ prevents a failing message from stalling its consumer. It does not prevent the loss of that message. Every message in it is a promise to a user that the system has not kept yet.

    The clock is also shorter than it looks. In an SQS dead letter queue fed by a standard queue, a message keeps its original enqueue timestamp, so time already spent in the source queue counts against the DLQ's retention.

    AWS's dead-letter queue guide gives the example of a message that spent one day in the source queue being deleted from a four-day DLQ after three days, and recommends setting DLQ retention longer than the source queue's.

    Other brokers fail the opposite way. Microsoft's documentation for the Azure Service Bus dead letter queue says "There's no automatic cleanup of the DLQ." Messages stay until someone retrieves and completes them, so an unwatched queue grows without limit instead of expiring.

    Both failure modes are silent, because nothing inside the consumer reports them. We wrote about catching this class of failure from the outside in you cannot instrument a silent failure from inside.

    Four Metrics That Make a Dead Letter Queue Observable

    A single "DLQ is not empty" alarm fires constantly or never, depending on the system. We instrument four signals instead, because each one answers a different question about the failures.

    1. Depth

    Depth is how many messages are waiting. For SQS, AWS recommends ApproximateNumberOfMessagesVisible as the metric for monitoring a DLQ. The rate of change matters more than the number, since a sudden jump usually means a deploy or a downstream outage.

    2. Age of the Oldest Message

    Age is the metric that predicts data loss. We page when the oldest message has used about half of its retention window, so someone has days, not hours, to act.

    On SQS this needs care. The CloudWatch metrics reference notes that ApproximateAgeOfOldestMessage on a DLQ reflects when the message moved there, not when it was first sent. For standard queues the expiry still counts from the original send, so we add the source queue's processing time to the reported age.

    3. Arrival Rate

    Arrival rate separates background noise from an incident. A steady trickle from a flaky third-party API may be acceptable, while a vertical line after a deploy is not.

    The trap here is that the obvious SQS metric does not measure it. NumberOfMessagesSent excludes messages that SQS moves to a DLQ automatically after maxReceiveCount, so an alarm on it stays quiet during the exact event it was meant to catch. We derive arrivals from the change in visible depth instead, which works while nothing consumes from the DLQ.

    4. Redrive Return Rate

    After a replay, we track how many messages come back to the DLQ. A high return rate means the cause was not fixed, and the replay only spent another round of retries.

    Metric Question it answers Alert we set
    Depth How much is waiting Ticket above a baseline, page on a sharp jump
    Oldest message age How close we are to losing data Page at about half the retention window
    Arrival rate Is this noise or an incident Page when arrivals spike after a deploy
    Redrive return rate Did the fix work Stop the replay if returns climb

    These thresholds are starting points rather than rules. We tune them per queue, because a payments queue and an analytics queue tolerate very different delays.

    Sort Dead Letters by Failure Class Before Acting

    Treating every dead letter the same is the fastest way to make things worse. We group failures by class first, because the right action differs completely between them.

    Failure class How we recognize it Safe to redrive
    Malformed payload Validation or parse error on every attempt Never without a transformation step
    Downstream unavailable Timeouts or 5xx from a dependency, clustered in time Yes, once the dependency recovers
    Schema drift Producer changed a field the consumer expects Only after the consumer fix ships
    Business-rule violation Valid message, impossible action, such as refunding a refunded order Rarely, it usually needs a person
    Expired or over-retried TTL or delivery-count reason on the message Depends on why the consumer was slow

    The broker usually tells us the class if we read the metadata. Azure Service Bus sets DeadLetterReason and DeadLetterErrorDescription, with system reasons such as MaxDeliveryCountExceeded and TTLExpiredException. RabbitMQ adds an x-death header with the queue, reason, count and original routing keys.

    The class tells us what failed, not why, and finding why is root cause analysis. Matching a spike of dead letters to the deploy behind it by hand is slow, so it helps to connect an investigator read-only to your logs and database and let it do the matching.

    The Redrive Protocol Starts With the Root Cause

    The most common DLQ question we hear is how to clear one. Deleting the messages is almost never the answer, because it destroys the evidence and the work at once. We follow four steps instead.

    1. Confirm the Fix Has Shipped

    We redrive only after the root cause is fixed and deployed. Replaying malformed or schema-drifted messages into an unchanged consumer just sends them back to the DLQ with one less chance of being noticed.

    2. Record Why You Are Replaying

    Every replay should leave a trail of who did it, when and why. Notion described building a DLQ Explorer after a long period when the only access was the AWS CLI with direct S3 access. In their tool, "a confirmation modal requires a justification reason before the operation proceeds", and re-enqueue history is tracked.

    3. Replay a Small Batch to an Idempotent Consumer

    We start with a handful of messages and watch them complete. SQS supports this with a custom redrive velocity of up to 500 messages per second, and AWS recommends starting low and ramping up.

    The consumer must be idempotent, because some of these messages may have partly succeeded the first time. We covered why in exactly-once is a property of the write.

    4. Watch the Return Rate

    If replayed messages start returning to the DLQ, we stop the task and go back to step one. On SQS a redrive cannot filter or modify messages, so a mixed batch needs sorting before it is replayed.

    Retention and Redrive Differ Across Brokers

    Each broker implements dead-lettering differently, and the differences decide how much time we have. This is the comparison we keep at hand.

    Broker What the DLQ is Retention behavior How to redrive
    Amazon SQS A separate queue named in the source queue's redrive policy 60 seconds to 14 days, default 4 days, and standard queues keep the original enqueue time Console or StartMessageMoveTask, back to the source queue or a custom destination
    Azure Service Bus A built-in $deadletterqueue subqueue per queue or subscription Time-to-live is not observed and there is no automatic cleanup Service Bus Explorer resend, or receive and resubmit in code
    RabbitMQ A dead letter exchange that routes to any queue Whatever the target queue's own limits are Consume from the target queue and republish
    Apache Kafka No broker feature, a convention of routing failures to another topic The DLQ topic's own retention settings A consumer or script that reads and republishes

    Two details in that table bite people. RabbitMQ's documentation warns that dead-lettered messages are re-published without publisher confirms, so default dead-lettering in a cluster is not guaranteed safe, while quorum queues offer at-least-once dead-lettering. In Kafka Connect, Confluent documents that DLQs apply only to sink connectors, enabled with errors.tolerance = all and errors.deadletterqueue.topic.name.

    For Kafka specifically, we would also watch consumer lag on the DLQ topic, which we covered in low CPU, no errors, rising lag.

    When a Dead Letter Queue Is the Wrong Tool

    We like DLQs, but there are two cases where we would not add one.

    • Data That Goes Stale in Seconds

    Live location updates, presence pings and price ticks are useless a few seconds later. Parking them in a DLQ fills a queue with data nobody should ever replay, so we drop and count them instead.

    • Work Where Order Matters

    AWS advises against a DLQ on a FIFO queue when breaking the exact order would change the meaning, with the example of edit instructions for a video editor. Pulling one message aside lets later ones run without it. For ordered work we would rather block the group and alert, or keep the failure in the stream with its position, as the outbox pattern on day 2 discusses.

    Who Owns the Dead Letter Queue and How Often They Look

    A DLQ that belongs to everyone belongs to no one. We assign it to the team that owns the consumer, because that team can fix what the messages reveal.

    • Name one owning team per DLQ in the service catalog, next to the consumer.
    • Triage on a fixed cadence, such as twice a week, with the goal of driving depth back to zero.
    • Page only on age and on sharp arrival spikes, and route steady low-volume failures to a ticket or a chat channel.
    • Review replay history in the same meeting, so repeated redrives of one failure class become a bug ticket.

    We have found the cadence matters more than the tooling. A team that looks twice a week rarely loses a message to retention.

    Make the Dead Letter Queue Somebody's Job This Week

    We would start small. Pick the queue whose failures cost the most, check its retention against the source queue, and add an age alert at half the window.

    A dead letter queue only protects data when someone owns it, watches age and arrivals, and replays with a reason. When a spike does need investigating, Operate can help. It reads your logs, databases and code through read-only adapters to find why messages failed, and proposes any code fix as a patch file while the redrive decision stays with your engineers.

    Frequently Asked Questions

    A poison message is one that fails every time it is processed, often crashing or hanging the consumer. The dead-letter queue is where such messages end up after the broker gives up. We use "poison queue" loosely for that destination, but the DLQ also holds expired and undeliverable messages.

    It keeps messages that cannot be processed so someone can inspect, correct and reprocess them later, while the main queue keeps moving. We see it as isolation plus evidence. Without it, one bad message either blocks the consumer or disappears after its retries run out.

    The broker counts delivery attempts and moves a message aside once it passes a limit or expires. We usually start from the defaults, a maxReceiveCount of 10 in the SQS API and a maximum delivery count of 10 in Azure Service Bus, then tune per queue based on how transient failures really are.

    Not as a broker feature. We build it as a convention, where a consumer that cannot process a record writes it to a separate topic with error details in headers. Kafka Connect offers this for sink connectors through configuration, and the DLQ topic needs its own retention setting.

    We clear one by fixing the cause and redriving, then deleting only what is truly unrecoverable. On SQS, PurgeQueue deletes messages permanently and takes up to 60 seconds. On Azure Service Bus, each message is removed by receiving and completing it, so we export anything worth keeping first.

    About the author

    Operate

    Operate Team

    The team behind Operate

    Operate Team builds Operate, enterprise AI agents for production operations. Operate answers production questions from Slack and Microsoft Teams, investigates alerts, and reviews performance, security and cost. It finds the root cause with evidence and proposes a fix for an engineer to review, with controlled access to your systems.

    Share: X LinkedIn
    #SRE
    #Observability
    #Cloud Infrastructure
    #Distributed Systems

    Keep reading