Dead Letter Channel
Dead Letter Channel captures messages that cannot be processed or delivered after the owning pipeline has exhausted its normal handling path. PatternKit keeps the channel application-owned: you choose the store, source name, failure reason, attempt count, and replay handoff.
Use DeadLetterChannel<TPayload> when a message should be preserved for operations instead of being dropped or retried forever.
var store = new InMemoryDeadLetterStore<FulfillmentCommand>();
var channel = DeadLetterChannel<FulfillmentCommand>.Create("fulfillment-dead-letter")
.FromSource("checkout.fulfillment")
.UseStore(store)
.UseIds((message, reason, context) => "fulfillment-dead:" + message.Headers.MessageId)
.Build();
var deadLetter = await channel.CaptureAsync(
command,
"carrier timeout",
exception,
attempts: 4,
cancellationToken: ct);
Captured messages preserve the original payload and headers, then add operational headers such as dead-letter-id, dead-letter-channel, dead-letter-reason, dead-letter-attempts, and dead-letter-source.
Replay Handoff
Replay is explicit. PrepareReplayAsync loads the captured message and adds replay metadata without deleting the dead-letter record:
var replay = await channel.PrepareReplayAsync(deadLetter.Id, ct);
if (replay.ReadyForReplay)
{
await fulfillmentInbox.ProcessAsync(replay.Message!, cancellationToken: ct);
}
Use a durable IDeadLetterStore<TPayload> implementation for production transport boundaries. InMemoryDeadLetterStore<TPayload> is intended for tests, samples, and embedded in-process usage.
Source Generator
Use [GenerateDeadLetterChannel] when the channel name, source, and store factory are stable at compile time. See Dead Letter Channel Generator.
Example
The production-shaped fulfillment example demonstrates the fluent channel, generated channel, and IServiceCollection integration:
src/PatternKit.Examples/Messaging/FulfillmentDeadLetterChannelExample.cstest/PatternKit.Examples.Tests/Messaging/FulfillmentDeadLetterChannelExampleTests.cs