Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
World desk7 min

Create a Delivery Once, Even When Your Node Worker Retries

Queue retries can run a side effect twice. Learn how to give each logical delivery a stable key, enforce it at the commit point, and use BullMQ and SQS features without assuming exactly-once delivery.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To make a delivery happen once when your Node.js worker retries, make the delivery operation idempotent. Give each logical delivery a stable identity, and enforce that identity at the point where the side effect is committed, so replaying the same job cannot create a second delivery. Queue retries and queue-level deduplication help control repeated jobs, but neither makes an external side effect exactly once on its own.

Why a retry can deliver twice

A worker can fail after it has already done the work it was asked to do. The common sequence looks like this: the processor sends the email, writes the row, or calls the payment API; the process then crashes, loses its connection, or times out before the job is marked complete. The queue sees an unfinished job and runs it again. From the queue’s point of view nothing went wrong. From the customer’s point of view, the delivery happened twice.

Two platform behaviours make this the normal case rather than a rare edge. BullMQ retries a job after a processor failure when retries are configured. Amazon SQS standard queues are documented as at-least-once: AWS states that a message may be received again in rare cases, and advises designing consumers to be idempotent. Your code therefore has to assume that the same work can arrive more than once.

Retries and idempotence solve different problems

A retry policy decides when a failed job is attempted again. It says nothing about whether repeating the side effect is safe. BullMQ’s retry guide covers attempts and fixed or exponential backoff; those settings control timing and count, not correctness.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Idempotence is the property that matters for correctness. BullMQ’s idempotent-jobs guidance defines it by the final system state: a job is idempotent when the end state is the same whether it succeeded on the first attempt or only after one or more retries. That is the target. A delivery is safe to retry when running it again leaves the system in the same state as running it once.

  • Retries answer: how many more attempts, and how far apart?
  • Idempotence answers: what happens to the world if an attempt repeats?
  • A higher retry count without idempotence increases the chance of duplicate side effects; it does not reduce it.

What queue deduplication does and does not cover

BullMQ offers job deduplication tied to job state or to a time-to-live (TTL). It can stop a repeated addition from creating another job while the matching job is still in the relevant state, or within the configured window. That is useful admission control for the queue. It is not evidence that the email was sent once.

BullMQ’s throttle-jobs guidance also warns about retention. If a completed or failed job that used a reused job ID has been removed, it no longer counts as an existing duplicate, so a later add with the same ID can create a new job. Check your removal settings before relying on job IDs as a long-term guard.

Amazon SQS FIFO queues suppress duplicate sends within a five-minute deduplication interval when you use content-based deduplication or supply an explicit deduplication ID. This is a send-side behaviour with a stated window. Outside that window, or once a message has been received and processed by a consumer that then fails before recording completion, the queue does not protect your side effect. The AWS exactly-once processing page describes this scope; read it as a statement about sends in that window, not as an end-to-end guarantee.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing where the idempotency boundary sits

The right boundary depends on where the side effect lands. The table below compares the layers that are commonly used, and what each one actually protects.

Layer What the documentation establishes What it protects, and what it does not
Application-level idempotence BullMQ defines idempotent jobs by unchanged final state after a retry. Protects the intended operation. Your code must define the logical identity and enforce it.
BullMQ job ID or deduplication Repeated adds can be ignored while a matching job exists, or according to the configured deduplication mode or TTL. Removal affects later duplicate detection. Controls queue admission. Does not make a third-party side effect idempotent.
Amazon SQS standard queue AWS documents that a message may be delivered again in rare cases and advises idempotent consumers. The consumer must tolerate repeated processing. The queue does not supply that tolerance.
Amazon SQS FIFO queue Duplicate sends are suppressed within the five-minute deduplication interval when content-based or explicit deduplication IDs are used. Scoped to sends in that interval. Not a universal exactly-once effect.

Sources for the table: BullMQ: Idempotent jobs, BullMQ: Deduplication, BullMQ: Throttle jobs, Amazon SQS: At-least-once delivery, and Amazon SQS: Exactly-once processing.

In practice, the layers stack. Queue deduplication reduces accidental duplicate jobs. The idempotent write at the side-effect boundary is what stops a repeated attempt from producing a second logical delivery.

