RabbitMQ
The RabbitMQ transport publishes outbox events with broker confirms and consumes them with manual acknowledgements, so an event is never lost between services: it is either delivered, still waiting in the outbox, or in a dead-letter queue.
pnpm add @relaybus/core @relaybus/rabbitmq amqplibRabbitMQ 3.13 or 4.x is required (quorum queues).
import { RelayBus } from '@relaybus/core';
import { RabbitMqTransport } from '@relaybus/rabbitmq';
const rabbit = new RabbitMqTransport({
name: 'rabbit',
url: process.env.AMQP_URL,
exchange: 'events',
consumerGroup: 'billing', // queue: relaybus.billing
});
const bus = new RelayBus({
store,
source: 'billing',
transports: [rabbit],
routes: [{ event: OrderPaid, transport: 'rabbit', destination: 'billing.order-paid' }],
subscriptions: [{ transport: 'rabbit', destinations: ['orders.*'] }],
});Events of a catalog that share a routing key need one route(produce(OrderEvents, { events: [...] }), { transport: 'rabbit', destination: 'billing.events' }) instead of a route per event; its optional wireTypes name the types for a jsonEnvelope codec.
What you get
publishresolves only after the broker confirmed the message; otherwise the outbox keeps the delivery and retries.- Messages are acknowledged after the inbox write. A crash before that redelivers; the inbox drops the duplicate.
- Failing handlers are retried through delay-tiered retry queues (about 1 hour by default) so a short database outage dead-letters nothing. Poison messages go to
<queue>.dead. - Reconnects with capped, jittered backoff.
Order is not preserved by default. Set ordered: true for per-queue ordering at the cost of throughput. Publishing uses the AMQP mandatory flag, so start consumers first or declare queues in advance. Details: @relaybus/rabbitmq.