ModularOutbox.Abstractions 1.0.3

dotnet add package ModularOutbox.Abstractions --version 1.0.3
                    
NuGet\Install-Package ModularOutbox.Abstractions -Version 1.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ModularOutbox.Abstractions" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ModularOutbox.Abstractions" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="ModularOutbox.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ModularOutbox.Abstractions --version 1.0.3
                    
#r "nuget: ModularOutbox.Abstractions, 1.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ModularOutbox.Abstractions@1.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ModularOutbox.Abstractions&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=ModularOutbox.Abstractions&version=1.0.3
                    
Install as a Cake Tool

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

  1. Your application modifies business data.
  2. Integration events are staged using IOutboxWriter.
  3. SaveChangesAsync() persists both business data and outbox messages in a single transaction.
  4. The background delivery service fetches pending messages.
  5. Messages are deserialized and dispatched.
  6. Registered handlers execute.
  7. IInboxStore records successful processing to guarantee idempotency.
  8. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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