Reliable delivery for events committed to Postgres.
Ferry is a Postgres-native transactional outbox relay planned as an open-source Go service. It carries events committed alongside application data to workers, internal services, and webhooks with retries, delivery tracking, dead-letter handling, and replay.
The name reflects the job: Ferry carries a committed event safely across the boundary between an application database and a downstream system.
Applications often need to make two related changes:
- Commit business data to PostgreSQL.
- Publish an event or trigger background work.
When the database and delivery system are separate, no ordinary application transaction makes both changes atomic. If an order commits and the process crashes before publishing order.created, the order remains while payment, email, analytics, or fulfilment never begins. Publishing before commit is also unsafe because a destination may act on data that later rolls back.
The transactional outbox pattern makes the database change and event creation atomic. The application inserts its business data and a Ferry outbox event in the same PostgreSQL transaction. Ferry acts only on committed outbox events and tracks their delivery durably.
Ferry is intended for backend and platform teams that:
- use PostgreSQL as an application system of record;
- need reliable webhook or internal HTTP delivery after a transaction commits;
- want to avoid operating a separate event platform for this narrow use case;
- accept at-least-once delivery and can make destinations idempotent;
- prefer a backend-only service with observable, inspectable state.
Ferry should initially require only PostgreSQL and the ferry server binary.
- Trigger payment, fulfilment, notification, or analytics work after business data commits.
- Deliver signed webhooks to internal or external HTTP destinations.
- Retry transient destination failures without losing the original event.
- Inspect dead-letter deliveries and replay them after an operator resolves the cause.
- Recover outstanding delivery work after Ferry is terminated or restarted.
Version one is a single-node Ferry service with PostgreSQL as its source of truth. Its planned scope includes:
- transactional outbox ingestion and idempotent event discovery;
- a non-empty destination ID set committed atomically with each outbox event;
- stable event IDs and delivery IDs;
- PostgreSQL-backed leases or equivalent durable work claiming;
- durable deliveries and delivery attempts;
- HTTP destinations with timeouts and HMAC signatures;
- bounded retry policies with exponential backoff and jitter;
- dead-letter handling and operator-initiated replay;
- per-destination concurrency limits and bounded internal queues;
- graceful shutdown and recovery after process termination;
- health, readiness, metrics, structured logs, configuration, and CLI operations;
- Docker packaging plus integration and failure tests against real PostgreSQL.
All of these capabilities are planned. An initial Go package scaffold exists, but no Ferry runtime capability is implemented yet.
Version one will not be:
- a general-purpose distributed streaming platform;
- a Kafka-compatible service;
- a workflow orchestration engine or workflow DSL;
- a database or an exactly-once processing system;
- a hosted control plane;
- a frontend application;
- a multi-region event platform;
- a clustered delivery system with global ordering;
- dependent on Kafka, Redis, NATS, Kubernetes, or a separate frontend.
Pull-based workers and a small read-only dashboard may be explored later. Neither belongs to the version-one core.
The first release is successful when evidence from automated tests shows that:
- An application can commit business data and an outbox event atomically in PostgreSQL.
- Ferry materializes each committed outbox event idempotently and creates the complete durable delivery set for its committed destination IDs.
- A configured HTTP destination receives the event with stable identifiers and a verifiable signature.
- Transient failures cause bounded retries, while exhausted deliveries enter a visible dead-letter state.
- Operators can inspect and replay a dead-letter delivery without editing database state by hand.
- Ferry can terminate at relevant failure points and recover outstanding work without silently losing committed events.
- Duplicate delivery remains possible and is documented, observable, and deduplicable by the destination.
- Queues and per-destination concurrency are bounded under overload.
- Health, readiness, metrics, and structured logs explain the service's operational state.
- A user can run the single-node service with PostgreSQL without adding a separate event platform.
These criteria define a target, not the repository's current implementation status.
Ferry owns reliable handoff after an event commits. It does not own the destination's business transaction. At-least-once delivery ends at the acknowledgement boundary: if the destination processes an event but Ferry does not durably record the acknowledgement, Ferry may send the same delivery again.
That boundary is why stable identifiers and idempotent destinations are part of the product contract rather than optional optimizations.