Skip to content

Generalize EnableDeadLetterQueueRecovery to SQS and Azure Service Bus (#3103)#3106

Merged
jeremydmiller merged 1 commit into
mainfrom
feature-3103-native-dlq-recovery
Jun 15, 2026
Merged

Generalize EnableDeadLetterQueueRecovery to SQS and Azure Service Bus (#3103)#3106
jeremydmiller merged 1 commit into
mainfrom
feature-3103-native-dlq-recovery

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3103.

Summary

RabbitMQ already has a first-class native-DLQ → durable-storage bridge via EnableDeadLetterQueueRecovery(). This generalizes that feature — with the same syntax — to Amazon SQS and Azure Service Bus, so natively dead-lettered messages land in wolverine_dead_letters and become queryable/replayable through IDeadLetters and tools like CritterWatch, instead of being invisible in a broker DLQ.

opts.UseAmazonSqsTransport().EnableDeadLetterQueueRecovery();          // SQS
opts.UseAzureServiceBus(cnx).EnableDeadLetterQueueRecovery();          // Azure Service Bus
// optional explicit names:
opts.UseAmazonSqsTransport().EnableDeadLetterQueueRecovery("orders-dlq");

What it does

  • SqsDeadLetterQueueListener — background service that drains the SQS dead letter queue(s) used by listeners (or explicit names) and writes each message into durable storage via MoveToDeadLetterStorageAsync. Original exception type/message come from the stamped failure headers.
  • AzureServiceBusDeadLetterQueueListener — drains both the Wolverine-managed dead letter queue (where buffered/durable endpoints move failures) and the native $DeadLetterQueue sub-queues of every listening queue/subscription (inline endpoints + ASB-native TTL/max-delivery). Exception metadata comes from stamped headers or the native DeadLetterReason/DeadLetterErrorDescription.
  • Wolverine.Transports.DeadLetterRecoveredException (core, shared) implements IDeadLetterExceptionInfo so the original exception type is recorded in durable storage rather than the recovery wrapper.
  • Messages are only deleted/completed off the broker after being safely persisted, so a transient storage outage never loses a dead letter.

Implementation note: I verified empirically that Wolverine's buffered/durable Azure Service Bus endpoints route failures to the managed wolverine-dead-letter-queue queue (not the native sub-queue, which is only inline + ASB-native). The ASB listener therefore drains both kinds of source, which is why the no-arg overload "just works" across endpoint modes.

Documentation

New "Recovering Native Dead Letters to Durable Storage" sections on the SQS, Azure Service Bus, and (previously undocumented) RabbitMQ dead-letter-queue pages, cross-linked so the consistent syntax is discoverable from each transport.

Tests

  • Per-transport registration tests (no infrastructure) — settings holder + hosted service registered exactly once; name handling.
  • End-to-end recovery integration tests — failing handler → broker DLQ → recovery listener → durable storage → queryable. Verified locally against LocalStack + Postgres (SQS) and the Azure Service Bus emulator + Postgres (ASB).

Verification

  • Full wolverine.slnx Release build clean (0 warnings / 0 errors).
  • All 8 new tests green; SQS and ASB recovery validated end-to-end against real infrastructure.

Pairs with

#3104 (per-endpoint dead-letter-destination contract) — filed/implemented separately.

🤖 Generated with Claude Code

…#3103)

RabbitMQ has long had a first-class native-DLQ -> durable-storage bridge
via EnableDeadLetterQueueRecovery(). This brings the same one-call feature,
with the same syntax, to Amazon SQS and Azure Service Bus so natively
dead-lettered messages become queryable/replayable through IDeadLetters
(and tools like CritterWatch) instead of being invisible in a broker DLQ.

- SqsDeadLetterQueueListener: background service that drains the SQS dead
  letter queue(s) used by listeners (or explicit names) and writes each
  message into durable storage via MoveToDeadLetterStorageAsync.
- AzureServiceBusDeadLetterQueueListener: drains BOTH the Wolverine-managed
  dead letter queue (buffered/durable endpoints) AND the native
  $DeadLetterQueue sub-queues (inline endpoints + ASB-native TTL/max-delivery),
  reading exception metadata from stamped headers or DeadLetterReason/
  DeadLetterErrorDescription.
- Shared Wolverine.Transports.DeadLetterRecoveredException implements
  IDeadLetterExceptionInfo so the original exception type is preserved in
  durable storage rather than the recovery wrapper.
- Messages are only deleted/completed off the broker AFTER being safely
  persisted, so a transient storage outage never loses a dead letter.

Docs: new "Recovering Native Dead Letters to Durable Storage" sections on
the SQS, Azure Service Bus, and (previously undocumented) RabbitMQ dead
letter queue pages, cross-linked.

Tests: per-transport registration tests (no infra) plus end-to-end
recovery integration tests verified locally against LocalStack + Postgres
(SQS) and the Azure Service Bus emulator + Postgres (ASB).

Closes #3103.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit b036ab4 into main Jun 15, 2026
23 of 24 checks passed
This was referenced Jun 15, 2026
This was referenced Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

First-class native DLQ → durable-storage recovery for all transports (generalize EnableDeadLetterQueueRecovery beyond RabbitMQ)

1 participant