ZeroAlloc.Outbox.Generator
2.5.0
dotnet add package ZeroAlloc.Outbox.Generator --version 2.5.0
NuGet\Install-Package ZeroAlloc.Outbox.Generator -Version 2.5.0
<PackageReference Include="ZeroAlloc.Outbox.Generator" Version="2.5.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="ZeroAlloc.Outbox.Generator" Version="2.5.0" />
<PackageReference Include="ZeroAlloc.Outbox.Generator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add ZeroAlloc.Outbox.Generator --version 2.5.0
#r "nuget: ZeroAlloc.Outbox.Generator, 2.5.0"
#:package ZeroAlloc.Outbox.Generator@2.5.0
#addin nuget:?package=ZeroAlloc.Outbox.Generator&version=2.5.0
#tool nuget:?package=ZeroAlloc.Outbox.Generator&version=2.5.0
ZeroAlloc.Outbox
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.Generatorpackage is still published for backwards compatibility with existing direct PackageReferences, but new consumers should reference onlyZeroAlloc.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
DbTransactionexplicitly. 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}/requeuePOST /outbox/api/messages/{id}/cancelPOST /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
- Actions —
Requeuea dead-lettered message ·Cancela pending one ·Force dispatchto run it now
| Tab | Screenshot |
|---|---|
| Pending — queue of messages awaiting first dispatch | ![]() |
| Retry — failed messages with back-off schedule | ![]() |
| Dead-lettered — exhausted retries with last error | ![]() |
| Dispatched — recently-succeeded history feeding the throughput chart | ![]() |
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/:
- Getting Started
- Outbox Pattern
- Message Types
- Dispatchers
- Store Adapters
- Background Worker
- Dependency Injection
- Diagnostics: ZO0001 · ZO0002 · ZO0003
License
MIT
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
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.5.0 | 20 | 5/14/2026 |
| 2.4.1 | 47 | 5/12/2026 |
| 2.4.0 | 85 | 5/4/2026 |
| 2.3.1 | 87 | 5/3/2026 |
| 2.3.0 | 85 | 5/1/2026 |
| 2.2.1 | 103 | 4/28/2026 |
| 2.2.0 | 90 | 4/28/2026 |
| 2.1.1 | 90 | 4/28/2026 |
| 2.1.0 | 96 | 4/26/2026 |
| 2.0.0 | 90 | 4/25/2026 |
| 1.3.0 | 87 | 4/25/2026 |
| 1.2.2 | 101 | 4/25/2026 |
| 1.2.1 | 88 | 4/24/2026 |
| 1.2.0 | 89 | 4/24/2026 |
| 1.1.2 | 101 | 4/23/2026 |
| 1.1.1 | 101 | 4/23/2026 |
| 1.1.0 | 95 | 4/22/2026 |
| 1.0.1 | 89 | 4/22/2026 |
| 1.0.0 | 101 | 4/20/2026 |
| 0.1.0 | 92 | 4/20/2026 |



