Outbox Pattern

1. The Problem

Imagine you're building an ASP.NET Core Order Service running in Azure.

When a customer places an order, the service needs to do two things:

  1. Save the order in Azure SQL Database.
  2. Publish an OrderCreated event to Azure Service Bus.

Other services depend on that event:

  • Payment Service starts payment processing.
  • Inventory Service reserves stock.
  • Notification Service sends the order confirmation.

At first glance, the flow looks simple:

Customer
   |
   v
ASP.NET Core Order Service
   |
   v
Azure SQL + Azure Service Bus

But there is a reliability problem.

The database and the message broker are two separate systems.

Suppose the Order Service does this:

  1. Save the order to Azure SQL.
  2. Commit the database transaction.
  3. Publish OrderCreated to Azure Service Bus.
  4. The application crashes before step 3.

Now you have:

  • Order exists in the database.
  • Event was never published.
  • Payment Service doesn't know about the order.
  • Inventory Service doesn't know about the order.
  • Notification Service doesn't send the confirmation.

The system is now inconsistent.

ASP.NET Core Order Service Messaging Azure Service Bus Azure SQL Database 1. Save Order 2. Publish OrderCreated Application crashes EVENT LOST

2. What's Actually Going Wrong?

The problem is not specific to Azure Service Bus.

The underlying problem is that the application is trying to make two separate systems behave like one atomic transaction.

The Azure SQL transaction can guarantee the database operation.

But the Azure SQL transaction does not automatically include the publish operation to Azure Service Bus.

Conceptually:

Azure SQL

“I can guarantee whether the database transaction commits.”

Azure Service Bus

“I can receive and process messages.”

But neither one can automatically make the entire operation behave like:

“Either both the database update and message publish succeed, or neither happens.”

Consider this sequence:

Save Order
   |
   v
Commit Azure SQL
   |
   v
Application crashes
   |
   v
Message never published

The order is now permanently stored, but the event is gone.

You might think:

“Then publish the message first.”

That creates the opposite problem.

Publish Event
   |
   v
Application crashes
   |
   v
Order never saved

Now other services have received an event for an order that doesn't actually exist.

So the real question is:

How can the Order Service reliably record both the business change and the event describing that change?

That's the problem the Outbox Pattern solves.


3. The Concept

The Outbox Pattern says:

Store the event in the same database transaction as the business data, then publish that event asynchronously.

Instead of immediately sending OrderCreated to Azure Service Bus, the ASP.NET Core Order Service first stores the event in an Outbox table in Azure SQL.

The database contains both:

  • the business data
  • the event that needs to be published

For example:

Orders table

IdCustomerIdAmount
123C456₹2,500

Outbox table

IdEvent TypeStatus
501OrderCreatedPending

The important part is that both records are created within the same database transaction.

Conceptually:

BEGIN TRANSACTION
  Save Order
  Save OrderCreated to Outbox
COMMIT

Now the system has a durable record of both the business operation and the event that must eventually be published.

ASP.NET Core Order Service SAME DATABASE TRANSACTION Table Orders Table Outbox Background worker Outbox Processor Messaging Azure Service Bus Payment Service Inventory Service Notification Service

4. How Does It Work?

Let's follow an actual order through the system.

Step 1: Customer places an order

The request reaches the ASP.NET Core Order Service.

For example:

POST /api/orders

The Order Service needs to:

  • create the order
  • notify other services that the order was created

Step 2: Save the order and event together

The application uses Entity Framework Core to persist the order and the Outbox event as part of the same Azure SQL transaction.

Conceptually:

BEGIN TRANSACTION
  Orders table: Order 123
  Outbox table: OrderCreated
COMMIT

The important thing is not the specific EF Core API being used.

The important thing is the transaction boundary:

Business data + Outbox event = one database transaction

If the transaction commits, both exist.

If the transaction rolls back, neither exists.

Step 3: The transaction commits

