Most advice about exactly once delivery starts in the wrong place. It treats the problem like a broker feature, a checkbox, or a transport guarantee you can enable if you pick the right platform. That framing causes a lot of bad designs.
What product teams usually need isn’t a message that traveled through the network exactly one time. They need the business effect to happen once. Charge the card once. Send the webhook effect once. Publish the social post once. Decrement inventory once. If a retry happens underneath, nobody cares, as long as the visible outcome is correct.
That distinction sounds academic until you build a system that retries under load, times out mid-write, or crashes after doing work but before recording that it did the work. Then the difference becomes the whole job. If you’re already working with retries, webhooks, or API calls that must survive flaky networks, this is the same reliability mindset behind patterns like the circuit breaker pattern for external failures. You don’t win by pretending failures won’t happen. You win by shaping what happens when they do.
The Truth About Exactly-Once Delivery
Exactly once delivery is a useful phrase, but it’s also a misleading one. In distributed systems, the hard part isn’t moving bytes from one machine to another. The hard part is making the final effect look as if it happened once, even when retries, crashes, and partial failures happen underneath.

The better question is whether you need exactly-once processing. That’s the gap that Marc Brooker’s discussion of delivery versus processing semantics pushes into directly. The receiver’s behavior matters more than the transport label. If a client retries the same payment request three times and your service charges the card once, your system did the right thing. If the network delivered the request once but your consumer applied the side effect twice, your system failed.
Exactly once is usually about controlling side effects, not proving a packet took one path through the world.
This is why senior engineers reach for idempotency, deduplication, and atomic state changes long before they argue about guarantees in the abstract. A message may be delivered more than once. A handler may start more than once. A worker may replay after recovery. None of those events are fatal if the operation is designed so repeated execution converges on one business result.
That shift in thinking changes implementation choices. You stop asking, “How do I make the network perfect?” and start asking:
- What identifies one logical action?
- Where do I record that it already happened?
- Can I store the effect and the processing marker atomically?
- What happens if I retry after timeout but before response?
Those questions lead to systems that hold up in production.
At-Most-Once vs At-Least-Once vs Exactly-Once
The simplest way to understand delivery semantics is to stop thinking about queues for a minute and think about mail handling.
At-most-once is like dropping a letter into a mailbox and never checking again. It might arrive. It might disappear. Nobody retries.
At-least-once is like sending the same document again whenever confirmation is missing. You reduce loss, but now the recipient may get copies.
Exactly-once processing, which is the practical target, is like the recipient keeping a ledger of document IDs. They may receive duplicates, but they only file the document one time.
Delivery Semantics Compared
| Semantic | Guarantee | Primary Risk | Good For |
|---|---|---|---|
| At-most-once | The system won’t retry after failure | Loss | Telemetry, low-value events, disposable notifications |
| At-least-once | The system retries until it gets confirmation | Duplication | Most event pipelines, background jobs, webhook delivery |
| Exactly-once | The visible effect is coordinated so it behaves once | Complexity and coordination cost | Payments, inventory, order state, high-value API actions |
For developers building products, at-least-once with idempotent handling is the default workhorse. That’s true in messaging systems, queue consumers, and webhook receivers. It’s also the model behind many practical webhook integration patterns in application APIs, where retries are expected and duplicate suppression lives at the receiver.
What each model feels like in code
At-most-once code is usually fast and dangerously simple. You read a message, try the work, then move on. If the process dies after fetching but before persisting, the message is gone from the application’s point of view.
At-least-once code adds retries and delayed acknowledgment. That’s better for durability, but duplicates become normal. Your handler has to assume the same logical event can reappear after a timeout, rebalance, or reconnect.
Exactly-once behavior adds another requirement. The system must tie work completion to state recording so they’re not independent events.
Practical rule: If the operation can hurt money, inventory, customer trust, or user-visible content, assume retries will happen and design for duplicate suppression from day one.
Picking the right trade-off
A lot of bugs come from choosing semantics by habit instead of by consequence.
- Choose at-most-once when dropping an event is acceptable and simplicity matters more than completeness.
- Choose at-least-once when every event matters but duplicates are survivable.
- Choose effectively-once processing when duplicate effects are worse than extra implementation effort.
The trap is trying to force every workflow into the strictest model. A metrics stream doesn’t need the same machinery as a billing event. A social publishing request isn’t a bank transfer, but it still shouldn’t create duplicate public posts if a client retries after a timeout. That’s why smart product systems often reserve stronger guarantees for the write paths that directly touch user trust.
Why Strict Exactly-Once Guarantees Are So Hard
Distributed systems fail in ways that are annoying precisely because they’re ambiguous. A producer sends a message and times out waiting for acknowledgment. Did the broker receive it and fail before replying? Did the packet vanish? Did the acknowledgment get lost on the way back? The sender can’t know for sure.
That uncertainty is why exactly-once delivery is widely treated as the hardest delivery guarantee. It has to prevent both loss and duplication across retries, crashes, and replays, and in practice it’s a coordination problem across source, processor, and sink rather than a simple network setting, as described in Conduktor’s explanation of exactly-once semantics in Kafka.

