feat(sqs): transport-wide DefaultDeadLetterQueueName with per-listener override precedence#2714
Merged
Merged
Conversation
…r override precedence Closes #2653. Adds `AmazonSqsTransportConfiguration.DefaultDeadLetterQueueName(...)` so a host can rename the SQS DLQ used by every auto-provisioned listener in one call instead of duplicating `ConfigureDeadLetterQueue("…")` across every `ListenToSqsQueue(...)` line. Required when multi-environment AWS accounts share a region and the historical `wolverine-dead-letter-queue` collides across environments, or when conventional routing / auto-provisioning makes per-listener configuration impractical. ```csharp opts.UseAmazonSqsTransport() .DefaultDeadLetterQueueName("my-service-dlq") .AutoProvision(); opts.ListenToSqsQueue("orders"); // inherits "my-service-dlq" opts.ListenToSqsQueue("payments") .ConfigureDeadLetterQueue("payments-errors"); // wins for this listener opts.ListenToSqsQueue("notifications") .DisableDeadLetterQueueing(); // wins for this listener ``` ## Resolution rule Per-listener `ConfigureDeadLetterQueue("name")` or `DisableDeadLetterQueueing()` always win over the transport-wide default. `DisableAllNativeDeadLetterQueues()` remains the global kill-switch and trumps both. Default fallback when nothing is set is the historical `wolverine-dead-letter-queue` — no behaviour change for hosts that don't opt in. ## Implementation `AmazonSqsQueue.DeadLetterQueueName` is no longer an auto-property default-initialized to the constant; it's a getter that resolves through the parent transport's new `DefaultDeadLetterQueueName` property when the queue hasn't been explicitly configured. The setter records an "explicitly set" flag, so the existing per-listener API surface (`ConfigureDeadLetterQueue` / `DisableDeadLetterQueueing`) keeps working unchanged — both paths go through that setter and mark the queue as having an explicit override (including `null` for "disabled"), which then beats the transport default at read time. The transport stores its default in `AmazonSqsTransport.DefaultDeadLetterQueueName`, initialised in the ctor to the historical `DeadLetterQueueName` constant. The new `DefaultDeadLetterQueueName(string)` configuration method validates the input is non-null/non-whitespace (suggests `DisableAllNativeDeadLetterQueues()` for the disable case) and runs `SanitizeSqsName` so authors can write `acme.payments.dlq` and get the same `acme-payments-dlq` canonical form per-listener config produces. System queues (response / control queues, when `EnableSystemQueues()` is on) explicitly opt out of dead-lettering during transport bootstrap by setting `DeadLetterQueueName = null` directly. Under the new resolver that null-set marks the system queue as explicitly disabled — so the transport-wide default doesn't accidentally enroll system queues into a DLQ on hosts that opt into `DefaultDeadLetterQueueName`. ## Test coverage 10 new tests in `default_dead_letter_queue_name.cs` covering every permutation of the resolution rule plus argument validation: - Built-in fallback when nothing is configured - Transport default applied to every unconfigured listener - Per-endpoint override wins over transport default (mixed listeners) - Per-endpoint disable wins over transport default - Global `DisableAllNativeDeadLetterQueues()` trumps the transport default - Sanitization of the transport-wide name (dots → hyphens) matches per-listener - Auto-provisioning: only the resolved DLQ name is provisioned, the historical default isn't - Mixed: inherit + override + disabled all coexist on the same transport - Argument validation throws for null / empty / whitespace - System queues keep their explicit no-DLQ setting under a transport default 10/10 passing on net9.0 against LocalStack. Docs: new "Customizing the Default Dead Letter Queue Name" section in `docs/guide/messaging/transports/sqs/deadletterqueues.md` covering the resolution order, sanitization behaviour, and the system-queue exemption. `vitepress build` clean.
This was referenced May 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2653.
Adds
AmazonSqsTransportConfiguration.DefaultDeadLetterQueueName(...)so a host can rename the SQS DLQ used by every auto-provisioned listener in one call instead of duplicatingConfigureDeadLetterQueue("…")across everyListenToSqsQueue(...). Multi-environment AWS accounts that share a region collide on the historicalwolverine-dead-letter-queuedefault; conventional routing / auto-provisioning makes per-listener configuration impractical.Resolution rule (per listener)
ConfigureDeadLetterQueue("name")on the listener wins.DisableDeadLetterQueueing()on the listener wins (no DLQ for that one).DefaultDeadLetterQueueName(...)is used.wolverine-dead-letter-queueapplies.DisableAllNativeDeadLetterQueues()remains the global kill-switch and trumps both. Default fallback when nothing is set is the historicalwolverine-dead-letter-queue— no behaviour change for hosts that don't opt in.Implementation
AmazonSqsQueue.DeadLetterQueueNameis no longer an auto-property default-initialized to the constant; it's a getter that resolves through the parent transport's newDefaultDeadLetterQueueNameproperty when the queue hasn't been explicitly configured. The setter records an "explicitly set" flag, so the existing per-listener API surface (ConfigureDeadLetterQueue/DisableDeadLetterQueueing) keeps working unchanged — both paths go through that setter and mark the queue as having an explicit override (includingnullfor "disabled"), which then beats the transport default at read time.The transport stores its default in
AmazonSqsTransport.DefaultDeadLetterQueueName, initialised in the ctor to the historicalDeadLetterQueueNameconstant. The newDefaultDeadLetterQueueName(string)configuration method validates the input (suggestsDisableAllNativeDeadLetterQueues()for the disable case) and runsSanitizeSqsNameso authors can writeacme.payments.dlqand get the sameacme-payments-dlqcanonical form per-listener config produces.System queues (response / control queues, when
EnableSystemQueues()is on) explicitly opt out of dead-lettering during transport bootstrap by settingDeadLetterQueueName = nulldirectly. Under the new resolver that null-set marks the system queue as explicitly disabled — so the transport-wide default doesn't accidentally enroll system queues into a DLQ on hosts that opt intoDefaultDeadLetterQueueName.Test plan — every permutation
default_dead_letter_queue_name.cs(10 tests, 10/10 green on net9.0 against LocalStack):wolverine-dead-letter-queueDisableAllNativeDeadLetterQueues()) trumps the transport defaultnull/ empty / whitespace throw with a hint atDisableAllNativeDeadLetterQueues()Docs
New "Customizing the Default Dead Letter Queue Name" section in
docs/guide/messaging/transports/sqs/deadletterqueues.mdcovering the resolution order, sanitization, and the system-queue exemption.vitepress buildclean.🤖 Generated with Claude Code