Problem
Consumers have no reliable, provider-agnostic way to know when a queue item has been permanently dead-lettered (i.e., exhausted all retries and will be discarded). This makes it impossible to cleanly:
- Delete associated data payloads (e.g., files in
IFileStorage that the queue item references)
- Execute compensating transactions or mark database records as permanently failed
- Alert or trigger manual intervention workflows
- Maintain an audit trail of permanently rejected messages
Current State
The only approach today is subscribing to the Abandoned event and predicting the dead-letter outcome by checking attempt counts:
queue.Abandoned.AddHandler((sender, args) => {
if (args.Entry.Attempts >= queue.Options.Retries + 1)
// Probably going to dead-letter...
return Task.CompletedTask;
});
This is unreliable for two reasons:
- The
Abandoned event fires before the dead-letter routing decision (in the finally block), so you are predicting, not observing
- For cloud-managed providers, dead-lettering is a server-side decision decoupled from Foundatio's
Retries option — Azure Service Bus uses MaxDeliveryCount, SQS uses a redrive policy max receive count, and neither calls back into Foundatio when the broker moves the message
Dead-Letter Support by Provider
| Provider |
Dead-Letter Storage |
GetDeadletterItemsAsync() |
Reliable Event Possible? |
InMemoryQueue |
In-memory ConcurrentQueue<T> |
✅ Yes |
✅ Yes — Foundatio controls routing |
RedisQueue |
Redis (internal) |
❌ No public API |
✅ Yes — Foundatio controls routing |
AzureServiceBusQueue |
Azure native $DeadLetterQueue |
❌ No |
⚠️ Best-effort — broker decides independently |
AzureStorageQueue |
None (messages silently dropped) |
❌ No |
❌ No dead-letter concept |
SQSQueue |
AWS native DLQ (separate queue) |
❌ No |
⚠️ Best-effort — broker decides independently |
Proposed Solution
Add a DeadLettered AsyncEvent to IQueue<T> that fires after the dead-letter routing decision is made:
InMemoryQueue / RedisQueue: Fire reliably in the implementation after the item is confirmed moved to dead-letter
AzureServiceBusQueue / SQSQueue: Fire as best-effort based on attempt count, with clear documentation that the broker may dead-letter independently
This is related to #6 (general queue item lifecycle notifications), but specifically targets the dead-letter transition case.
Problem
Consumers have no reliable, provider-agnostic way to know when a queue item has been permanently dead-lettered (i.e., exhausted all retries and will be discarded). This makes it impossible to cleanly:
IFileStoragethat the queue item references)Current State
The only approach today is subscribing to the
Abandonedevent and predicting the dead-letter outcome by checking attempt counts:This is unreliable for two reasons:
Abandonedevent fires before the dead-letter routing decision (in thefinallyblock), so you are predicting, not observingRetriesoption — Azure Service Bus usesMaxDeliveryCount, SQS uses a redrive policy max receive count, and neither calls back into Foundatio when the broker moves the messageDead-Letter Support by Provider
GetDeadletterItemsAsync()InMemoryQueueConcurrentQueue<T>RedisQueueAzureServiceBusQueue$DeadLetterQueueAzureStorageQueueSQSQueueProposed Solution
Add a
DeadLetteredAsyncEventtoIQueue<T>that fires after the dead-letter routing decision is made:InMemoryQueue/RedisQueue: Fire reliably in the implementation after the item is confirmed moved to dead-letterAzureServiceBusQueue/SQSQueue: Fire as best-effort based on attempt count, with clear documentation that the broker may dead-letter independentlyThis is related to #6 (general queue item lifecycle notifications), but specifically targets the dead-letter transition case.