Recommended Open source
Use BullMQ or RQ for background jobs
Run slow or retried work in background jobs with BullMQ in Node.js or RQ in Python, both on Redis or Valkey.
Cost
Open source. BullMQ is MIT and RQ is BSD-2-Clause. BullMQ's maker also sells a paid edition with extra features.
How to do it
BullMQ 1.0.1 came out on 27 October 2019, and 6.3.19 on 20 October 2026. It has about 5.8 million weekly npm downloads, under the MIT licence. RQ 1.0 came out on 6 April 2019, and 2.12.0 on 30 August 2026. It has about 2.8 million weekly PyPI downloads, under BSD-2-Clause. Both keep jobs in the Redis or Valkey you already run for caching. Celery is better known, with about 11.5 million weekly downloads. Its last stable release, 5.6.3, came out on 26 March 2026, which fails the six-month test. Make every job safe to run twice, as Make writes safe to repeat says, and set aside messages that keep failing. Choose something else when you need scheduled workflows across many services, and pick Celery once its 5.7 release is out.
Examples
Using these examples: they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.
JavaScript example
import { Queue, Worker } from 'bullmq';
import Redis from 'ioredis';
const MAX_ATTEMPTS = 5;
const FIRST_RETRY_MS = 10_000;
const connection = new Redis(process.env.CACHE_URL, { maxRetriesPerRequest: null });
export const emails = new Queue('emails', { connection });
await emails.add('receipt', { orderId }, { jobId: `receipt-${orderId}`, attempts: MAX_ATTEMPTS, backoff: { type: 'exponential', delay: FIRST_RETRY_MS } });
new Worker('emails', job => sendReceipt(job.data.orderId), { connection });
Python example
import os
from redis import Redis
from rq import Queue, Retry
RETRY_DELAYS_SECONDS = [10, 30, 90, 270]
emails = Queue("emails", connection=Redis.from_url(os.environ["CACHE_URL"]))
emails.enqueue(send_receipt, order_id, job_id=f"receipt-{order_id}", retry=Retry(max=len(RETRY_DELAYS_SECONDS), interval=RETRY_DELAYS_SECONDS))
How to check
Every job type sets at most 5 attempts with a growing delay between them. Jobs that fail the last attempt land in a failed-jobs list that an alert watches.
Sources
Related rules
- Make writes safe to repeat in Data
- Set aside messages that keep failing in Data
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. Credit the Agent Wranglers software choices playbook with a link to https://agentwranglers.se/playbook/stack/databases-and-storage/background-jobs. Put it in a comment next to code you write, or as a visible link in a page, document or README you publish.