Skip to content

GH-5055 Dead letter outgoing envelopes a transport cannot map - #5185

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-5055-unmappable-dead-letter
Oct 23, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
gh-5055-unmappable-dead-letter

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

What changed

When a transport cannot map an outgoing envelope to its broker message, the envelope now goes to dead letter storage. It used to be logged and then deleted as if it had been sent.

  • Azure Service Bus (AzureServiceBusSenderProtocol): an unmappable envelope is reported through MarkSerializationFailureAsync. Only the envelopes that were really sent are reported successful. Before this, the success path reported the whole original batch. Covers both the plain and the partitioned send paths.
  • SNS (SnsSenderProtocol): mapping failures are reported as serialization failures, not as successes. A chunk where nothing could be mapped is not published at all. Under ServerlessWithOutbox, the durability pass now counts these envelopes as OutgoingDeadLettered instead of OutgoingSent.
  • Kafka (KafkaSenderProtocol): only the envelope that fails to map is reported as a serialization failure. Before, one bad envelope reported the whole batch, so the envelopes that mapped fine were dropped too. The protocol also gets an internal logger so the original exception is logged.
  • Sending agents: SendingAgent.MarkSerializationFailureAsync now moves the envelopes to dead letter storage whenever the application has a message store, in every durability mode. With no runtime or a NullMessageStore, it only logs, as before.
  • ServerlessWithOutbox single-envelope inline send (DurableSendingAgent.sendInlineAsync): an unsendable envelope is dead lettered right away. This matches the grouped inline send and the durability pass (ServerlessWithOutbox: a row the broker refuses as a serialization failure is re-sent by every durability pass #5047).
  • SQS docs and comments updated: an oversized message is now "dead lettered", not "discarded".

There is no public API change. The new members are internal: MoveUnsendableToDeadLetterStorageAsync, UnsendableEnvelopeException and KafkaSenderProtocol.Logger.

Survey of the other transports

  • SQS: already reported unmappable or oversized bodies as serialization failures. An entry that fails while building attributes is retried, not dropped. No change.
  • RabbitMQ, Pulsar, NATS: these are not batch protocols. A mapping exception is thrown from SendAsync and goes down the agent's retry path, so nothing is dropped. No change.
  • Pub/Sub, Redis, MQTT: a mapping exception fails the batch, or the rest of it, through MarkProcessingFailureAsync, which means a retry. Nothing is dropped silently, so these were left alone. They retry rather than dead letter, though, and for Redis and MQTT an unmappable envelope keeps failing everything after it in the batch.

Tests run locally (net9.0, Release)

  • CoreTests, new unmappable_outgoing_envelopes_are_dead_lettered_5055 plus the Amazon SQS: an oversized message is retried forever, and there is no built-in way to send one #3926 and ServerlessWithOutbox: a row the broker refuses as a serialization failure is re-sent by every durability pass #5047 classes: 12/12.
  • CoreTests, nearby classes (*SendingAgent*, *sending_agent*, *serverless*, *inline_send*, *BatchedSender*, *Durability*): 182/182.
  • Wolverine.AzureServiceBus.Tests, new unmappable_envelopes_are_reported_as_serialization_failures_5055 (batch of three with one unmappable, plain and partitioned) plus the 5044 and message-too-large classes: 10/10. These use a faked ServiceBusSender; the emulator was not used.
  • Wolverine.AmazonSns.Tests against LocalStack and Postgres, new unmappable_envelopes_are_dead_lettered_5055 (Balanced mode, 2 delivered and 1 dead letter row) plus serverless_with_outbox_sns_5044 (durability pass: OutgoingSent = 2, OutgoingDeadLettered = 1; also the new inline send case), OutgoingSnsBatchTests and the 3793 class: 15/15.
  • Kafka: the library builds pinned to net9.0. No Kafka test was added (Kafka.Tests targets net10.0 only, and no broker was running).

Closes #5055

🤖 Generated with Claude Code

https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U

… of reporting them sent

- Azure Service Bus: an envelope whose mapping throws is reported through
  MarkSerializationFailureAsync and left out of what is reported sent (the
  success path used to report the whole original batch)
- SNS: mapping failures are reported as serialization failures, not successes;
  a chunk with nothing mappable is not published. Under ServerlessWithOutbox the
  durability pass now counts them as OutgoingDeadLettered, not OutgoingSent
- Kafka: only the unmappable envelope is a serialization failure; it used to
  take the whole batch down with it
- SendingAgent.MarkSerializationFailureAsync moves the envelopes to dead letter
  storage when the application has a message store (every mode). The durable
  agent deletes the outbox row once the dead letter is written, and leaves it
  for a later sweep when that write fails
- The ServerlessWithOutbox single-envelope inline send dead letters an
  unsendable envelope right away, consistent with the grouped inline send and
  the durability pass (GH-5047)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
@jeremydmiller
jeremydmiller merged commit df755a1 into main Oct 23, 2026
@jeremydmiller jeremydmiller mentioned this pull request Oct 23, 2026
This was referenced Oct 25, 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.

Discussion: Azure Service Bus and SNS silently discard a message they cannot map

1 participant