The acknowledgment problem
The classic mental model here is the Two Generals problem. Two sides need certainty that both sides agree to act, but every acknowledgment can itself be lost. Real systems hit the same wall.
A consumer might process a message, write to a database, and crash before acknowledging the queue. After restart, the broker redelivers. From the broker’s perspective, retrying is correct. From your database’s perspective, you’re now one bug away from a duplicate side effect.
The mirror image is just as bad. If the consumer acknowledges too early, then crashes before doing the work, you’ve traded duplication for loss.
Coordination beats optimism
The hard part isn’t any single retry. It’s the number of moving parts involved in a single logical action.
- Source uncertainty means a sender may retry because it doesn’t know whether the prior attempt committed.
- Processor uncertainty means business logic may run again after recovery.
- Sink uncertainty means an external API or database may apply work even when the caller never receives the success response.
If your flow crosses service boundaries, the problem gets worse. A scheduler may enqueue a publish request. A worker may transform content. An outbound client may call a platform API. A webhook may later confirm delivery. Every boundary creates another place where success and observation can diverge. Teams dealing with scheduled publishing flows run into the same retry and duplicate concerns seen in social scheduling pipelines that fan out user actions across platforms.
A timeout doesn’t mean failure. It means you lost visibility.
Why a broker setting isn’t enough
People often expect one configuration flag to solve all of this. It can’t. A broker can help suppress duplicate writes to its own log. A stream processor can atomically tie offsets to produced output. But the moment your business logic touches an external database, a third-party API, or a webhook target, the guarantee depends on whether that system also participates in the protocol or tolerates retries safely.
That’s why exact guarantees tend to collapse into a more practical design rule. Make replay safe. If you do that well, you don’t need certainty at every network hop. You only need enough coordination to ensure repeated attempts don’t create repeated effects.
Building for Idempotency The Key to Reliability
If you want exactly-once behavior in production, idempotency is the lever that moves the system. An idempotent operation can run multiple times and still produce the same final state as one successful run.
That doesn’t mean every retry returns the same transport response or follows the same execution path. It means the business effect doesn’t multiply. That’s the property you need for payment requests, order mutations, webhook consumers, and publishing APIs.

