ZeroAlloc.Outbox.InMemory
2.3.0
dotnet add package ZeroAlloc.Outbox.InMemory --version 2.3.0
NuGet\Install-Package ZeroAlloc.Outbox.InMemory -Version 2.3.0
<PackageReference Include="ZeroAlloc.Outbox.InMemory" Version="2.3.0" />
<PackageVersion Include="ZeroAlloc.Outbox.InMemory" Version="2.3.0" />
<PackageReference Include="ZeroAlloc.Outbox.InMemory" />
paket add ZeroAlloc.Outbox.InMemory --version 2.3.0
#r "nuget: ZeroAlloc.Outbox.InMemory, 2.3.0"
#:package ZeroAlloc.Outbox.InMemory@2.3.0
#addin nuget:?package=ZeroAlloc.Outbox.InMemory&version=2.3.0
#tool nuget:?package=ZeroAlloc.Outbox.InMemory&version=2.3.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.
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
| Product | Versions 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. |
-
net10.0
- ZeroAlloc.Collections (>= 0.1.6)
- ZeroAlloc.Outbox (>= 2.3.0)
-
net8.0
- ZeroAlloc.Collections (>= 0.1.6)
- ZeroAlloc.Outbox (>= 2.3.0)
-
net9.0
- ZeroAlloc.Collections (>= 0.1.6)
- ZeroAlloc.Outbox (>= 2.3.0)
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.3.0 | 31 | 5/1/2026 |
| 2.2.1 | 79 | 4/28/2026 |
| 2.2.0 | 76 | 4/28/2026 |
| 2.1.1 | 78 | 4/28/2026 |
| 2.1.0 | 87 | 4/26/2026 |
| 2.0.0 | 90 | 4/25/2026 |
| 1.3.0 | 87 | 4/25/2026 |
| 1.2.2 | 92 | 4/25/2026 |
| 1.2.1 | 89 | 4/24/2026 |
| 1.2.0 | 90 | 4/24/2026 |
| 1.1.2 | 90 | 4/23/2026 |
| 1.1.1 | 86 | 4/23/2026 |
| 1.1.0 | 85 | 4/22/2026 |
| 1.0.1 | 82 | 4/22/2026 |
| 1.0.0 | 96 | 4/20/2026 |
| 0.1.0 | 90 | 4/20/2026 |