Skip to main content
Version: v1.5.1

Dead-Letter Deployment

The dead-letter feature can be deployed in two topologies: same-process (relay runs inside the publisher application) or cross-process (dedicated worker process).

Same-Process Deployment​

Register all components in the same application host:

services.AddEventPublisher(options =>
{
options.Source = new Uri("https://orders.example.com");
options.ThrowOnErrors = false;
})
.AddRabbitMq(options => { /* transport config */ })
.AddDeadLetter()
.WithEntityFramework(options => options.UseSqlServer(connectionString))
.WithReplayWorker(options =>
{
options.Interval = TimeSpan.FromSeconds(30);
options.MaxRetryCount = 3;
});

Pros​

BenefitDescription
Simple setupOne deployment, one configuration
No additional infrastructureNo separate worker process to manage
Good for developmentEasy to test and debug

Cons​

DrawbackDescription
Resource contentionRelay competes with main application for CPU/memory
Coupled lifecycleRelay restarts when application restarts
Cannot scale independentlyMust scale entire application to scale replay

When to Use​

  • Development and testing
  • Low to medium message volumes
  • Applications where simplicity is paramount

Cross-Process Deployment​

Split the feature across two applications:

Publisher Application​

Publishes to the real transport and persists failures into the dead-letter store:

// Publisher application Program.cs
services.AddEventPublisher(options =>
{
options.Source = new Uri("https://orders.example.com");
options.ThrowOnErrors = false;
})
.AddRabbitMq(options => { /* transport config */ })
.AddDeadLetter()
.WithEntityFramework(options => options.UseSqlServer(connectionString));
// No WithReplayWorker() here

Worker Application​

Connects to the same store and runs WithReplayWorker():

// Worker application Program.cs
services.AddEventPublisher("transport", builder => builder
.Configure(options =>
{
options.Source = new Uri("https://orders.example.com/transport");
options.ThrowOnErrors = true;
})
.AddRabbitMq(options => { /* transport config */ }));

services.AddEventPublisher()
.AddDeadLetter()
.UseRepository<DbDeadLetterMessage, EntityDeadLetterMessageStore<DbDeadLetterMessage>>()
.WithFactory<DbDeadLetterMessage, DefaultDeadLetterMessageFactory<DbDeadLetterMessage>>()
.WithReplayWorker(options =>
{
options.TransportPublisherName = "transport";
options.Interval = TimeSpan.FromSeconds(30);
});

Pros​

BenefitDescription
Independent scalingScale workers without scaling the publisher
Isolated failuresWorker crashes don't affect the publisher
Dedicated resourcesWorker can run on different hardware
Simplified deploymentUpdate worker logic without redeploying publisher

Cons​

DrawbackDescription
More complex deploymentSeparate worker process to manage
Shared databaseRequires database access from both applications
Additional infrastructureNeed to monitor and maintain worker

When to Use​

  • High message volumes
  • Mission-critical applications requiring high availability
  • When replay processing is resource-intensive
  • When you need to scale replay independently

Comparison Table​

AspectSame-ProcessCross-Process
Setup complexitySimpleModerate
InfrastructureMinimalAdditional worker process
ScalabilityLimitedExcellent
IsolationNoneFull
Resource contentionYesNo
DeploymentSingleMultiple
Best forDev/Test, low volumeProduction, high volume