Building a logical delivery key

Everything depends on a key that identifies the logical delivery, not the attempt. The rule is simple to state:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • A retry of the same business event reuses the same key.
  • A genuinely new delivery, such as a second notice for a new order, gets a different key.

Derive the key from the business event or request identity, such as an order ID plus a notification type, or a client-supplied request ID. Do not derive it from the BullMQ job ID, a timestamp, or a random value generated inside the processor, because those change between attempts or between enqueues. This is an implementation pattern consistent with how BullMQ and AWS describe deduplication and idempotency keys; neither source prescribes a particular key format.

Enforcing the key in your database

For a delivery you record in your own database, a uniqueness constraint is the most direct enforcement. Concurrent attempts can both start, but only one insert can succeed. An illustrative PostgreSQL table and a Node.js claim step look like this:

CREATE TABLE deliveries (delivery_key text PRIMARY KEY, status text NOT NULL, created_at timestamptz NOT NULL DEFAULT now());

const { rowCount } = await pool.query(
  `INSERT INTO deliveries (delivery_key, status)
   VALUES ($1, 'pending')
   ON CONFLICT (delivery_key) DO NOTHING`,
  [deliveryKey]
);

if (rowCount === 0) {
  // Another attempt already claimed this delivery. Read its status and return.
  return;
}

// Only the attempt that inserted the row performs the side effect.
await sendNotification(payload);
await pool.query(
  `UPDATE deliveries SET status = 'sent' WHERE delivery_key = $1`,
  [deliveryKey]
);

This sketch has a gap you must close. If the worker crashes after the insert but before the side effect completes, the row stays pending forever. Add a lease or an updated-at timestamp, and let a later attempt take over a pending row only after the lease expires, then check the side-effect system for evidence that the send happened before repeating it. Where the side effect cannot be checked, you have to accept an at-most-once or manual-review path for that narrow window.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Calling an external API

When the side effect is an external API call, your database key is not enough on its own. If the provider documents an idempotency mechanism, pass your logical key through it so the provider can recognise repeats. If it does not, you cannot assume repeated calls are safe, and you need to design around that: record intent before the call, reconcile against the provider’s records after a crash, or choose a flow the provider can verify. Check the provider’s current documentation for the specific mechanism, parameter names, and retention period. Do not assume one exists because the pattern is common.

Keeping the worker step small and retries bounded

BullMQ’s idempotent-jobs guidance recommends simple, atomic jobs. A job that performs many actions can make partial progress, and then rollback and tracking become difficult. Splitting the work so that each job has one side effect, and so that the idempotent claim happens first, makes the failure window smaller and easier to reason about.

For transient failures, configure a bounded number of attempts with fixed or exponential backoff. Then watch the failures that exhaust those attempts; they need a human or a dead-letter path, not an unlimited loop. Configure retries as a cost and latency control, and treat idempotence as the correctness control.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing the failure window

The failure that matters is a crash after the side effect has been committed and before the job is recorded as complete. You can reproduce it deliberately in a staging environment:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Enqueue one job for a single logical delivery key.
  2. In the processor, let the claim and the side effect succeed, then terminate the worker process before the job completes, for example with process.exit(1) placed after the side effect.
  3. Restart the worker so that the queue retries the job with the same logical key.
  4. Count the logical deliveries in your own store and in the downstream system. Confirm there is exactly one.
  5. Repeat the scenario for a concurrent retry, starting two workers against the same job, and confirm the claim still yields one delivery.

Run the same steps after any change to the claim logic, the retention settings for job IDs, or the retry configuration.

Version and scope limits

The behaviours described here come from BullMQ’s and AWS’s published documentation as of October 2026. Option names, retention defaults, and deduplication modes can change between releases. Before you ship version-sensitive code, read the documentation for the BullMQ version you have installed and the current SQS guide. This article does not report benchmarks or measurements of its own; the examples show the pattern, and you should test them against your own stack.

BullMQ’s documentation is at docs.bullmq.io. The retry options are described in Retrying failing jobs, and the idempotence guidance in Idempotent jobs.

The design rule is short: one logical key, enforced where the effect is committed, with queue features used only to reduce accidental duplicates. Build that, and a retry becomes a harmless replay rather than a second delivery.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.