# Dead Letter Queue (/sdks/csharp/how-to/queues/dead-letter-queue)



<Callout type="info" title="Which to use">
  This page is the task-oriented walkthrough: send a message with DLQ routing configured, exhaust its retries, and consume the diverted message. For the `MaxReceiveCount`/`MaxReceiveQueue` field reference — defaults, edge cases, and how the policy travels with the message — see [Dead Letter Policy](./dead-letter-policy).
</Callout>

## Overview [#overview]

A &#x2A;*dead-letter queue (DLQ)** gives a poison message somewhere to go instead of looping through consumers forever. When a message keeps failing — a malformed payload, a downstream outage, a handler bug — retrying it forever wastes consumer cycles and blocks everything behind it. A DLQ takes that decision out of your hands: past a set number of failed attempts, the broker diverts the message to a separate channel instead of retrying it again.

Routing runs on two settings attached to the message: `MaxReceiveCount` and `MaxReceiveQueue`. Every failed delivery — a missing `AckAsync()`, a reject, or an expired visibility window — increments the receive count; past the threshold, the broker reroutes the message to the DLQ instead of redelivering it. The DLQ itself is an ordinary queue, consumed like any other channel.

**Gotchas:** the DLQ doesn't drain itself — a dedicated consumer must watch it. The count increments on *any* failed delivery, not just deliberate rejections — a slow consumer that lets the visibility window lapse counts the same as an explicit nack. A typo in the DLQ channel name quietly creates an unrelated channel instead of failing loudly.

## Prerequisites [#prerequisites]

* KubeMQ server running on `localhost:50000`
* C# SDK installed (`dotnet add package KubeMQ.SDK.CSharp`)

## Code [#code]

```csharp title="Program.cs"
// KubeMQ .NET SDK — Queues: Dead Letter Queue
//
// This example demonstrates configuring a dead letter queue (DLQ).
// After MaxReceiveCount failed attempts, the message moves to the DLQ channel.
//
// Prerequisites:
//   - KubeMQ server running on localhost:50000
//   - dotnet run

using KubeMQ.Sdk.Client;
using KubeMQ.Sdk.Queues;
using System.Text;

await using var client = new KubeMQClient(new KubeMQClientOptions
{
    ClientId = "csharp-queues-dead-letter-queue-client",
});
await client.ConnectAsync();

Console.WriteLine("Connected to KubeMQ server");

// Send a message with DLQ configuration
await client.SendQueueMessageAsync(new QueueMessage
{
    Channel = "csharp-queues.dead-letter-queue-source",
    Body = Encoding.UTF8.GetBytes("Order that will fail processing"),
    MaxReceiveCount = 3,
    MaxReceiveQueue = "csharp-queues.dead-letter-queue-destination"
});

Console.WriteLine("Sent message with MaxReceiveCount=3 and DLQ configured");
Console.WriteLine("After 3 failed receive attempts, the message moves to 'csharp-queues.dead-letter-queue-destination'");

// Poll the DLQ for failed messages with manual ack (AutoAck=false)
await using var receiver = await client.CreateQueueDownstreamReceiverAsync();
var dlqBatch = await receiver.PollAsync(new QueuePollRequest
{
    Channel = "csharp-queues.dead-letter-queue-destination",
    MaxMessages = 10,
    WaitTimeoutSeconds = 5,
    AutoAck = false
});

if (dlqBatch.HasMessages)
{
    foreach (var msg in dlqBatch.Messages)
    {
        Console.WriteLine($"DLQ message: {Encoding.UTF8.GetString(msg.Body.Span)}");
        await msg.AckAsync();
    }
}
else
{
    Console.WriteLine("No messages in DLQ yet (need to exhaust retries first)");
}

Console.WriteLine("Done.");

```

## How It Works [#how-it-works]

* `MaxReceiveCount = 3` on `QueueMessage` configures the broker-side retry limit. Each time a consumer receives and does not acknowledge the message, the count increments. After 3 unacknowledged receives, the broker routes it to `MaxReceiveQueue`.
* `MaxReceiveQueue` is the destination channel for failed messages — a separate queue you can monitor and alert on.
* The DLQ poll uses `AutoAck = false` with per-message `AckAsync()` so that DLQ messages themselves are processed reliably.
* In this example the source message is published but never consumed, so the DLQ will be empty on first run — you need to exhaust the receive count (by receiving and not acking) to see messages appear there.

## Related [#related]

* [Dead Letter Policy](./dead-letter-policy) — field-level reference for `MaxReceiveCount` and `MaxReceiveQueue`
* [Pattern overview](/learn/queues/getting-started)
* [C# SDK Reference](/sdks/csharp/reference)
* [Send & Receive](/sdks/csharp/tutorials/send-receive)
* [Ack All](/sdks/csharp/how-to/queues/ack-all)