Now Azure SQL contains an Order (Order 123) and an Outbox Event (OrderCreated).

At this point, the application doesn't need to worry about whether Azure Service Bus is temporarily available.

The event has already been durably recorded.

Step 4: The Outbox Processor publishes the event

A background process periodically looks for pending Outbox records.

It finds OrderCreated.

It then publishes that event to Azure Service Bus.

Azure Service Bus can now deliver the event to the interested services.

Step 5: Downstream services react

The Payment Service receives OrderCreated.

The Inventory Service receives OrderCreated.

The Notification Service receives OrderCreated.

Each service can perform its own business operation independently.

This gives you a reliable flow:

Business operation
   |
   v
Durable Outbox record
   |
   v
Asynchronous message publishing
   |
   v
Downstream processing

5. Why Is This Better?

Without the Outbox Pattern:

Save Order --> Publish Event

There is a dangerous gap between those operations.

If the application crashes in that gap, the event can be lost.

With the Outbox Pattern:

Save Order + Save Event --> Commit --> Publish Later

The event is already safely stored before the application attempts to publish it.

The problem changes from:

“Can I successfully publish this event right now?”

to:

“Have I durably recorded that this event needs to be published?”

That is a much stronger reliability model.

The Outbox table effectively becomes a durable handoff point between the database transaction and the message broker.


6. What If Azure Service Bus Is Temporarily Unavailable?

Suppose the Order Service successfully commits the transaction:

Order saved       [OK]
Outbox event saved [OK]

But when the Outbox Processor tries to publish the event:

Azure Service Bus unavailable [X]

That's okay.

The event is still sitting safely in Azure SQL.

The Outbox Processor can retry later.

The important sequence is:

Azure SQL
  OrderCreated
     |
     v
Outbox Processor
  Attempt 1 -> Failed
  Attempt 2 -> Failed
  Attempt 3 -> Success
     |
     v
Azure Service Bus

This is one of the major benefits of the Outbox Pattern.

The event isn't dependent on the message broker being available at the exact moment the business transaction happens.

Azure SQL Order ✓ Outbox Event ✓ Background worker Outbox Processor Messaging Azure Service Bus Attempt 1 ✗ Retry Success ✓
The event survives because it was already persisted in the Outbox before Azure Service Bus was ever contacted.

7. What If the Outbox Processor Crashes After Publishing?

There is another important failure scenario.

Suppose the Outbox Processor successfully publishes OrderCreated to Azure Service Bus.

Then, immediately afterward, the processor crashes before marking the Outbox record as processed.

The sequence could be:

Outbox --> Azure Service Bus [OK] --> Processor crashes

When the processor starts again, it may see the same Outbox record and publish it again.

The consumer could therefore receive OrderCreated, and then OrderCreated again.

This means the Outbox Pattern generally provides at-least-once delivery, not guaranteed exactly-once processing.

That's an important distinction.

The Outbox Pattern solves:

Don't lose the event.

It does not automatically solve:

Make sure the event can never be delivered twice.

This is why downstream consumers should ideally be idempotent.

For example, if the Payment Service receives the same OrderCreated event twice, it should not accidentally charge the customer twice.

A common approach is for the consumer to keep track of processed event IDs or use another business-level idempotency mechanism.

The exact implementation depends on the service and business requirement.


8. When Should You Use It?

The Outbox Pattern is particularly useful when an ASP.NET Core service needs to:

  • Update business data in a database.
  • Publish an event because of that business change.
  • Reliably communicate with other services.
  • Continue working correctly even when the message broker is temporarily unavailable.
  • Avoid losing events between the database commit and message publication.

Typical examples include:

Order Created
   +--> Payment
   +--> Inventory
   +--> Notification

Customer Registered
   +--> Welcome Email
   +--> Customer Profile
   +--> Analytics

Document Signed
   +--> Audit
   +--> Notification
   +--> Workflow

The pattern is especially valuable in event-driven and microservices architectures, where one business operation often needs to trigger work in multiple independent services.


