ZeroAlloc.Outbox 2.5.0

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

ZeroAlloc.Outbox

NuGet Build License: MIT AOT GitHub Sponsors

Source-generated transactional outbox for .NET. Annotate a message type with [OutboxMessage] and a Roslyn source generator emits a typed writer and dispatcher bridge — no reflection, no boxing, AOT-safe. Backed by EF Core (production) or in-memory (tests), with a built-in polling worker, exponential-backoff retry, and dead-letter support.

Multiple packages in this family — see Documentation or NuGet for the full list.


Install

The source generator is bundled into the main package — a single PackageReference is all you need:

# Core abstractions + source generator (always required)
dotnet add package ZeroAlloc.Outbox

# Pick a store:
dotnet add package ZeroAlloc.Outbox.EfCore    # production — Entity Framework Core
dotnet add package ZeroAlloc.Outbox.InMemory  # testing — in-process, no database

The standalone ZeroAlloc.Outbox.Generator package is still published for backwards compatibility with existing direct PackageReferences, but new consumers should reference only ZeroAlloc.Outbox.


Quick start

1. Annotate your message:

using ZeroAlloc.Outbox;

[OutboxMessage]
public sealed record OrderPlaced(int OrderId, decimal Amount);

The generator emits IOutboxWriter<OrderPlaced> and its DI registration extension.

2. Register with DI:

builder.Services.AddOutbox(options =>
        {
            options.PollingInterval = TimeSpan.FromSeconds(5);
            options.BatchSize       = 50;
            options.MaxAttempts     = 3;
        })
        .WithEfCore<AppDbContext>()      // or .WithInMemoryStore()
        .AddOrderPlacedOutbox();         // generated extension

3. Write in a transaction:

public class OrderService(IOutboxWriter<OrderPlaced> writer, AppDbContext db)
{
    public async Task PlaceOrderAsync(Order order, CancellationToken ct)
    {
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
        await writer.WriteAsync(new OrderPlaced(order.Id, order.Total), ct: ct);
    }
}

For atomic writes (both or neither commit), pass the DbTransaction explicitly. See EF Core Transaction.

4. Implement a dispatcher:

public class OrderPlacedDispatcher(IMessageBus bus) : IOutboxDispatcher<OrderPlaced>
{
    public async Task DispatchAsync(OrderPlaced message, CancellationToken ct)
        => await bus.PublishAsync(message, ct);
}

// Register the dispatcher
builder.Services.AddTransient<IOutboxDispatcher<OrderPlaced>, OrderPlacedDispatcher>();

Dashboard

Operate the outbox at runtime: inspect pending / retry / dead-lettered / dispatched messages, watch a live throughput chart, and requeue or cancel individual messages.

Add the package, then register the event publisher and map the endpoints:

dotnet add package ZeroAlloc.Outbox.Dashboard
// Register the publisher (required for SSE live updates)
builder.Services.AddOutbox().WithDashboardEvents();

// Map the dashboard endpoints
app.MapOutboxDashboard("/outbox");

// Optional: protect with auth
app.MapOutboxDashboard("/outbox").RequireAuthorization("AdminPolicy");

The mapped root (/outbox) serves the HTML dashboard; REST endpoints (snapshot, throughput, requeue, cancel, force-dispatch) and the SSE stream (events) live under the same prefix.

Security

The dashboard exposes write actions (requeue, cancel, force-dispatch) as POST endpoints:

  • POST /outbox/api/messages/{id}/requeue
  • POST /outbox/api/messages/{id}/cancel
  • POST /outbox/api/messages/{id}/force-dispatch

Never mount the dashboard unauthenticated in a production environment. Always apply authentication/authorization:

app.MapOutboxDashboard("/outbox").RequireAuthorization("AdminPolicy");

The IEndpointConventionBuilder returned by MapOutboxDashboard supports all standard ASP.NET Core auth middleware (RequireAuthorization, AllowAnonymous, route filters, etc.).

CSRF protection is the host application's responsibility — the dashboard does not emit or validate anti-forgery tokens. If your authentication scheme is cookie-based, apply the standard ASP.NET Core [ValidateAntiForgeryToken] or enable the antiforgery middleware as appropriate.

What the dashboard shows

  • Pending — messages awaiting their first dispatch attempt
  • Retry queue — messages that have failed at least once and are scheduled for retry
  • Dead-lettered — messages that exceeded MaxAttempts, with the last failure reason
  • Dispatched — most-recently succeeded messages
  • Throughput — SVG chart of dispatched + failed counts per minute
  • ActionsRequeue a dead-lettered message · Cancel a pending one · Force dispatch to run it now
