Introduction
In modern microservices architecture using .NET and ASP.NET Core, one of the biggest challenges developers face is ensuring reliable message delivery between services. When your application performs a database operation and also needs to send a message (for example, to a message broker like Kafka or RabbitMQ), there is always a risk of failure.
For example:
Data is saved in the database
But the message fails to publish
This leads to data inconsistency, which is a serious problem in distributed systems.
To solve this issue, we use the Outbox Pattern in .NET microservices.
In this article, we will understand the Outbox Pattern in simple words and learn how to implement it step-by-step using ASP.NET Core and Entity Framework Core.
What is the Outbox Pattern?
The Outbox Pattern is a design pattern used to ensure that database changes and messages are saved together reliably.
In simple words
Instead of sending messages directly:
Save the message in a database table (Outbox table)
Later, a background service reads and publishes it
This ensures:
No message is lost
Data consistency is maintained
Why Do We Need the Outbox Pattern?
In a typical microservices system:
You update your database
Then you publish an event
But what if:
Database save succeeds
Message publishing fails?
This creates inconsistency.
The Outbox Pattern solves this by:
Storing both data and message in the same transaction
Publishing messages later safely
Real-Life Example
Imagine an e-commerce system:
Order is placed
Database is updated
Event "OrderCreated" should be sent
Without Outbox:
If event fails → other services never know about the order
With Outbox:
Event is stored in DB
It will be sent eventually
How Outbox Pattern Works
The flow is simple:
Application saves business data
Application also saves event in Outbox table
Both operations happen in a single transaction
Background worker reads Outbox table
Publishes messages to message broker
Marks message as processed
This guarantees eventual consistency in distributed systems.
Step 1: Create Outbox Table
First, create an Outbox entity.
public class OutboxMessage
{
public Guid Id { get; set; }
public string Type { get; set; }
public string Payload { get; set; }
public DateTime CreatedAt { get; set; }
public bool Processed { get; set; }
}
Add it to DbContext:
public DbSet<OutboxMessage> OutboxMessages { get; set; }
Step 2: Save Data and Event Together
When saving business data, also save the event.
public async Task CreateOrder(Order order)
{
_context.Orders.Add(order);
var message = new OutboxMessage
{
Id = Guid.NewGuid(),
Type = "OrderCreated",
Payload = JsonSerializer.Serialize(order),
CreatedAt = DateTime.UtcNow,
Processed = false
};
_context.OutboxMessages.Add(message);
await _context.SaveChangesAsync();
}
Key point
Both operations happen in one transaction, ensuring reliability.
Step 3: Create Background Service to Process Outbox
Now create a background worker.
public class OutboxProcessor : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public OutboxProcessor(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var messages = await context.OutboxMessages
.Where(x => !x.Processed)
.ToListAsync();
foreach (var message in messages)
{
// Publish message (simulate)
Console.WriteLine($"Publishing: {message.Type}");
message.Processed = true;
}
await context.SaveChangesAsync();
await Task.Delay(5000);
}
}
}
Step 4: Register Background Service
builder.Services.AddHostedService<OutboxProcessor>();
Step 5: Publish Message to Broker (Optional)
Instead of Console.WriteLine, integrate with:
RabbitMQ
Kafka
Azure Service Bus
Example:
await _messageBus.PublishAsync(message.Payload);
Handling Failures and Retries
To make the system robust:
Retry failed messages
Use exponential backoff
Log errors
Avoid infinite retries
Add fields like:
public int RetryCount { get; set; }
public string Error { get; set; }
Benefits of Outbox Pattern in .NET
Reliable message delivery
Prevent data inconsistency
Supports eventual consistency
Works well with microservices
Improves system resilience
Common Challenges
Table growth (Outbox table can grow large)
Need cleanup strategy
Slight delay in message delivery
Best Practices
Use batching for processing messages
Clean processed messages regularly
Use indexing for performance
Keep payload small
Use JSON serialization carefully
Real-World Use Case
In a payment system:
Payment is processed
Event is stored in Outbox
Notification service receives event
Even if messaging fails initially, it will retry and succeed.
Summary
The Outbox Pattern in .NET microservices is a powerful technique to ensure reliable message delivery and maintain data consistency across distributed systems. By storing events in a database table and processing them asynchronously using a background service, you can avoid message loss and build resilient, scalable applications. This pattern is essential for modern ASP.NET Core microservices that rely on event-driven architecture and message brokers.

Priyanka BhattacharjeePosted Apr 9, 2026, 11:24 AM
Excellent breakdown of the Outbox pattern! Dealing with the 'dual-write' problem is one of the trickiest parts of building resilient microservices, and your step-by-step implementation using .NET makes the concept very accessible. I particularly appreciated the focus on using a single transaction for both the business entity and the outbox message—it's a critical detail often missed by beginners. Thanks for sharing!