A useful implementation reference is Twilio’s description of using a message ID plus a de-duplication window, so keys can expire instead of being stored forever. The point is to ensure only one message with a given ID is sent within that bounded window, which is a practical way to approximate exactly-once behavior at scale in Twilio’s writeup on exactly-once delivery design.
Use idempotency keys at the API edge
The cleanest place to start is the request boundary. If a client is attempting one logical action, it should send one stable identifier for that action.
Typical pattern:
- The client generates an idempotency key.
- The server stores the key before or during execution.
- If the same key appears again, the server returns the original outcome or the current status instead of repeating the side effect.
This is ideal for operations like “create invoice”, “submit order”, or “publish post”. The key must represent the logical action, not the request attempt. If the HTTP client retries after a timeout, it must reuse the same key.
Example sketch:
POST /publish
Idempotency-Key: pub_7f8c...
body = {
"account_id": "acct_123",
"content": "Release notes are live"
}
On the server side, the table usually looks something like:
- key
- request hash or normalized payload
- status
- response reference
- created_at
- expires_at
Store enough state to answer, “Have I already done this action?” Then make duplicate requests converge on that answer. Systems that expose publishing endpoints often put this directly into the API contract. One example is letmepost’s publishing API, which includes built-in idempotency for publish actions.
After the first implementation pass, watch for one subtle bug. If you save the idempotency key separately from the actual business write, you can still create split-brain behavior. The reliable version records both in a transaction or an equivalent atomic step.
A short walkthrough helps:
Use a transactional outbox for state plus events
The next failure mode shows up when your service both writes local state and emits an event.
Suppose an order service inserts an orders row and then publishes order.created. If the database commit succeeds but the publish fails, downstream consumers never hear about the order. If the publish succeeds but the process crashes before marking local state, you may replay and emit again.
The transactional outbox handles this by writing the domain change and an outbound event record in the same database transaction. A relay process reads unsent outbox rows and publishes them later.
That pattern buys you a lot:
- Atomic local truth: The business row and the intent to publish commit together.
- Safe retries: The relay can retry publishing without re-running the business transaction.
- Auditable state: You can inspect what should have been emitted even during incidents.
It doesn’t eliminate duplicates by itself. The relay might publish twice after a crash. That’s why downstream consumers still need idempotency or deduplication.
Treat outbox publishing as durable intent, not magical exactly-once transport.
Use receiver-side deduplication for webhooks and consumers
Sometimes you don’t control the sender, or you can’t make the full path transactional. Then the receiver needs a memory of what it has already applied.
A practical receiver-side dedup flow looks like this:
- Accept a stable event ID: The sender includes an immutable identifier per logical event.
- Check before side effects: Look up whether that ID already completed successfully.
- Record and apply together: If possible, write the processed marker in the same transaction as the side effect.
- Expire old entries: Keep IDs for a bounded replay window rather than forever.
This pattern is common in webhook handling. Providers retry because they should. Receivers survive because they don’t trust delivery count.
The retention question matters. Infinite dedup state sounds pure, but it doesn’t scale well and usually isn’t necessary. A bounded replay window is often enough if it matches your retry behavior, operational recovery patterns, and business tolerance. That’s the part many developers miss. Dedup isn’t only a correctness feature. It’s also a storage and lifecycle design decision.
Exactly-Once in the Real World
The clean theory gets messy as soon as you use actual infrastructure. That’s where the useful questions change from “Is exactly once possible?” to “What exactly is being protected, and where does the guarantee stop?”
Kafka makes duplicates less visible, not failure impossible
Kafka is the standard example because it implements the strongest practical form of these ideas inside a messaging system. The key ingredients are idempotent producers, transactional state updates, and atomic commit semantics.
Confluent’s explanation is explicit on the mechanics. enable.idempotence=true removes producer-side duplicates per partition, and processing.guarantee=exactly_once makes a Kafka Streams application commit processing and state changes exactly once in the face of retries and failures, as described in Confluent’s breakdown of Kafka exactly-once semantics.
Under the hood, this works because the broker can tell whether a retried write is new. The producer gets a unique identity and a per-partition sequence number, so the broker can ignore duplicate retries rather than appending them again. Transactions extend that idea so output records and consumer progress are treated as one unit.
That’s powerful, but developers often overgeneralize it. Kafka can coordinate Kafka-native state transitions well. It can’t make your external REST call idempotent. It can’t force a third-party database to join the same atomic commit unless you build that coordination yourself.
Webhooks need replay protection on both sides
Webhook systems live in the at-least-once world by default. They should. If a receiver is briefly down, the sender needs to retry.
The sender’s job is to include a stable event ID, sign the payload, and retry responsibly. The receiver’s job is to verify the signature, use the event ID as the dedup key, and make side effects conditional on whether that event was already applied.
A solid webhook handler usually does this in order:
- Verify authenticity.
- Parse the event ID.
- Check the dedup store.
- Apply the side effect.
- Mark the event as processed.
If steps four and five aren’t atomic, you still have a gap. That’s not a reason to give up. It’s a reason to make the side effect idempotent too. For example, “set subscription status to active” is safer than “increment active count by one.”
APIs should model a logical action, not a single request attempt
Modern APIs often fail by pretending every request is unique. In reality, a timeout often represents one action with multiple attempts.
Product APIs should expose a way for clients to say, “This is the same operation as before.” That’s what idempotency keys are for. They turn retries from a guessing game into a protocol.
Cloud messaging products show similar limits when they expose exactly-once features. Google Pub/Sub, for example, documents that exactly-once support is constrained to specific operating conditions. It applies only to pull subscriptions, is region-scoped, depends on acknowledgments within the ack deadline, and requires clients to retain per-message progress to suppress duplicate work. Google also notes that when ordering is combined with exactly-once, throughput is limited to about an order of thousands of messages per second because acknowledgments must be in order, as described in Google Cloud Pub/Sub’s exactly-once delivery documentation.
That’s the lesson. Practical exactly-once behavior is always scoped. It depends on what the platform can coordinate and what the application records.
If your product publishes content outward to user-facing platforms, this matters immediately. A retry after a client timeout should reconcile to one logical publish action, not create two public posts. Teams building cross-platform social flows run into that exact concern when they connect posting actions across channels such as in Facebook to Instagram publishing workflows.
Embracing Practicality Over Perfection
The strongest engineers I know don’t chase perfect delivery. They design systems that stay correct when delivery isn’t perfect.
That’s the mindset shift behind exactly once delivery done well. You accept retries. You accept replays. You assume acknowledgments can be ambiguous. Then you build your API, consumer, and storage boundaries so repeated attempts collapse into one business outcome.
The trade-off is real. Recent guidance on streaming pipelines emphasizes that exactly-once behavior depends on coordination between source and sink, replay during recovery, and checkpoint overhead, and that at-least-once plus idempotent sinks can be faster and simpler when duplicates are tolerable, as noted in this practical discussion of exactly-once delivery trade-offs. That’s why stronger patterns are usually reserved for workflows where duplicate or missing effects carry direct cost.
Reliability isn’t a flag you switch on. It’s a property you design into every retry path.
When you’re deciding how much machinery to add, ask one question first: what breaks if this action happens twice, or not at all? If the answer is “not much,” keep the system simple. If the answer is “money moves, inventory shifts, or customers see duplicate public output,” spend the extra effort on idempotency keys, deduplication, atomic writes, and careful recovery behavior.
That’s the practical version of exactly-once. It isn’t magic. It’s disciplined engineering.
If you’re building social publishing, agent workflows, or webhook-driven automations, letmepost gives you one API for cross-platform posting with built-in idempotency and HMAC-signed webhooks, which makes retry-safe publish flows much easier to implement.