OutboxNet.SqlServer 8.0.2

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package OutboxNet.SqlServer --version 8.0.2
                    
NuGet\Install-Package OutboxNet.SqlServer -Version 8.0.2
                    
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="OutboxNet.SqlServer" Version="8.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OutboxNet.SqlServer" Version="8.0.2" />
                    
Directory.Packages.props
<PackageReference Include="OutboxNet.SqlServer" />
                    
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 OutboxNet.SqlServer --version 8.0.2
                    
#r "nuget: OutboxNet.SqlServer, 8.0.2"
                    
#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 OutboxNet.SqlServer@8.0.2
                    
#: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=OutboxNet.SqlServer&version=8.0.2
                    
Install as a Cake Addin
#tool nuget:?package=OutboxNet.SqlServer&version=8.0.2
                    
Install as a Cake Tool

OutboxNet

CI NuGet License: MIT

A transactional outbox library for .NET that guarantees reliable webhook delivery in distributed systems. OutboxNet ensures that when your application writes data and needs to notify external systems, either both happen or neither does — eliminating the class of bugs where your database commits but the notification is silently lost.

The Problem

In distributed systems, the dual-write problem occurs when a service needs to update two different systems (e.g., a database and a webhook endpoint) and there is no built-in way to guarantee both succeed or both fail.

1. Save order to database      ✅ succeeds
2. Send webhook to payment svc  ❌ app crashes / network timeout / partial failure
→ Order exists but payment never initiated

This isn't an edge case — in production systems handling thousands of requests, these failures happen daily.

The Solution: Transactional Outbox Pattern

1. BEGIN TRANSACTION
2.   Save order to database
3.   Write outbox message to OutboxMessages table (same DB, same transaction)
4. COMMIT TRANSACTION
5. Background processor picks up outbox messages and delivers webhooks
6. On success → mark delivered | On failure → retry with backoff | On exhaustion → dead-letter

By writing the outbox message in the same database transaction as your domain data, you get atomicity for free.

Packages

Package Description
OutboxNet.Core Core contracts, models, options, observability
OutboxNet.EntityFrameworkCore EF Core + SQL Server stores and publisher
OutboxNet.SqlServer Direct ADO.NET SQL Server stores and publisher (no EF dependency)
OutboxNet.Processor Background hosted service for outbox processing
OutboxNet.Delivery HTTP webhook delivery with HMAC-SHA256 signing and retry
OutboxNet.AzureStorageQueue Azure Storage Queue publisher for queue-mediated processing
OutboxNet.AzureFunctions Azure Functions timer trigger for serverless processing

Getting Started

Step 1: Install packages

Choose your persistence provider and install the required packages.

EF Core app (most common):

dotnet add package OutboxNet.Core
dotnet add package OutboxNet.EntityFrameworkCore
dotnet add package OutboxNet.Processor
dotnet add package OutboxNet.Delivery

Direct ADO.NET / Dapper app:

dotnet add package OutboxNet.Core
dotnet add package OutboxNet.SqlServer
dotnet add package OutboxNet.Processor
dotnet add package OutboxNet.Delivery

Azure Functions (serverless):

dotnet add package OutboxNet.Core
dotnet add package OutboxNet.EntityFrameworkCore  # or OutboxNet.SqlServer
dotnet add package OutboxNet.AzureFunctions
dotnet add package OutboxNet.Delivery

Step 2: Configure services

Option A: Entity Framework Core

// Program.cs
var connectionString = builder.Configuration.GetConnectionString("Default");

builder.Services.AddOutboxNet(options =>
{
    options.SchemaName = "outbox";
    options.BatchSize = 50;
    options.DefaultVisibilityTimeout = TimeSpan.FromSeconds(60);
    options.MaxConcurrentDeliveries = 10;
})
.UseSqlServer<AppDbContext>(connectionString, sql =>
{
    sql.MigrationsAssembly = "MyApp.Migrations";
})
.AddProcessor()
.AddWebhookDelivery();

Option B: Direct SQL Server

// Program.cs
var connectionString = builder.Configuration.GetConnectionString("Default");

builder.Services.AddOutboxNet(options =>
{
    options.SchemaName = "outbox";
    options.BatchSize = 50;
})
.UseDirectSqlServer(connectionString)
.AddProcessor()
.AddWebhookDelivery();

// Register your transaction accessor for the publisher
builder.Services.AddScoped<ISqlTransactionAccessor, MySqlTransactionAccessor>();

Step 3: Set up the database

EF Core — apply outbox table configurations in your DbContext:

