Skip to content

Add DeadLettered event to IQueue<T> for reliable dead-letter detection across all implementations #18

Description

@niemyjski

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:

  1. The Abandoned event fires before the dead-letter routing decision (in the finally block), so you are predicting, not observing
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions