# Outbox Foundation

## Goal

Coordinate outgoing messages with successful business work using the appropriate durability level.

## Agent enforcement rules

### Do

- Choose retry or redelivery only for transient failures.
- Keep persistence updates durable before publish/send side effects.
- Keep handlers idempotent for replay safety.

### Do not

- Do not publish or send before durable state changes.
- Do not treat outbox as a replacement for idempotent business logic.
- Do not apply outbox patterns partially where end-to-end consistency is required.

## Build checklist

- Use the In-Memory Outbox to buffer messages produced by a consumer until that attempt succeeds. It is not durable across a process failure.
- Use the Transactional Consumer Outbox when consumer state, duplicate detection, and outgoing messages must be stored together.
- Use the Transactional Bus Outbox when an API or application scope writes state and publishes/sends messages together.
- Configure retry or redelivery only for transient failures, and keep handlers idempotent for replay safety.

## Example: in-memory outbox on receive endpoint

```csharp
cfg.ReceiveEndpoint("saga-state", e =>
{
    e.UseMessageRetry(r => r.Immediate(3));
    e.UseInMemoryOutbox(context);

    e.ConfigureSaga<SagaState>(context);
});
```

## Example: Entity Framework Consumer and Bus Outbox

```csharp
services.AddMassTransit(x =>
{
    x.AddEntityFrameworkOutbox<RegistrationDbContext>(o =>
    {
        o.UsePostgres();
        o.UseBusOutbox(); // Bus Outbox for scoped application publishing/sending
    });

    x.AddConfigureEndpointsCallback((context, name, cfg) =>
    {
        cfg.UseMessageRetry(r => r.Immediate(3)); // Only for transient failures
        cfg.UseEntityFrameworkOutbox<RegistrationDbContext>(context);
    });
});
```

And in your DbContext model configuration:

```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.AddInboxStateEntity();
    modelBuilder.AddOutboxMessageEntity();
    modelBuilder.AddOutboxStateEntity();
}
```

## Example: MongoDB transactional outbox

```csharp
services.AddMassTransit(x =>
{
    x.AddMongoDbOutbox(o =>
    {
        o.QueryDelay = TimeSpan.FromSeconds(1);
        o.ClientFactory(provider => provider.GetRequiredService<IMongoClient>());
        o.DatabaseFactory(provider => provider.GetRequiredService<IMongoDatabase>());
        o.DuplicateDetectionWindow = TimeSpan.FromSeconds(30);
        o.UseBusOutbox();
    });
});
```

## Guardrails

- Do not publish/send before state changes are durable.
- Do not rely on outbox to replace idempotent business logic.
- Do not assume an In-Memory Outbox survives a process failure after state is committed.
- Do not configure retry or redelivery for business failures that cannot succeed on another attempt.

## Verification

- `dotnet build`
- `dotnet test --filter Outbox`
- `dotnet test --filter Retry`

## References

- [Outbox (Concepts)](/concepts/outbox)
- [Outbox Middleware Configuration](/configuration/middleware/outbox)
- [Retry Middleware Configuration](/configuration/middleware/retry)
- [Redelivery Middleware Configuration](/configuration/middleware/redelivery)