Tab Screenshot
Pending — queue of messages awaiting first dispatch Pending tab
Retry — failed messages with back-off schedule Retry tab
Dead-lettered — exhausted retries with last error Dead-lettered tab
Dispatched — recently-succeeded history feeding the throughput chart Dispatched tab

The dashboard is fully responsive — tablet (768 × 1024) and mobile (375 × 812) captures live in docs/screenshots/.

Blazor component

For apps already using Blazor, ZeroAlloc.Outbox.Dashboard.Blazor ships an <OutboxDashboard /> component that embeds the dashboard via iframe:

dotnet add package ZeroAlloc.Outbox.Dashboard.Blazor
@* In any Razor page / component *@
<OutboxDashboard BaseUrl="/outbox" />

You still need MapOutboxDashboard("/outbox") — the Blazor component is a thin wrapper around the mapped endpoints.


Performance

Correctness-matched overhead vs a hand-rolled SQLite outbox (same connection, both transactional). .NET 10.0.7, i9-12900HK, BenchmarkDotNet v0.15.4.

Operation Hand-rolled ZA.Outbox Overhead
Enqueue (1 message) 6.86 µs / 2.08 KB 6.99 µs / 2.13 KB +2% time, +2% alloc
Dispatch tick (10 messages) 105.4 µs / 11.9 KB 115.0 µs / 11.09 KB +9% time, −7% alloc

Near-zero abstraction overhead vs writing the same outbox by hand — the 2–9% delta is IOutboxWriter<T> + IOutboxStore interface dispatch. The value of ZA.Outbox is the [OutboxMessage] attribute + typed writer + ecosystem composability (resilience / telemetry / dispatcher bridges) at this cost.

Full methodology: docs/performance.md.

Features

Feature Notes
Source-generated writers [OutboxMessage] triggers generator; typed IOutboxWriter<T> emitted at compile time
Typed dispatchers IOutboxDispatcher<T> — implement once, wire to any transport (bus, HTTP, email)
EF Core store Writes and reads via DbContext; enlist in ambient transaction for atomicity
InMemory store Thread-safe in-process store for unit and integration tests
Polling worker OutboxWorkerService (IHostedService) polls on configurable interval with scope isolation
Exponential backoff Retry delay = RetryBaseDelay × 2^(attempt-1); configurable via OutboxOptions
Dead-letter Entries that exceed MaxAttempts are dead-lettered with the failure reason
AOT / trimmer safe All dispatch code is generated; no Type.GetType, no MakeGenericType
IOptions<OutboxOptions> Full options support with hot-reload via standard Microsoft.Extensions.Options

Diagnostics

ID Severity Description
ZO0001 Warning [OutboxMessage] applied to an interface — code will not be generated
ZO0002 Warning [OutboxMessage] applied to a static class — code will not be generated
ZO0003 Warning [OutboxMessage] applied to a nested type — use a top-level type for a stable type discriminator

Documentation

Full docs live in docs/:


License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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 (9)

Showing the top 5 NuGet packages that depend on ZeroAlloc.Outbox:

Package Downloads
ZeroAlloc.Scheduling.EfCore

EF Core job store adapter for ZeroAlloc.Scheduling.

ZeroAlloc.Outbox.EfCore

EF Core store adapter for ZeroAlloc.Outbox.

ZeroAlloc.Outbox.InMemory

In-memory store adapter for ZeroAlloc.Outbox (testing).

ZeroAlloc.Outbox.Telemetry

ZeroAlloc.Telemetry bridge for ZeroAlloc.Outbox — wraps IOutboxTypeDispatcher with a source-generated OpenTelemetry proxy.

ZeroAlloc.Outbox.Dashboard

Operations dashboard for ZeroAlloc.Outbox. Minimal API + SSE + embedded HTML.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.5.0 116 5/14/2026
2.4.1 263 5/12/2026
2.4.0 575 5/4/2026
2.3.1 224 5/3/2026
2.3.0 202 5/1/2026
2.2.1 205 4/28/2026
2.2.0 128 4/28/2026
2.1.1 117 4/28/2026
2.1.0 133 4/26/2026
2.0.0 128 4/25/2026
1.3.0 130 4/25/2026
1.2.2 145 4/25/2026
1.2.1 547 4/24/2026
1.2.0 126 4/24/2026
1.1.2 130 4/23/2026
1.1.1 130 4/23/2026
1.1.0 127 4/22/2026
1.0.1 129 4/22/2026
1.0.0 138 4/20/2026
0.1.0 132 4/20/2026