Product Decisions This Supports
- Reliability investment for async side effects. This package targets a narrow, real failure class in at-least-once messaging: the listener succeeded but the acknowledgement was lost (connection drop, broker restart, worker killed), so RabbitMQ redelivers and the work runs twice. That is a correctness bug users see as double emails, double charges, duplicate records — a support-ticket and trust problem, not an infrastructure nicety.
- Build vs. buy vs. broker plugin. The alternative paths are (a) hand-rolled dedup tables/idempotency keys inside every listener, or (b) operating a RabbitMQ-side deduplication plugin. This package takes the third path: application-level dedup via an existing Laravel cache store, no broker changes, no plugin lifecycle to own. For a team already on
rabbitevents/listener ^10.0, "buy" is close to free — one composer require and one env var.
- Roadmap dependency: publisher upgrade to RabbitEvents 10.x. Messages published by RabbitEvents 9.x carry no ID and are silently not deduplicated. Adopting this package is therefore only meaningful once the publishing services are on 10.x; treat that migration as a prerequisite milestone, not a follow-up, or you ship a package that appears installed but does nothing for a portion of traffic.
- Scope decision: per-queue dedup is the contract. A message fanning out to several queues is handled once in each queue. If two queues ultimately feed the same downstream side effect (same customer record, same ledger entry), this package does not protect you — that remains a listener-level idempotency concern.
- Feature scoping: partial failures stay out of scope. A worker dying while listeners run is still "handled again," and listeners with side effects must remain idempotent. This package reduces duplicate exposure; it does not let you delete idempotency work from the engineering backlog. Plan and communicate it as risk reduction, not a correctness guarantee.
- Operational decision: fail-fast vs. degrade. Misconfigured cache store (missing from
config/cache.php) halts the worker at start — a loud, cheap-to-catch failure that should surface in CI or staging. An unreachable store degrades: messages are processed without deduplication and a warning is logged. That is the right default, but it means dedup coverage is not observable by default; a log-based or metric-based alert on those warnings is a small, worthwhile adjacent task.
- Infrastructure prerequisite shapes vendor/architecture choices. A cache store shared by all workers of a queue is mandatory —
redis, database, or memcached. Teams on per-host file/array caching cannot adopt this without adding shared infrastructure, which turns a small package decision into an infrastructure decision.
- Use cases where the value is concrete: payment and billing events, transactional email/SMS dispatch, inventory decrements, webhook fan-out to third parties, and any workflow where a duplicate delivery is visible to a customer or an auditor.
When to Consider This Package
Consider it when:
- You run
rabbitevents/listener ^10.0 (or are willing to upgrade to it) and RabbitMQ redelivery is a plausible failure mode — i.e. multi-worker consumers, deploys/restarts during message processing, or unreliable networks between worker and broker.
- Your listeners have externally visible side effects where duplicates cost money, trust, or cleanup time.
- You already have Redis/Memcached/DB cache shared across workers, or can add it cheaply. Adoption cost is then one dependency plus
RABBITEVENTS_DEDUPLICATION_STORE.
- You want dedup without operating a RabbitMQ plugin or taking on broker-level configuration and upgrade risk.
- You are willing to still write idempotent listeners for the partial-execution case — and want the package for the case listener code genuinely cannot see (post-success ack loss).
Look elsewhere, or accept the gap, when:
- Publishers are still on RabbitEvents 9.x and cannot move soon — you get no dedup for that traffic.
- The application runs on
file/array cache or workers do not share a store; you would be building infrastructure as part of the rollout.
- You need exactly-once semantics. This is at-least-once with duplicate suppression; duplicates remain possible (partial runs, store outage, cross-queue fan-out).
- You need dedup keyed to a business identity (same order ID published twice by two different upstream code paths) rather than a broker message ID — that is a different problem and belongs in listener logic.
- Throughput is extremely high and one extra cache round-trip per message is a measured bottleneck; benchmark before adopting, not after.
- The team cannot tolerate degraded-mode silent fallback and would rather fail hard when the store is unreachable during an incident.
- You want a mature, battle-tested dependency: this is a v1.0.0 first release with a narrow maintainer surface. Pin the version, read the source (it is small), and watch the changelog before betting a critical path on it.
How to Pitch It (Stakeholders)
For executives: Messaging systems deliver some messages twice — this is normal, expected behaviour, not a vendor bug. When it happens on work that moves money, sends customer email, or touches inventory, the customer sees the duplicate. This package closes one specific and otherwise-invisible version of that problem: the work was done, the acknowledgement was lost, and RabbitMQ replays it. It is a single small dependency, needs no changes to our broker or RabbitMQ configuration, and reuses the Redis we already run. If the cache is unavailable it keeps processing messages rather than stopping the queue — degraded, not down — and logs a warning. Cost is low, blast radius is small, and it addresses a class of incident that is expensive to diagnose after the fact.
For engineering leadership / architecture: Drop-in for services on rabbitevents/listener ^10.0; one composer require plus one environment variable pointing at a shared cache store. It covers ack-loss replays and nothing else — listeners must remain idempotent for mid-execution worker death, and dedup is per queue, so cross-queue fan-out into a shared side effect is still our problem. Two deliberate behaviours to know: a cache store missing from config/cache.php stops the worker at boot (fail fast, catchable in CI), and an unreachable store lets messages through undeduplicated with a warning (fail open, so we should alert on that log line). Prerequisite: publishers on RabbitEvents 9.x emit no message ID and get no protection, so the publisher upgrade to 10.x schedules first. This is a v1.0.0 with a deliberately small footprint — pin the version and keep the diff reviewed.
One-line summary: A cheap, low-risk reliability add-on that stops a specific, hard-to-debug class of duplicate side effects — worth adopting once publishers are on 10.x and a shared cache store exists, provided nobody mistakes it for exactly-once delivery.