Transactional outbox and relay
Publish events after database commit through a bounded relay, with explicit confirmation and duplicate boundaries.
A transactional outbox closes the gap between saving business data and notifying another service. A handler records the outgoing event inside its database transaction. After the commit succeeds, a relay managed by Lily publishes that record to the broker.
Enqueue the message in the transaction#
The public API accepts a message validated by TransactionalOutboxMessage::try_new through PostgresTransaction::enqueue or MongoTransaction::enqueue. There is no separate Outbox::publish call or handler-owned relay loop. This fragment belongs inside the backend example’s handler body, where transaction and outgoing are already defined.
transaction.enqueue(TransactionalOutboxMessage::try_new(
outgoing,
"domain.events",
"orders.persisted",
1,
PublishContentKind::Json,
br#"{"status":"persisted"}"#.as_slice(),
)?).await?;outgoing is a non-nil UUID supplied by the application. Event IDs are unique within the outbox table or collection and remain unchanged across all relay publication attempts. Preserve the identity when republishing the same logical event. Generating a new UUID in a fully rolled-back MongoDB body attempt does not by itself violate atomicity; assigning a new identity when republishing a committed outgoing event defeats downstream deduplication.
Publication order and the meaning of success#
- The relay claims a bounded number of records within a byte budget.
- It durably advances the publication-attempt count.
- It performs a persistent, mandatory publish with canonical event/version/content headers and waits for broker confirmation.
- After a successful confirm, it marks the database record as delivered.
A confirmation does not show that a downstream handler has completed its work. If the process stops after confirmation but before the delivered mark is stored, the same record can be published again. Its event ID stays the same; downstream consumers must also be idempotent. An unroutable mandatory publication is not a successful delivery, so provision the exchange and destination bindings through your deployment and topology design.
Record, batch and retry limits#
| Field | Default / rule |
|---|---|
| Exchange and routing key | 1–200 UTF-8 bytes; no surrounding whitespace or control characters |
| Schema version | Positive u16 |
| One payload | Absolute limit of 16 MiB, additionally bounded by the default 8 MiB policy |
relay_batch_size |
100 records; at most 1000 |
relay_max_publish_attempts |
5, including the first attempt |
| Retry backoff | 250 ms initial delay; default maximum of 30,000 ms |
MongoDB also validates BSON document size, including space required for claim and failure metadata. A 16 MiB body is therefore not guaranteed to fit every MongoDB outbox document. The size policy applies during database enqueue as well; raising the relay batch limit does not raise the absolute per-message ceiling.
When publication attempts are exhausted, the record is retained, the relay terminates with an error and the provider failure reaches the Consumer lifecycle. This is not an infinite background retry loop. There is no public manual retry/reset API. Investigate the retained record and operational evidence instead of deleting records to hide failed publications.
Shared relays and shutdown#
Queues with the same backend and database_cell share a relay. Their entire TransactionalInboxConfig values must match, not only the relay_* fields. Active transaction tracking remains separate for each queue. Conflicting policies are rejected during startup.
Graceful shutdown closes active work and relay owners within the existing application deadline; it does not guarantee an empty outbox. Records committed after admission stops, or not yet published, can remain in the database for a later startup. shutdown_drain_timeout_millis does not extend the outer application deadline. Observe the outcome through operational snapshots and shutdown.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.