Recuro.

At-Least-Once Delivery

Quick Summary — TL;DR

  • At-least-once delivery guarantees every message is delivered, but it may arrive more than once.
  • It's the default for most systems (webhooks, message queues, job queues) because preventing data loss matters more than preventing duplicates.
  • Handle duplicates by making consumers idempotent — use event IDs, deduplication tables, or database upserts.

At-least-once delivery means the sender guarantees every message reaches the receiver at least one time. The trade-off: the same message may be delivered more than once. No data is lost, but duplicates are possible.

How it works

The mechanism is simple: the sender transmits the message and waits for an acknowledgment (ack) from the receiver. If the ack doesn't arrive within a timeout window, the sender assumes delivery failed and retries. This continues until the receiver acknowledges or the sender exhausts its retry limit.

  1. Sender sends message to receiver
  2. Receiver processes the message and sends back an ack (typically a 200 response)
  3. If the sender doesn't receive the ack in time, it sends the message again
  4. Repeat until ack is received or max retries are exhausted

Why it's the default for most systems

Almost every system that delivers messages over a network uses at-least-once delivery: webhook providers (Stripe, GitHub, Shopify), message queues (SQS, RabbitMQ), and job scheduling services. The reason is practical: in a distributed system, you have to choose between potentially losing messages or potentially duplicating them. For payments, order processing, and data synchronization, losing a message is far worse than handling a duplicate.

Why duplicates happen

Duplicates are not caused by bugs. They're an inherent consequence of reliable delivery over unreliable networks. Here are the common scenarios:

In every case, the sender did the right thing by retrying. The duplicate is the cost of guaranteeing delivery.

How to handle duplicates

Idempotent consumers

The standard approach: make your processing logic idempotent so that handling the same message twice produces the same result as handling it once. If your webhook handler uses INSERT ... ON CONFLICT UPDATE instead of a plain insert, processing the same event twice writes the same row rather than creating a duplicate.

Deduplication with event IDs

Most webhook providers include a unique event ID in each delivery (e.g., Stripe's evt_1234). Store processed event IDs in a deduplication table. Before processing, check if the ID already exists. If it does, return 200 and skip processing.

Transactional processing

Wrap your message processing and deduplication check in a single database transaction. This prevents race conditions where two copies of the same message arrive simultaneously and both pass the deduplication check before either is recorded.

Comparison with other delivery guarantees

Guarantee Delivery Duplicates? Data loss? Use case
At-most-once0 or 1 timesNoPossibleMetrics, logging, non-critical events
At-least-once1 or more timesPossibleNoWebhooks, payments, data sync
Exactly-onceExactly 1 timeNoNoAchieved via at-least-once + idempotency

FAQ

What is at-least-once delivery?

At-least-once delivery is a guarantee that every message will be delivered to the receiver at least one time. The sender retries until it receives an acknowledgment, ensuring no messages are lost. The trade-off is that the receiver may get the same message more than once.

Why do duplicates happen with at-least-once delivery?

Duplicates happen because the sender can't distinguish between "the message was never delivered" and "the message was delivered but the acknowledgment was lost." In both cases, the sender's only safe option is to retry. This means a message that was already processed successfully may be sent again.

How do I handle duplicate messages?

Make your message handlers idempotent. Use the unique event ID included in the message to detect duplicates: store processed IDs in a deduplication table and skip any message you've already seen. For database writes, use upserts instead of inserts to ensure the same result regardless of how many times the operation runs.

At-least-once delivery is the foundation of reliable webhook systems, where providers retry failed deliveries according to a retry policy with exponential backoff. Making your consumers idempotent turns at-least-once delivery into exactly-once processing in practice. Compare with at-most-once delivery, which sacrifices reliability for simplicity.

Stop managing infrastructure. Start scheduling jobs.

Recuro handles cron scheduling, retries, alerts, and execution logs -- so you can focus on building your product.

No credit card required