ModularOutbox.Abstractions
1.0.3
dotnet add package ModularOutbox.Abstractions --version 1.0.3
NuGet\Install-Package ModularOutbox.Abstractions -Version 1.0.3
<PackageReference Include="ModularOutbox.Abstractions" Version="1.0.3" />
<PackageVersion Include="ModularOutbox.Abstractions" Version="1.0.3" />
<PackageReference Include="ModularOutbox.Abstractions" />
paket add ModularOutbox.Abstractions --version 1.0.3
#r "nuget: ModularOutbox.Abstractions, 1.0.3"
#:package ModularOutbox.Abstractions@1.0.3
#addin nuget:?package=ModularOutbox.Abstractions&version=1.0.3
#tool nuget:?package=ModularOutbox.Abstractions&version=1.0.3
ModularOutbox
A lightweight, production-ready Transactional Outbox + Inbox implementation for .NET applications using Entity Framework Core and PostgreSQL.
ModularOutbox helps you reliably publish integration events without the dual-write problem by storing events in the same database transaction as your business data.
Features
- ✅ Transactional Outbox Pattern
- ✅ Inbox Pattern for idempotent consumers
- ✅ Entity Framework Core integration
- ✅ PostgreSQL storage provider
- ✅ Automatic integration event discovery
- ✅ Automatic background delivery service
- ✅ Automatic cleanup service
- ✅ Polly resilience support
- ✅ Configurable retry policies
- ✅ UUIDv7 event identifiers
- ✅ High-performance batch processing
- ✅ Safe for multiple application instances
- ✅ Custom JSON serialization options
Architecture
HTTP Request
│
▼
Application Service
│
┌───────────────┴────────────────┐
│ │
▼ ▼
Save Business Data Enqueue Integration Event
│ │
└───────────────┬────────────────┘
▼
DbContext.SaveChanges()
│
Single Database Transaction
│
┌───────────────┴────────────────┐
│ │
▼ ▼
Business Tables Outbox Table
│
▼
Background Delivery Service
│
▼
Integration Event Dispatcher
│
▼
Registered Event Handlers
│
▼
Inbox Table
Installation
Install the required packages.
dotnet add package ModularOutbox.Core
dotnet add package ModularOutbox.EntityFrameworkCore
dotnet add package ModularOutbox.PostgreSQL
Quick Start
1. Configure Entity Framework Core
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
options
.UseNpgsql(builder.Configuration.GetConnectionString("Database"))
.UseModularOutbox(sp);
});
2. Register ModularOutbox
builder.Services.AddModularOutbox(outbox =>
{
outbox.ConfigureOptions(options =>
{
options.ConnectionString =
builder.Configuration.GetConnectionString("Database")!;
options.Schema = "messaging";
options.BatchSize = 100;
options.EnableDeliveryService = true;
options.EnableCleanupService = true;
});
outbox
.RegisterHandlersFromAssemblies(typeof(Program).Assembly)
.AddResilienceDecorators()
.UseEntityFrameworkCore()
.UsePostgreSql(
builder.Configuration.GetConnectionString("Database")!);
});
3. Configure your DbContext
Apply the ModularOutbox entity configurations.
public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
: DbContext(options)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyModularOutboxConfigurations(this);
}
}
4. Create an Integration Event
Simply inherit from IntegrationEvent.
public sealed record UserRegisteredIntegrationEvent(
Guid UserId,
string Email
) : IntegrationEvent;
Optional: Stable Message Names
To avoid breaking message contracts after refactoring, assign a logical message name.
[OutboxMessageName("identity.user-registered.v1")]
public sealed record UserRegisteredIntegrationEvent(
Guid UserId,
string Email
) : IntegrationEvent;
5. Publish an Event
Inject IOutboxWriter and enqueue the event before calling SaveChangesAsync().
public sealed class RegisterUserHandler
{
public async Task Handle(
RegisterUser command,
AppDbContext dbContext,
IOutboxWriter outboxWriter,
CancellationToken ct)
{
var user = new User(command.Email);
dbContext.Users.Add(user);
outboxWriter.Enqueue(
new UserRegisteredIntegrationEvent(
user.Id,
user.Email));
await dbContext.SaveChangesAsync(ct);
}
}
Important
SaveChangesAsync()commits both your entity changes and the outbox message in the same database transaction.If the transaction fails, neither the entity nor the event is persisted.
6. Handle Events
Implement IIntegrationEventHandler<T>.
public sealed class UserRegisteredHandler(
IInboxStore inboxStore,
ILogger<UserRegisteredHandler> logger)
: IIntegrationEventHandler<UserRegisteredIntegrationEvent>
{
private const string Consumer =
nameof(UserRegisteredHandler);
public async Task HandleAsync(
UserRegisteredIntegrationEvent integrationEvent,
CancellationToken ct)
{
if (await inboxStore.HasBeenProcessedAsync(
integrationEvent.Id,
Consumer,
ct))
{
return;
}
logger.LogInformation(
"Sending welcome email to {Email}",
integrationEvent.Email);
await inboxStore.MarkAsProcessedAsync(
integrationEvent.Id,
Consumer,
ct);
}
}
The IInboxStore ensures your handlers are idempotent, preventing duplicate processing when messages are retried.
Resilience
ModularOutbox integrates with Microsoft.Extensions.Resilience / Polly.
Register a resilience pipeline.
builder.Services.AddResiliencePipeline(
"emails",
pipeline =>
{
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3
});
});
Apply it to your handler.
[ResilientHandler("emails")]
public sealed class UserRegisteredHandler
: IIntegrationEventHandler<UserRegisteredIntegrationEvent>
{
}
If no attribute is specified, the default pipeline registered by AddResilienceDecorators() is used.
Configuration
builder.Services.AddModularOutbox(outbox =>
{
outbox.ConfigureOptions(options =>
{
options.ConnectionString = "...";
options.Schema = "messaging";
options.BatchSize = 100;
options.PollingInterval = TimeSpan.FromSeconds(10);
options.LockTimeout = TimeSpan.FromSeconds(15);
options.MaxRetries = 5;
options.CleanupAfter = TimeSpan.FromHours(24);
options.CleanupInterval = TimeSpan.FromHours(12);
options.EnableDeliveryService = true;
options.EnableCleanupService = true;
});
});
| Option | Default | Description |
|---|---|---|
ConnectionString |
Required | PostgreSQL connection string |
Schema |
messaging |
Database schema |
BatchSize |
100 |
Number of messages processed per batch |
PollingInterval |
10 seconds |
Idle polling interval |
LockTimeout |
15 seconds |
Message lease duration |
MaxRetries |
5 |
Maximum retry attempts |
CleanupAfter |
24 hours |
Message retention period |
CleanupInterval |
12 hours |
Cleanup execution interval |
EnableDeliveryService |
true |
Enables background delivery service |
EnableCleanupService |
true |
Enables cleanup service |
How It Works
- Your application modifies business data.
- Integration events are staged using
IOutboxWriter. SaveChangesAsync()persists both business data and outbox messages in a single transaction.- The background delivery service fetches pending messages.
- Messages are deserialized and dispatched.
- Registered handlers execute.
IInboxStorerecords successful processing to guarantee idempotency.- Successfully processed messages are cleaned up automatically.
Sample
app.MapPost(
"/users",
async (
CreateUserRequest request,
AppDbContext db,
IOutboxWriter outbox,
CancellationToken ct) =>
{
var user = new User(request.Email);
db.Users.Add(user);
outbox.Enqueue(
new UserRegisteredIntegrationEvent(
user.Id,
user.Email));
await db.SaveChangesAsync(ct);
return Results.Ok();
});
Roadmap
- Transactional Outbox
- Inbox Pattern
- PostgreSQL provider
- Entity Framework Core integration
- Polly resilience support
- SQL Server provider
- MongoDB provider
- Distributed transport integrations
- Metrics and OpenTelemetry
License
Licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on ModularOutbox.Abstractions:
| Package | Downloads |
|---|---|
|
ModularOutbox.Core
Package Description |
|
|
ModularOutbox.EntityFrameworkCore
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.3 | 169 | 8/2/2026 |
| 1.0.2 | 136 | 8/2/2026 |
| 1.0.1 | 146 | 8/1/2026 |
| 1.0.0 | 136 | 8/1/2026 |
| 1.0.0-preview2 | 129 | 8/1/2026 |
| 1.0.0-preview1 | 120 | 7/29/2026 |