public class AppDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyOutboxConfigurations(schema: "outbox");
        // ... your own entity configurations
    }
}

Then generate and apply migrations:

dotnet ef migrations add AddOutbox --context AppDbContext
dotnet ef database update

Direct SQL — create the tables manually:

OutboxNet uses three tables: OutboxMessages, WebhookSubscriptions, and DeliveryAttempts. See the EF Core entity configurations in OutboxNet.EntityFrameworkCore/Configurations/ for the exact column definitions, or generate the SQL from a temporary EF Core migration.

Step 4: Register webhook subscriptions

Insert subscription rows into the WebhookSubscriptions table for each event type and endpoint:

Column Example
EventType order.placed
WebhookUrl https://payment-svc.internal/webhooks
Secret whsec_abc123... (used for HMAC signing)
IsActive true

Step 5: Publish outbox messages

EF Core publisher — writes in the same transaction as your domain data:

public class PlaceOrderHandler
{
    private readonly AppDbContext _db;
    private readonly IOutboxPublisher _outbox;

    public PlaceOrderHandler(AppDbContext db, IOutboxPublisher outbox)
    {
        _db = db;
        _outbox = outbox;
    }

    public async Task Handle(PlaceOrderCommand cmd, CancellationToken ct)
    {
        await using var transaction = await _db.Database.BeginTransactionAsync(ct);

        var order = new Order { /* ... */ };
        _db.Orders.Add(order);
        await _db.SaveChangesAsync(ct);

        // This INSERT goes into the SAME transaction
        await _outbox.PublishAsync(
            eventType: "order.placed",
            payload: new { order.Id, order.Total, order.CustomerId },
            correlationId: cmd.CorrelationId,
            cancellationToken: ct);

        await transaction.CommitAsync(ct);
        // If commit fails → both order AND outbox message are rolled back
        // If commit succeeds → background processor delivers the webhook
    }
}

Direct SQL publisher — uses ISqlTransactionAccessor:

public class MySqlTransactionAccessor : ISqlTransactionAccessor
{
    public SqlConnection Connection { get; set; } = default!;
    public SqlTransaction Transaction { get; set; } = default!;
}

public class PlaceOrderHandler
{
    private readonly IOutboxPublisher _outbox;
    private readonly MySqlTransactionAccessor _txAccessor;
    private readonly string _connectionString;

    public async Task Handle(PlaceOrderCommand cmd, CancellationToken ct)
    {
        await using var connection = new SqlConnection(_connectionString);
        await connection.OpenAsync(ct);
        await using var transaction = connection.BeginTransaction();

        // Your domain write (Dapper, raw ADO.NET, etc.)
        await connection.ExecuteAsync(
            "INSERT INTO Orders ...",
            new { /* ... */ },
            transaction);

        // Provide connection/transaction to the outbox publisher
        _txAccessor.Connection = connection;
        _txAccessor.Transaction = transaction;

        await _outbox.PublishAsync(
            eventType: "order.placed",
            payload: new { cmd.OrderId, cmd.Total },
            cancellationToken: ct);

        await transaction.CommitAsync(ct);
    }
}

Step 6: Run the processor

The background processor starts automatically as a hosted service when you call .AddProcessor(). It polls for pending outbox messages, locks them, delivers webhooks, and handles retries.

No additional configuration is required — the processor runs as long as your application is running.

How It Works

