Runax.Messaging.Outbox 2.0.0

dotnet add package Runax.Messaging.Outbox --version 2.0.0
                    
NuGet\Install-Package Runax.Messaging.Outbox -Version 2.0.0
                    
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="Runax.Messaging.Outbox" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Runax.Messaging.Outbox" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Runax.Messaging.Outbox" />
                    
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 Runax.Messaging.Outbox --version 2.0.0
                    
#r "nuget: Runax.Messaging.Outbox, 2.0.0"
                    
#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 Runax.Messaging.Outbox@2.0.0
                    
#: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=Runax.Messaging.Outbox&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Runax.Messaging.Outbox&version=2.0.0
                    
Install as a Cake Tool

Runax.Messaging.Outbox

Transactional outbox for Runax.Messaging. Persist messages in the same database transaction as your business data, then let a background dispatcher deliver them to the transport — so a crash between "commit" and "publish" can't lose a message.

Install

dotnet add package Runax.Messaging
dotnet add package Runax.Messaging.Outbox

Register

An outbox belongs to one bus — configure both halves inside that bus's AddBus block:

using Runax.Messaging;
using Runax.Messaging.Outbox;

builder.Services.AddRunaxMessaging(messaging =>
{
    messaging.AddBus(bus =>
    {
        bus.AddTransport(new RabbitMqConfig { HostName = "localhost" });
        bus.AddConsumer<OrderPlacedConsumer>();

        bus.AddOutbox(o => o.PollingInterval = TimeSpan.FromSeconds(2));
        bus.AddOutboxStore(new InMemoryOutboxStoreConfig());   // or your own OutboxStoreConfig
    });
});

AddOutbox swaps the bus's publish sink: bus.PublishAsync writes the envelope to the IOutboxStore instead of the transport, and the per-bus OutboxDispatcher background service drains pending entries to the bus's transport and marks them dispatched. Buses without an outbox publish straight to their transport, so publishing on another (outbox-less) bus skips the outbox. AddOutbox on a BusMode.ConsumeOnly bus throws at configuration time — the outbox exists to publish.

Providing a durable store

AddOutbox registers the pattern only — it does not register a store. You must pair it with AddOutboxStore(...): an outbox without a store (or a store without an outbox) throws when the AddBus block completes, and a second AddOutboxStore on the same bus throws like a second AddTransport does. Stores register via a config type — the same uniform pattern as AddTransport: InMemoryOutboxStoreConfig ships in the box (tests/single-process only), and a durable store (EF Core, Dapper, Mongo, ADO.NET, …) derives OutboxStoreConfig:

public sealed class EfOutboxStoreConfig : OutboxStoreConfig
{
    // protected (not protected internal): the base member's `internal` half
    // doesn't carry across assemblies.
    protected override IOutboxStore CreateStore(OutboxStoreContext context) =>
        new EfOutboxStore(context.Services.GetRequiredService<IDbContextFactory<AppDbContext>>());
}

The atomicity guarantee comes from your store: implement IOutboxStore so that AddAsync enlists in the caller's transaction (e.g. adds a row to your EF Core DbContext without calling SaveChanges), so the outbox row commits together with your business data. Note that GetPendingAsync takes the bus name and OutboxMessage carries a Bus field, so one store (one table) can serve several buses:

public sealed class EfOutboxStore(AppDbContext db) : IOutboxStore
{
    public Task AddAsync(OutboxMessage message, CancellationToken ct = default)
    {
        db.OutboxMessages.Add(message);   // committed by the caller's SaveChangesAsync
        return Task.CompletedTask;
    }

    public async Task<IReadOnlyList<OutboxMessage>> GetPendingAsync(
        string bus, int maxCount, CancellationToken ct = default) =>
        await db.OutboxMessages.Where(m => m.Bus == bus && m.DispatchedAt == null)
            .OrderBy(m => m.CreatedAt).Take(maxCount).ToListAsync(ct);

    public async Task MarkDispatchedAsync(Guid id, CancellationToken ct = default) =>
        await db.OutboxMessages.Where(m => m.Id == id)
            .ExecuteUpdateAsync(s => s.SetProperty(m => m.DispatchedAt, DateTimeOffset.UtcNow), ct);
}

InMemoryOutboxStore is provided for tests and single-process use only — it is not durable or transactional.

Scoping. The bus's publish sink and the OutboxDispatcher are singletons, so a store that depends on a scoped DbContext should not capture it directly. Resolve the unit of work per operation instead — inject IDbContextFactory<AppDbContext> (or IServiceScopeFactory) and create a context inside each IOutboxStore call.

Options

Passed to bus.AddOutbox(o => ...) (OutboxOptions):

Option Default Description
PollingInterval 5s How often the dispatcher polls the store.
BatchSize 100 Maximum pending messages drained per poll.

License

MIT

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.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 98 9/10/2026
1.0.0 114 7/26/2026