SquirrelBox.Messaging.Pigeon
2.1.0
dotnet add package SquirrelBox.Messaging.Pigeon --version 2.1.0
NuGet\Install-Package SquirrelBox.Messaging.Pigeon -Version 2.1.0
<PackageReference Include="SquirrelBox.Messaging.Pigeon" Version="2.1.0" />
<PackageVersion Include="SquirrelBox.Messaging.Pigeon" Version="2.1.0" />
<PackageReference Include="SquirrelBox.Messaging.Pigeon" />
paket add SquirrelBox.Messaging.Pigeon --version 2.1.0
#r "nuget: SquirrelBox.Messaging.Pigeon, 2.1.0"
#:package SquirrelBox.Messaging.Pigeon@2.1.0
#addin nuget:?package=SquirrelBox.Messaging.Pigeon&version=2.1.0
#tool nuget:?package=SquirrelBox.Messaging.Pigeon&version=2.1.0
SquirrelBox
SquirrelBox is the Elysium inbox/outbox toolkit for .NET services.
It protects incoming work from duplicate execution with the inbox pattern, persists outgoing work with the outbox pattern, and uses Mule durable actions for deferred execution. The core is transport-neutral: HTTP, messaging, Pigeon, custom transports, and dashboard diagnostics all use the same model.
Packages
dotnet add package SquirrelBox
dotnet add package SquirrelBox.InMemory
dotnet add package SquirrelBox.EntityFrameworkCore
dotnet add package SquirrelBox.AspNetCore
dotnet add package SquirrelBox.AspNetCore.Dashboard
dotnet add package SquirrelBox.Messaging
dotnet add package SquirrelBox.Messaging.Pigeon
dotnet add package SquirrelBox.Mule
Package reference example:
<PackageReference Include="SquirrelBox" Version="2.1.0" />
<PackageReference Include="SquirrelBox.AspNetCore" Version="2.1.0" />
<PackageReference Include="SquirrelBox.AspNetCore.Dashboard" Version="2.1.0" />
<PackageReference Include="SquirrelBox.EntityFrameworkCore" Version="2.1.0" />
<PackageReference Include="SquirrelBox.Mule" Version="2.1.0" />
Getting Started
Register core services, choose storage, and add Mule when you want durable deferred execution:
using Mule.InMemory;
using SquirrelBox;
using SquirrelBox.EntityFrameworkCore;
using SquirrelBox.Mule;
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
services
.AddSquirrelBox(options =>
{
options.DefaultEntryLifetime = TimeSpan.FromHours(24);
options.AllowPayloadHashAsIdempotencyKey = true;
options.ScanAssemblyContaining<OrdersFingerprintProfile>();
})
.UseEntityFramework<AppDbContext>();
services.AddSquirrelBoxMule();
services.AddMule(mule => mule
.UseInMemory()
.AddActionsFromAssemblyContaining<SquirrelBoxOutboxMuleAction>());
SquirrelBox augments the registered AppDbContext automatically. Your application DbContext
does not need SquirrelBox DbSet properties or OnModelCreating changes.
Use UseInMemory() instead of EF for tests, samples, and local development.
Inbox
The inbox pattern identifies incoming work by:
Source + Operation + IdempotencyKey
Basic usage:
var open = await inbox.OpenOrContinueAsync(
InboxOpenRequest.For(
source: "http",
operation: "POST /orders",
idempotencyKey: "client-key-1",
payload: request),
cancellationToken);
if (!open.Accepted)
return;
try
{
await handler.Handle(request, cancellationToken);
await inbox.CompleteCurrentAsync(cancellationToken: cancellationToken);
}
catch (Exception ex)
{
await inbox.FailCurrentAsync(ex, cancellationToken);
throw;
}
Entries use ULID ids and move through Started, Completed, Failed, and Expired.
Declared Operations
Declared operations are the durable-safe path for switching HTTP or other synchronous entry points between inline and deferred execution.
public sealed class CreateOrderOperation
: SquirrelBoxOperation<CreateOrderRequest, OrderCreated>
{
protected override async ValueTask<OrderCreated> ExecuteAsync(
CreateOrderRequest request,
SquirrelBoxOperationContext context,
CancellationToken cancellationToken)
{
var service = context.Services.GetRequiredService<IOrderService>();
return await service.CreateAsync(request, cancellationToken);
}
}
Invoke from an endpoint:
var result = await operations
.ExecuteAsync<CreateOrderOperation, CreateOrderRequest, OrderCreated>(
request,
cancellationToken);
if (result.Deferred)
return Results.Accepted($"/inbox/{result.Context.Entry.Id}");
if (result.Executed)
return Results.Created($"/orders/{result.Result.Id}", result.Result);
return Results.Conflict(result.Decision);
When execution is deferred, SquirrelBox stores the inbox entry first, schedules a Mule durable action, and completes/fails the inbox later from a worker scope.
Outbox
The outbox pattern persists outgoing work before it is delivered by a transport.
var envelope = await outbox.EnqueueAsync(new OutboxEnqueueRequest
{
Transport = "pigeon",
Operation = "orders.created",
Destination = "orders",
Payload = new OrderCreated(orderId),
CorrelationId = correlationId
}, cancellationToken);
SquirrelBox stores an OutboxEnvelope with:
Ulid Id
Transport
Operation
Destination
PayloadType
Payload
Headers
Metadata
CorrelationId
Status
Mule later executes squirrelbox.outbox.publish.v1, loads the envelope by ULID, resolves the matching IOutboxTransportPublisher, and marks the envelope as Published or Failed.
Custom publisher:
public sealed class MyPublisher : IOutboxTransportPublisher
{
public string Transport => "my-transport";
public async ValueTask<OutboxPublishResult> PublishAsync(
OutboxEnvelope envelope,
CancellationToken cancellationToken = default)
{
await client.SendAsync(envelope.Payload, cancellationToken);
return OutboxPublishResult.Success;
}
}
Profiles
Inbox fingerprints and outbox profiles are discovered from scanned assemblies.
public sealed class OrdersFingerprintProfile : InboxFingerprintProfile
{
public override void Configure(InboxFingerprintProfileBuilder builder)
{
builder.For<CreateOrderRequest>()
.Use(request => new
{
request.CustomerId,
request.ExternalOrderId,
request.Amount
});
}
}
Outbox profiles can customize how enqueue requests become durable envelopes without adding noisy fluent configuration.
ASP.NET Core
Add HTTP idempotency:
using SquirrelBox.AspNetCore;
services.AddSquirrelBoxAspNetCore(options =>
{
options.RequestHeaderNames.Clear();
options.RequestHeaderNames.Add("Idempotency-Key");
options.ResponseHeaderName = "Idempotency-Key";
options.CaptureCompletedResponses = true;
options.ReplayCompletedResponses = true;
});
app.UseSquirrelBox();
If a request has an idempotency header, middleware reserves the inbox entry before the endpoint runs. If the request does not have a header, your endpoint can open SquirrelBox after model binding so the computed key is based on the DTO instead of raw body bytes.
Dashboard
Add the event-driven dashboard:
using SquirrelBox.AspNetCore.Dashboard;
services.AddSquirrelBoxDashboard(options =>
{
options.Authentication.RootUser.Username = "admin";
options.Authentication.RootUser.Password = "<from-secret-store>";
});
app.MapSquirrelBoxDashboard("/squirrelbox");
The dashboard:
- Loads initial Inbox/Outbox history from storage.
- Receives live events through Server-Sent Events.
- Does not poll the database.
- Shows Inbox, Outbox, Deferred Work, and live Events.
- Uses the SquirrelBox logo palette.
- Is closed by default: root user, ASP.NET Core auth, or custom auth must be configured.
ASP.NET Core auth mode:
services.AddSquirrelBoxDashboard(options =>
{
options.Authentication.Mode = SquirrelBoxDashboardAuthenticationMode.AspNetCoreAuthentication;
});
app.MapSquirrelBoxDashboard("/squirrelbox")
.RequireAuthorization("SquirrelBoxDashboard");
Custom auth mode:
services.AddSingleton<ISquirrelBoxDashboardAuthenticator, MyDashboardAuthenticator>();
services.AddSquirrelBoxDashboard(options =>
{
options.Authentication.Mode = SquirrelBoxDashboardAuthenticationMode.Custom;
});
Messaging
Use SquirrelBox.Messaging when building a transport adapter or orchestration layer:
var result = await messages.OpenAsync(new InboxMessageContext
{
Transport = "rabbitmq",
Topic = "orders",
Version = "1.0.0",
Subscription = "billing",
Operation = "created",
MessageId = messageId,
Payload = payload,
Metadata = metadata
});
if (result.ShouldExecute)
{
await consumer.Handle(payload, cancellationToken);
await inbox.CompleteCurrentAsync(cancellationToken: cancellationToken);
}
messages.AttachEffectiveKey(replyMetadata);
The default operation shape is:
topic:version/subscription/operation
Pigeon
SquirrelBox.Messaging.Pigeon targets Pigeon 4.0.0 and integrates with consume and publish interceptors.
services.AddSquirrelBoxPigeon(options =>
{
options.Transport = "pigeon";
options.EnableOutbox = true;
options.ExecutionModeResolver = context =>
context.Topic == "orders.deferred"
? InboxExecutionMode.Deferred
: InboxExecutionMode.Inline;
});
Consume:
- Decision interceptor opens the inbox before the consumer handler.
- Execution interceptor completes or fails the inbox after the handler.
- Deferred replay uses
IPigeonConsumerInvoker.
Publish:
- Publish decision interceptor persists Pigeon's prepared
PigeonPublishEnvelopein SquirrelBox Outbox. - Pigeon publish is skipped inline after the envelope is durable.
- Mule later publishes through
IPigeonPublisherInvokerwithout rerunning producer interceptors, publish decision interceptors, or Pigeon's internal outbox logic. - Normal and raw publish flows are supported.
Mule
Register SquirrelBox actions with Mule:
services.AddSquirrelBoxMule();
services.AddMule(mule => mule
.UseInMemory()
.AddActionsFromAssemblyContaining<SquirrelBoxOutboxMuleAction>()
.AddActionsFromAssemblyContaining<SquirrelBoxPigeonMuleAction>());
SquirrelBox attaches durable metadata such as squirrelbox-inbox-id, squirrelbox-outbox-id, and idempotency-key.
Sample
Run the sample app:
dotnet run --project samples/SquirrelBox.Sample/SquirrelBox.Sample.csproj --urls http://127.0.0.1:5188
Try:
curl -i -X POST http://127.0.0.1:5188/orders/inline \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inline-key-1" \
-d "{\"customerId\":\"cust-1\",\"externalOrderId\":\"inline-1\",\"amount\":42.5}"
curl -i -X POST http://127.0.0.1:5188/orders/deferred \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deferred-key-1" \
-d "{\"customerId\":\"cust-3\",\"externalOrderId\":\"deferred-1\",\"amount\":99.99}"
curl -i -X POST http://127.0.0.1:5188/pigeon/inline \
-H "Content-Type: application/json" \
-d "{\"orderId\":\"pigeon-inline-1\",\"customerId\":\"cust-4\",\"amount\":12.50}"
curl -i -X POST http://127.0.0.1:5188/outbox/direct \
-H "Content-Type: application/json" \
-d "{\"orderId\":\"audit-1\",\"reason\":\"manual-check\"}"
Open the dashboard at http://127.0.0.1:5188/squirrelbox with admin / secret.
Testing
The test suite covers:
- Core inbox lifecycle, policies, fingerprints, operation execution, and outbox publication.
- In-memory inbox/outbox storage.
- ASP.NET Core idempotency and dashboard auth/state.
- Messaging and Pigeon consume/publish adapters.
- Mule deferred inbox and outbox execution.
- EF Core SQL Server e2e tests using Docker.
Run everything:
dotnet test SquirrelBox.slnx -c Debug
| 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
- Pigeon.Messaging (>= 4.0.0)
- SquirrelBox (>= 2.1.0)
- SquirrelBox.Messaging (>= 2.1.0)
- SquirrelBox.Mule (>= 2.1.0)
-
net8.0
- Pigeon.Messaging (>= 4.0.0)
- SquirrelBox (>= 2.1.0)
- SquirrelBox.Messaging (>= 2.1.0)
- SquirrelBox.Mule (>= 2.1.0)
-
net9.0
- Pigeon.Messaging (>= 4.0.0)
- SquirrelBox (>= 2.1.0)
- SquirrelBox.Messaging (>= 2.1.0)
- SquirrelBox.Mule (>= 2.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.