┌─────────────────────────────────────────────────────────────────────┐
│                        Your Application                            │
│                                                                     │
│  ┌──────────────┐    ┌──────────────────┐    ┌──────────────────┐  │
│  │ Domain Logic  │───>│ IOutboxPublisher  │───>│   SQL Server DB  │  │
│  │ (e.g. Order)  │    │ (same transaction)│    │  ┌────────────┐ │  │
│  └──────────────┘    └──────────────────┘    │  │  Orders     │ │  │
│                                               │  │  OutboxMsgs │ │  │
│                                               │  └────────────┘ │  │
│                                               └────────┬────────┘  │
│                                                         │          │
│  ┌─────────────────────────────────────────────────────┐│          │
│  │           Background Processor                       ││          │
│  │  ┌────────────┐  ┌──────────────┐  ┌─────────────┐ ││          │
│  │  │ IOutboxStore│─>│ Delivery     │─>│ Retry Policy│ ││          │
│  │  │ (lock batch)│  │ (HTTP+HMAC)  │  │ (exp backoff│ ││          │
│  │  └────────────┘  └──────┬───────┘  └─────────────┘ ││          │
│  └──────────────────────────┼──────────────────────────┘│          │
│                              │                           │          │
└──────────────────────────────┼───────────────────────────┘          │
                               │                                      │
                    ┌──────────▼──────────┐                           │
                    │  External Webhooks   │                           │
                    │  • Payment Service   │                           │
                    │  • Inventory Service │                           │
                    │  • Analytics         │                           │
                    └─────────────────────┘

Key Features

  • Transactional guarantee — outbox writes participate in your existing database transaction
  • Multi-instance safe — visibility timeout + instance-level locking prevents duplicate processing
  • HMAC-SHA256 webhook signing — receivers can verify payload authenticity
  • Exponential backoff retries — with jitter to avoid thundering herd
  • Dead-letter queue — exhausted messages are dead-lettered for manual review
  • Per-subscription settings — independent timeout, retry, and header configuration per endpoint
  • Observability — built-in OpenTelemetry ActivitySource and System.Diagnostics.Metrics
  • Two SQL Server providers — EF Core for convenience, direct ADO.NET for minimal overhead

Configuration Reference

builder.Services.AddOutboxNet(options =>
{
    options.SchemaName = "outbox";                                // SQL schema name (default: "outbox")
    options.BatchSize = 50;                                       // Messages per processing cycle
    options.DefaultVisibilityTimeout = TimeSpan.FromSeconds(60);  // Lock duration per message
    options.MaxConcurrentDeliveries = 10;                         // Parallel webhook deliveries
    options.ProcessingMode = ProcessingMode.DirectDelivery;       // or QueueMediated
    // InstanceId is auto-generated as "{MachineName}-{Guid}" for uniqueness
});

Which SQL Server Package?

If you... Use
Already use EF Core and want migrations + DbContext integration OutboxNet.EntityFrameworkCore
Use Dapper, raw ADO.NET, or want zero EF Core overhead OutboxNet.SqlServer
Want the lightest possible dependency footprint OutboxNet.SqlServer
Need outbox writes in the same transaction as your EF Core SaveChangesAsync OutboxNet.EntityFrameworkCore

Publishing to NuGet

Automated (GitHub Actions)

This repository includes a GitHub Actions workflow that automatically publishes all packages to NuGet when you create a GitHub release.

One-time setup:

  1. Get your NuGet API key from nuget.org/account/apikeys
    • Select Push and Push new packages and package versions scopes
    • Set the glob pattern to OutboxNet.*
  2. Add the key as a repository secret:
    • Go to your repo Settings > Secrets and variables > Actions
    • Create a secret named NUGET_API_KEY with your API key

To publish a new version:

  1. Create a GitHub release with a tag matching the desired version (e.g., 1.0.0, 1.2.0-preview.1)
  2. The workflow automatically:
    • Builds and tests
    • Packs all 7 packages with the release tag as the version
    • Pushes .nupkg and .snupkg (symbols) to nuget.org

Manual (local)

# Pack all projects
dotnet pack -c Release -o ./nupkgs /p:Version=1.0.0

# Push to NuGet (replace YOUR_API_KEY)
dotnet nuget push ./nupkgs/*.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate

Versioning

The version is set in Directory.Build.props (<Version>1.0.0</Version>). All packages share the same version. When publishing via GitHub Actions, the release tag overrides this version.

Project Structure

OutboxNet/
├── src/
│   ├── OutboxNet.Core/                    # Contracts, models, options, observability
│   ├── OutboxNet.EntityFrameworkCore/     # EF Core + SQL Server stores & publisher
│   ├── OutboxNet.SqlServer/              # Direct ADO.NET SQL Server stores & publisher
│   ├── OutboxNet.Processor/              # Background processing hosted service
│   ├── OutboxNet.Delivery/               # HTTP webhook delivery + HMAC + retry
│   ├── OutboxNet.AzureStorageQueue/      # Azure Storage Queue transport
│   └── OutboxNet.AzureFunctions/         # Azure Functions timer trigger
├── tests/
│   ├── OutboxNet.Core.Tests/
│   ├── OutboxNet.Delivery.Tests/
│   └── OutboxNet.Processor.Tests/
├── docs/
│   └── architecture.md                    # Detailed architecture documentation
├── Directory.Build.props                  # Shared build + NuGet package properties
├── Directory.Packages.props               # Centralized package version management
└── .github/workflows/
    ├── ci.yml                             # Build + test on every push/PR
    └── publish.yml                        # Publish to NuGet on GitHub release

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 was computed.  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 was computed.  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