ianfoxdev/outbox
A transactional outbox for Laravel: write messages or events to a database table inside your existing transaction, then relay them to queues or external services afterward, ensuring reliable delivery and no lost events if a job fails or the app crashes mid-request.
Architecture fit This package implements the transactional outbox pattern: business writes and outbox rows commit in the same database transaction, and a separate relay process publishes those rows to the downstream broker. That is the correct shape for any Laravel service where "write to DB" and "publish event" must not diverge — order placement, payment capture, inventory reservation, notification fan-out. It fits as an infrastructure-layer dependency beneath domain code, not as a domain concern, so it should be adopted as a platform capability rather than per-team.
GA confirms one architectural fact the rc1 notes did not surface: the relay is a Go binary, not a PHP worker. The release notes name franz-go (a Kafka client) and prometheus/client_golang as its build dependencies, and ship it as a container image with a Kubernetes base. So the integration is genuinely two runtimes — a PHP/Laravel library embedded in the app, plus a separately deployed Go process with its own image, registry, scrape endpoint, and broker credentials. That is broader than a typical Laravel package and widens the set of operational owners: schema migrations, a containerized long-running process, observability, and possibly an HTTP surface. Four distinct owners, as noted at RC, and now a fifth concern — container image lifecycle.
The Kafka inference matters for fit. franz-go is a Kafka client; there is no indication of AMQP, SQS, or NATS support in the packaged relay. If our downstream broker is Kafka, this is a clean fit. If it is not, the relay is not the piece we adopt — we would be adopting the outbox table and PHP API and writing our own publisher, which changes the value proposition substantially.
Integration feasibility Feasible for a team already on Laravel with a supported relational database and Kafka downstream. The work is not package installation but process supervision (now: container orchestration rather than a supervisor entry), schema management (a new table plus partition maintenance if partitioning is enabled), observability wiring (Prometheus scraping of a Go exporter), and image supply chain (registry access, pull policy, scanning). None of that is exotic, but each is a discrete task with a distinct owner.
The GA event itself is close to a no-op on the contract: the PHP API, table, relay settings, lock key, message layout, partitioning, metrics, and endpoints are explicitly unchanged from rc1, and ADR 0008's freeze now converts to a permanent 1.x guarantee — those surfaces change only in a major version. The only deltas in 1.0.0 are build-level: franz-go 1.22.2, prometheus/client_golang 1.25.0, an additional floating image tag 1, and a Kubernetes base pinned to 1.0. Nothing to re-validate on the contract; the RC spike results carry forward intact.
Technical risk
composer update. Modest, but it is new surface for a team that thinks in PHP releases.1 tag follows the latest 1.x release and the Kubernetes base references 1.0. A floating major tag means relay patches arrive without an application deploy — convenient, and directly in tension with the "pin exactly" mitigation below. Choose deliberately: 1.0.0 for control, 1 for patch uptake, and never both by accident.Key questions
franz-go implies Kafka. If our downstream is RabbitMQ/SQS/NATS, the relay is not reusable as-is and we must scope a custom publisher.Stack fit Natural fit: Laravel service, relational database, Kafka downstream, and now a container platform that can run the relay image. The package slots in as a composer dependency plus one migration for the app side, and a pinned container image plus scrape config for the relay side. Domain code keeps writing outbox rows inside existing transactions; no framework-level surgery. The blast radius stays confined to infrastructure, but "infrastructure" now spans two deployment artifacts.
Migration path
Compatibility
Three axes now: framework/runtime versions (verify via composer, do not assume), database engine capabilities (partitioning and lock implementation are the likely MySQL/PostgreSQL divergence), and broker protocol (Kafka via franz-go). Treat the frozen table and partitioning scheme as a schema contract we adopt permanently — review the migration before it runs in production rather than after. Pin the image by digest or exact tag in our manifests; the upstream 1 and 1.0 tags are conveniences for their demo, not a policy recommendation for us.
Sequencing The RC-versus-GA sequencing question is now moot: 1.0.0 is out and the contract is locked, so there is no remaining advantage to waiting and no remaining lever on the frozen surfaces. Sequence: spike and compatibility validation first, consumer idempotency second, pilot producer third. The demo's six-day, 25-leader-kill run gives reasonable confidence to move faster on the pilot than the RC assessment implied — but the throughput ceiling and the broker-compatibility answer should be known before we commit a payment-path producer, not after.
Maintenance
Three recurring obligations now, not two: partition maintenance (creating and dropping partitions on a schedule, or the table grows unbounded), relay image lifecycle (version tracking, vulnerability scanning, pull-policy, and a decision about the floating 1 tag), and relay supervision (restart policy, log aggregation, alerting on process death — now in the orchestrator rather than supervisor). All are platform-team-shaped work. Schema evolution of the outbox table should be treated as a platform change, not an application change, once multiple producers depend on it.
Support Still the weakest dimension. Single-maintainer upstream, no stated SLA, no stated post-1.0.0 support commitment in the GA notes. Mitigations: pin the image and the composer version exactly, vendor a fork or maintain an internal patch branch if the package lands on a critical path, and budget engineering time to own the relay if upstream stalls. The frozen contract helps — it bounds how much upstream drift we absorb — and GA plus a clean six-day demo lowers the probability of needing an emergency patch in the first quarter.
Scaling Two independent levers and one hard constraint. Lever one is partition pruning and archival, keeping the table and index working set bounded. Lever two is relay batching and poll frequency. The hard constraint remains the frozen lock key: whatever concurrency the relay supports today is what it supports through 1.x. The demo's ~775k orders over six days is a correctness datapoint, not a capacity datapoint — it exercises failover, not sustained load. Validate the ceiling against our peak event volume during the spike. If the answer is "not enough," the escape hatch is sharding the outbox by lock key or multiple relays with distinct keys, and that must be designed in from the start rather than retrofitted.
Failure modes
1 tag in play, a relay pulled on restart can differ from the one validated in the spike. Mitigate by digest-pinning in our own manifests and treating relay upgrades as deliberate changes.Ramp-up Low learning curve for engineers fluent in Laravel and the outbox pattern; the package implements a well-known pattern rather than a novel abstraction. The ramp-up cost sits with on-call and platform staff, who now need to reason about both a PHP library and a Go container: recognizing relay lag, distinguishing a stuck lock from a slow consumer, reading a Go Prometheus exporter rather than PHP instrumentation, and executing the lock-clearing runbook. Budget for a written runbook and one game-day exercise covering relay death, stuck lock, and poison message before the pilot producer goes live — the demo's leader-kill resilience is encouraging, but it validates the maintainer's consumer, not our runbook.
How can I help you explore Laravel packages today?