Why background jobs

Move slow, unreliable, or non-critical work out of the request path. Email sending, PDF generation, third-party API calls, and report aggregation all belong in background jobs.

Idempotency is mandatory

Jobs are retried on failure. If a job is not idempotent, a retry sends two emails or charges a customer twice. Use idempotency keys: before processing, check whether a key already exists in a processed-jobs table. If yes, skip.

Retry strategy

Exponential backoff with jitter: attempt 1 immediately, attempt 2 after 1 min, attempt 3 after 5 min, attempt 4 after 30 min. Cap at 5 attempts for most jobs. Some jobs (payments) should not retry automatically — send to manual review instead.

Dead letter queue

After max retries, move the job to a DLQ. Alert on DLQ depth. Every DLQ message is a bug or an edge case that needs handling. Treat DLQ messages as high-priority issues.

Tooling

  • BullMQ (Node/Redis), Sidekiq (Ruby/Redis), Celery (Python), Laravel Queues (PHP) — all battle-tested.
  • For simple use cases: Postgres-backed queues (pgqueue, River) avoid the Redis dependency entirely.