As posted in How Azure Service Bus Handles Retries Part- 1 , the below post focuses on the retry flow and monitoring the Dead-Letter Queue (DLQ)
Step-by-Step Upgrade
1) Simulate a Failure in Message Processing
Replace your original message processor with logic that randomly fails:
csharp
var processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions());
processor.ProcessMessageAsync += async args =>
{
string body = args.Message.Body.ToString();
Console.WriteLine($"Received: {body}");
// Simulate a failure randomly
if (new Random().Next(1, 4) == 1)
{
throw new Exception("Simulated processing failure.");
}
await args.CompleteMessageAsync(args.Message);
Console.WriteLine("Message completed successfully.");
};
processor.ProcessErrorAsync += async args =>
{
Console.WriteLine($" Error Handler: {args.Exception.Message}");
await Task.CompletedTask;
};
This simulates real-world transient issues.
2) Read from the Dead-Letter Queue (DLQ)
This processor will read messages from the DLQ after they fail too many times:
csharp
Console.WriteLine("Starting Dead-Letter Queue Listener...");
var dlqReceiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions
{
SubQueue = SubQueue.DeadLetter
});
var deadLetters = await dlqReceiver.ReceiveMessagesAsync(maxMessages: 10, TimeSpan.FromSeconds(5));
if (deadLetters.Count == 0)
{
Console.WriteLine(" No messages in DLQ.");
}
else
{
Console.WriteLine($" Found {deadLetters.Count} message(s) in DLQ:");
foreach (var msg in deadLetters)
{
Console.WriteLine($" ❗ Message: {msg.Body}");
await dlqReceiver.CompleteMessageAsync(msg); // Mark as handled
}
}
You can loop this or run it on a timer in a real app.
Test It Out:
Send messages with the original sender
Let the random failure simulate retries
Once the MaxDeliveryCount is hit (default: 10), the message will land in the DLQ
DLQ monitor will read and log them
Optional Improvements:
- Push DLQ metrics to Application Insights
- Automatically re-send DLQ messages to retry queue (with delay)
- Add logging to a file or database
Question:
can we use even driven architecture when the message retry fails?
Answer:
Yes — using event-driven architecture even when message retries fail is not only possible, but it's also one of the best patterns to handle failure gracefully and reactively.
Here’s how it works and why it’s useful:
Using Event-Driven Architecture After Retry Fails
What it means:
Instead of letting failed messages silently die or sit in the DLQ, you can emit an event when a failure happens (after all retries), and other systems can react to that event.
Typical Flow with Retry Failure
Producer --> Queue --> Processor --> Fails N times
↓
Moved to DLQ
↓
Dead-letter handler raises an event → Notifier / Logger / Dashboard / Alert
Examples of Event-Driven Responses After Failure
How to Implement
- Emit Custom Event on DLQ Handler After reading from DLQ:
csharp
foreach (var msg in deadLetters)
{
var body = msg.Body.ToString();
Console.WriteLine($" Dead-lettered: {body}");
// Raise an event (example: publish to another queue or topic)
var failureEvent = new ServiceBusMessage(JsonSerializer.Serialize(new
{
Type = "OrderProcessingFailed",
OriginalPayload = body,
Timestamp = DateTime.UtcNow
}));
await failureEventSender.SendMessageAsync(failureEvent);
await dlqReceiver.CompleteMessageAsync(msg);
}
You could publish this to:
Another Service Bus queue or topic
Event Grid
A webhook or REST API
A logging pipeline (e.g., Application Insights, Datadog)
Why This Matters?
Benefits of triggering events after retries fail:
- Real-time observability of critical failures
- Prevent silent data loss in DLQ
- Let multiple teams (notifications, support, analytics) respond independently
- Enables automatic recovery, fallbacks, or audits
Best Practices: