Azure Service Bus .NET SDK Deep Dive – Deadlettering

Explains where messages got when certain conditions are met, for more posts in this series go to Contents.

If we are using queuing, we certainly don’t want to lose messages. After all, those queues decouple the sender from the receiver by persisting the business intent messages and allowing them to handle them whenever the receiver has time. But what if:

  • The receiver doesn’t pick up the message before the message’s time to live is expired (Expiry)
  • The receiver could not successfully handle the message due to processing errors for multiple times (MaxDeliveryCount)
  • The receiver forcefully deadletters a message (Deadletter)

Where should those messages go? At the end of the day, it is probably a business decision what you want to do with those messages, and the answer might vary depending on the message type or the input queue. The good news is though that Azure Service Bus offers dead lettering capabilities. Dead letter queues are dedicated queues that allow messages to be parked there for a period of time until we have a way to deal with those, or we ultimately decide to purge them. The important piece though, is that putting messages to the deadletter queue means they are no longer in the input queue and that is a good thing because they no longer block the receiver from processing other messages in the queue.

Dead letter queues are sub-queues hidden behind a special name called queueName/$DeadLetterQueue. There are more sub-queues per queue, but we will talk about them at a later point in this article series.

To enable deadlettering of messages that expired it is required to opt-in for that when creating the queue by specifying EnableDeadLetteringOnMessageExpiration.

var client = new ManagementClient(connectionString);
var description = new QueueDescription(destination)
    EnableDeadLetteringOnMessageExpiration = true
await client.CreateQueueAsync(description);

Let’s have a closer look at the three scenarios. When sending three message to a destination queue like the following

var client = new QueueClient(connectionString, destination);
// Scenario: Expiry
var message = new Message();
message.Body = Encoding.UTF8.GetBytes("Half life");
message.TimeToLive = TimeSpan.FromSeconds(1);
await client.SendAsync(message);
// Scenario: MaxDeliveryCount
message = new Message();
message.Body = Encoding.UTF8.GetBytes("Delivery Count");
await client.SendAsync(message);
// Scenario: Deadletter
message = new Message();
message.Body = Encoding.UTF8.GetBytes("Poor Soul");
message.UserProperties.Add("Yehaa", "Why so happy?");
await client.SendAsync(message);

and then receiving them

    async (msg, token) =>
        switch (Encoding.UTF8.GetString(msg.Body))
            case "Half life":
                await client.AbandonAsync(msg.SystemProperties.LockToken);
            case "Delivery Count":
                throw new InvalidOperationException();
            case "Poor Soul":
                await client.DeadLetterAsync(msg.SystemProperties.LockToken, 
                new Dictionary<string, object>
                    {"Reason", "Because we can!"},
                    {"When", DateTimeOffset.UtcNow}
    new MessageHandlerOptions(exception => Task.CompletedTask)
        AutoComplete = false,
        MaxConcurrentCalls = 3

The receiver in our case, does the following:

  • When a message with content “Half life” is received it abandons the message so the message will be retried until the expiry time is reached
  • When a message with content “Delivery Count” is received, an InvalidOperationException is thrown, which causes the broker to automatically redeliver the message again and again until the MaxDeliveryCount is reached (by default ten retries). If all attempts are failed the message is moved to the deadletter queue
  • When a message with content “Poor Soul” is received the client is used to directly deadletter the message with additional metadata to provide a deadletter reason

Let’s explore the state of the deadletter queue once the receiver has done its job.

The above video shows the three messages in the deadletter queue and the first message that was expired. Next up let’s inspect the message which exceeded the delivery count (for the demo I set the MaxDeliveryCount on the queue to one)

and last but not least the message that was explicitly deadlettered

As we can see in the above video, not only the custom dead letter properties have been transferred but also the initial user properties on the message.

About the author

Daniel Marbach


Recent Posts