LiteBus
Reliable messaging

Reliable Messaging

Read the Event Module for publish-subscribe, then The Handler Pipeline to understand the stages all three message types share.

In-process mediation runs handlers in the current call. If the process crashes mid-handler, that work is lost. The inbox and outbox add durability: they persist a message to a store first, then a background processor executes or publishes it with at-least-once delivery, leases, retries, and idempotency.

See Reliable messaging semantics for delivery guarantees and broker comparison.

Note: Inbox and outbox behavior is opt-in. Plain SendAsync, QueryAsync, and PublishAsync calls remain in process and do not access a durable store.

In-Process Versus Durable

PathAPIExecutionSurvives a crash
In-process commandICommandMediator.SendAsyncNow, in the callerNo
Durable message (inbox)IInbox.AcceptAsyncLater, by PipelinedInboxProcessorYes
In-process eventIEventMediator.PublishAsyncNow, in the callerNo
Durable message (outbox)IOutbox.EnqueueAsyncLater, by PipelinedOutboxProcessorYes

Durable APIs return a receipt that confirms storage, not a business result.

Commands with results (ICommand<TResult>) cannot be stored in the inbox; analyzer LB1004 reports that at compile time.

The Inbox: Deferred Execution

The inbox stores registered message types and replays them through PipelinedInboxProcessor and IInboxDispatcher. See Inbox.

ConcernPackage examplesRole
CoreLiteBus.InboxIInbox, IInboxProcessor, contracts
StorageLiteBus.Inbox.Storage.PostgreSql, *.InMemory, *.EntityFrameworkCoreStore roles
DispatchLiteBus.Inbox.Dispatch.InProcess, LiteBus.Inbox.Dispatch.Amqp, LiteBus.Inbox.Dispatch.InMemoryUseInProcessDispatch, UseAmqpDispatch, UseInMemoryDispatch
IngressLiteBus.Inbox.Ingress.AmqpBroker to IInbox.AcceptAsync

The Outbox: Reliable Publication

The outbox stores a message after a state change commits, then PipelinedOutboxProcessor publishes through IOutboxDispatcher. See Outbox.

ConcernPackage examplesRole
CoreLiteBus.OutboxIOutbox, IOutboxProcessor
StorageLiteBus.Outbox.Storage.PostgreSql, *.InMemory, *.EntityFrameworkCoreStore roles
DispatchLiteBus.Outbox.Dispatch.InProcess, LiteBus.Outbox.Dispatch.Amqp, LiteBus.Outbox.Dispatch.InMemoryUseInProcessDispatch, UseAmqpDispatch, UseInMemoryDispatch

Transactional Writes (Domain + Store)

IInbox.AcceptAsync and IOutbox.EnqueueAsync commit immediately. That suits ingress and standalone storage.

When a command handler must persist domain state and inbox/outbox rows in one database transaction, use ITransactionalInbox / ITransactionalOutbox (PostgreSQL) or ITransactionalInbox<TContext> / ITransactionalOutbox<TContext> (EF Core). Registration, Marten enlistment, and API choice: Transactional messaging writes.

Delivery Semantics

LiteBus durable messaging provides at-least-once execution and publication, not exactly-once side effects.

ScenarioRiskMitigation
Worker crash after handler runs, before terminal persistDuplicate execution on lease reclaimIdempotent handlers; IdempotencyKey on accept
Worker crash after broker publish, before outbox persistDuplicate publicationConsumer deduplication; outbox idempotency key
Lease expiry during slow handlerSecond worker dispatches same rowHeartbeat renewal; handler idempotency
Shutdown with HonorShutdownTokenOnPersist = trueCanceled terminal persistDefault false; document operator drain policy
In-memory storesProcess-wide onlyNot cross-process; tests and single-node dev
In-memory transport consumerRequeue on handler throwDocumented in Architecture; not broker DLQ

Processor pipelines are not atomic with external side effects by design. Combine inbox/outbox with application idempotency and broker deduplication headers where needed. Built-in idempotency middleware is deferred to Roadmap.

Durable Contracts

Every durable message needs a stable contract name and version. Register it on the matching message or durable module builder, or use [MessageContract] with analyzer enforcement for production code.

Composition Checklist

A working durable path needs:

  1. AddInbox / AddOutbox with contracts
  2. Use*Storage inside the same builder
  3. UseInProcessDispatch, UseAmqpDispatch, or UseInMemoryDispatch on the matching module builder
  4. EnableInboxProcessor / EnableOutboxProcessor when the generic host should run loops
flowchart LR
    A[Application] -->|AcceptAsync or EnqueueAsync| B[(Store)]
    B --> C[Pipelined processor leases rows]
    C -->|inbox| D[IInboxDispatcher]
    C -->|outbox| E[IOutboxDispatcher]
    D -->|records result| B
    E -->|marks published| B

See Dependency Graph for package boundaries.

Next

On this page