9. What Happens If You Don't Use It?

A common implementation looks like this:

ASP.NET Core Order Service
   |
   v
Save Order to Azure SQL
   |
   v
Publish OrderCreated to Azure Service Bus

It may appear perfectly fine during normal operation.

The problem only becomes visible during failures.

For example:

Save Order        [OK]
Commit Database    [OK]
Application crashes [X]
Publish Event never happens

No retry mechanism inside the crashed application can recover an event that was never durably recorded.

This is the fundamental difference.

Without an Outbox:

The event exists only in the application's execution flow until it is published.

With an Outbox:

The event is durably stored before publication is attempted.

10. Visualize It in Everyday Terms

Imagine a restaurant taking a large catering order.

The restaurant needs to do two things:

  1. Record the customer's order.
  2. Tell the kitchen to prepare it.

A staff member could write the order in the official order book and then immediately run to the kitchen.

But what if the staff member gets interrupted after writing the order?

The order exists in the book, but the kitchen never hears about it.

Instead, imagine the restaurant has a dispatch tray.

When the order is accepted, the restaurant does two things together:

  • records the order in the official order book
  • puts a dispatch slip in the dispatch tray

Later, another staff member checks the tray and takes the dispatch slips to the kitchen.

If the kitchen is temporarily unavailable, the dispatch slip stays in the tray.

It can be delivered later.

Mapping the analogy

  • Restaurant order book = Azure SQL business tables
  • Customer order = Order entity
  • Dispatch tray = Outbox table
  • Dispatch slip = Outbox event
  • Staff member checking the tray = Outbox Processor
  • Kitchen = Azure Service Bus and downstream consumers
  • Recording the order + putting the dispatch slip in the tray together = same database transaction
  • Checking the tray later = asynchronous publishing
  • Trying again after failure = retry
  • Same dispatch slip being delivered twice = duplicate message

So the Outbox Pattern is essentially:

Record what happened and what needs to be communicated together, then communicate it asynchronously.

11. What Should You Remember?

If you remember only one thing, remember this:

Don't try to atomically update your database and publish to your message broker. Store the event in the database first, in the same transaction as the business change.

The mental model is:

ASP.NET Core Order Service
   |
   v
Azure SQL (Order + Outbox Event)
   |
   v
Outbox Processor
   |
   v
Azure Service Bus
   |
   v
Other Services

The key points are:

  • Business data and the Outbox event are saved in the same database transaction.
  • The event is stored in an Outbox table.
  • A background process publishes Outbox events asynchronously.
  • If the application crashes after the transaction commits, the event remains safely stored.
  • If Azure Service Bus is temporarily unavailable, the event can be retried.
  • Publishing can result in duplicates.
  • Consumers should therefore be designed to handle duplicate events safely.
  • The Outbox Pattern solves reliable event publication, not every distributed-systems problem.

The simplest mental model is:

Database first. Message second.

12. .NET + Azure Context

Application: ASP.NET Core

Data access: Entity Framework Core

Database: Azure SQL Database (business data + Outbox table)

Background processing: A dedicated Outbox Processor / worker

Messaging: Azure Service Bus

Consumer design: Idempotent consumers, safe against duplicate delivery

The architecture looks like:

ASP.NET Core
   |
   v
Entity Framework Core
   |
   v
Azure SQL
   |
   v
Outbox Processor
   |
   v
Azure Service Bus
   |
   v
Payment / Inventory / Notification Services

The important thing is that the technology can change while the architectural principle remains the same.

You could replace Azure SQL with another relational database or Azure Service Bus with another message broker.

The Outbox Pattern is still the same:

Business Change + Event
   |
   v
Same Database Transaction
   |
   v
Durably Stored
   |
   v
Publish Asynchronously
   |
   v
Retry if Necessary

The pattern gives you a reliable bridge between transactional data and asynchronous messaging.

That's the Outbox Pattern.