Written in Go  ·  PostgreSQL WAL

outboxd

Transactional outbox relay for PostgreSQL

A lightweight low-latency outbox event relay powered by PostgreSQL logical replication. Bridging the gap between naive polling and full-blown CDC solutions like Debezium.

$ go get github.com/pivovarit/outboxd

Four steps. Zero infrastructure.

Your application writes to the outbox table. outboxd handles everything else.

Insert

Your app inserts a row into the outbox table inside a transaction alongside your business data.

Stream

outboxd picks up the INSERT via a PostgreSQL logical replication slot in real time.

Deliver

Your handler receives the message and delivers it - to Kafka, RabbitMQ, HTTP, or anywhere.

Cleanup

The row is deleted from the outbox table and the WAL position is acknowledged automatically.

Just a function. Nothing else.

No framework to learn, no interfaces to implement, no configuration files. Just a Go function.

Low Latency

Powered by PostgreSQL logical replication. Events are picked up as soon as they're committed - no polling intervals.

At-Least-Once Delivery

WAL position is acknowledged only after successful handler execution. If the handler fails or the relay crashes, the message will be redelivered.

Zero Infrastructure

No JVM, no Kafka Connect, no Debezium. Just PostgreSQL with wal_level=logical and your Go application.

Automatic Retries

Configurable max retries with a drop callback. Failed messages don't block the pipeline.

Competing Consumers

PostgreSQL replication slots are exclusive. Run multiple relay instances for high availability - failover is automatic.

Graceful Reconnection

Exponential backoff on connection loss. The relay reconnects automatically without losing messages or duplicating deliveries.

Plug in and start relaying.

Define a handler, create a relay, start it. That's the entire API.

main.go
// Define your handler - return nil when done, error to retry handler := func(ctx context.Context, msg outboxd.Message) error { return rabbitCh.PublishWithContext(ctx, "exchange", msg.Topic, false, false, amqp.Publishing{Body: msg.Payload}, ) } // Create and start the relay relay := outboxd.New(databaseURL, handler, outboxd.Config{ SlotName: "outbox_relay", Publications: []string{"outbox_pub"}, }) relay.Start(ctx)

One SQL statement.

PostgreSQL must have wal_level=logical enabled. Create a publication for your outbox table.

Publication Required
CREATE PUBLICATION outbox_pub
FOR TABLE outbox
WITH (publish = 'insert');

One command. Full demo.

Starts PostgreSQL, RabbitMQ, a producer, a consumer, and two competing relay instances.

terminal
$ cd $(mktemp -d)
$ git clone https://github.com/pivovarit/outboxd.git
$ cd outboxd/example
$ docker compose up --build
Grzegorz Piwowarek

Grzegorz Piwowarek

Java Champion, Oracle ACE, and WarsawJUG Leader