If a page in your product is starting to feel sluggish — a signup that hangs while a
welcome email sends, a report that times out, a webhook handler that occasionally drops
data — the fix usually isn’t a bigger server. It’s a background job queue. This guide is
for founders and product leads who keep hearing “we should move that to a queue” from
their engineers and want to understand what that actually means, when it’s worth doing,
and what tends to go wrong.
What a background job queue actually does
Most web requests should do one thing: read or write a small amount of data and return
a response in well under a second. The trouble starts when a request also has to send an
email, call a third-party API, generate a PDF, resize an image, or write to two systems
at once. Do all of that inline and your response time is now the sum of every slow step,
and a single flaky dependency can take your whole checkout flow down with it.
A background job queue splits that work in two. The web request does the minimum
needed to acknowledge the user, then hands the slow part to a queue as a small, durable
message. A separate worker process picks the message up, does the work, and retries if it
fails — all without the user waiting or the request thread being tied up. This is the same
pattern behind most “processing…” states you’ve seen in software you use daily, and it’s
what people mean when they talk about background job processing or asynchronous task
processing.
The signs you actually need one
It’s easy to reach for a queue too early and add operational complexity you don’t need
yet. The clearer signals are:
- A request depends on a third-party API with unpredictable latency (payment
providers, email services, enrichment APIs). - You’re processing uploads — CSV imports, image transforms, video encoding — that
take more than a second or two. - You need scheduled or recurring work: nightly exports, subscription renewals,
cleanup jobs. - You’re receiving webhooks and need to acknowledge them instantly while doing the
real processing afterwards — a pattern we cover in our
webhook reliability guide. - A single slow dependency has already caused an outage or a support ticket about a
hung page.
If none of those apply yet, don’t build the queue. A small product with fast, simple
requests doesn’t need one, and adding a message broker, worker fleet and retry logic
before you need it just gives you more to monitor and more ways to lose a job silently.
This is a case where doing less is the right engineering call, not a shortcut.
How the pieces fit together
Whatever technology you choose, a job queue architecture that holds up in production
tends to share the same shape:
- Producer and consumer are decoupled. The web app enqueues a
message and moves on; a separate worker process consumes it. Scaling one doesn’t
require scaling the other. - Jobs are idempotent. Because queues guarantee “at least once”
delivery in practice, the same job can run twice. A job that charges a card or sends an
email needs a way to recognise it’s already been done. - Retries use backoff with jitter. A failed job should retry after a
growing delay, with some randomness added, so a downstream outage doesn’t get hit by
every failed job retrying at exactly the same second. - Failed jobs land somewhere visible. A dead-letter queue for jobs
that exhaust their retries stops failures from vanishing silently — someone needs to see
them and decide what to do. - Queues are separated by risk, not lumped together. A queue mixing
low-stakes marketing emails with high-stakes payment confirmations means a burst of
email failures can starve or delay the payment jobs behind them. Split queues by
latency sensitivity and failure tolerance.
Where these workers live is itself an architecture decision — in a small product they
might run inside the same service as separate processes; in a larger one they may be
split out entirely. That trade-off overlaps heavily with the choice we cover in
Monolith vs Microservices: What Startups Should Build First,
and the same advice applies: don’t split things out before the seams are real.
The consistency problem nobody warns you about
The trap most teams fall into isn’t the queue itself — it’s the moment just before a
job is enqueued. A common pattern is: write to the database, then enqueue a job to send a
confirmation email. If the enqueue call fails after the database commit succeeds, the
email never sends and nothing tells you. Do it the other way round — enqueue first, then
commit — and you can end up processing a job for a record that was never actually saved.
The reliable fix is to write the job as part of the same database transaction as the
business data (often called a transactional outbox), then have a separate process read
unsent jobs from that table and publish them to the queue. It’s a bit more setup, but it
removes an entire class of “the email never went out and we don’t know why” bugs — the
same class of reliability problem that makes webhook delivery hard to get right.
Choosing a queue technology
You don’t need to build this from scratch. For most teams the practical choices are:
- Managed cloud queues (for example a hosted message queue or task
scheduler from your cloud provider) — least operational overhead, good for teams
already committed to one cloud. - Redis-backed libraries (such as BullMQ for Node.js, or Sidekiq for
Ruby) — lightweight, well documented, and enough for most products up to meaningful
scale. - Dedicated message brokers (such as RabbitMQ or Kafka) — worth it
once you have multiple services genuinely needing to communicate asynchronously, not
before.
Whichever you pick, the deciding factor should be who’s going to operate it. A team
without dedicated infrastructure support is usually better off with a managed option,
even at a slightly higher monthly cost, than running its own broker.
Monitoring background jobs in production
A queue that isn’t monitored will eventually fail quietly — jobs pile up, a worker
crashes and doesn’t restart, or a dead-letter queue fills up unnoticed for weeks. At a
minimum, track queue depth, job failure rate, and time-to-completion, and alert when any
of them moves outside a normal range. This is a specific case of the wider point in our
guide to observability for startups:
you want to know about a stuck job before a customer emails you about it.
It’s worth building this into the product from the start rather than retrofitting it
after an incident. If you’re planning a build that involves webhooks, third-party
integrations, imports or scheduled processing, it’s worth designing the async layer
properly from day one — it’s the kind of infrastructure decision that’s cheap to get
right early and expensive to unpick later.
If you’d like a second opinion on whether your product needs a job queue yet, or help
designing one that won’t fall over under load, book a free consultation
and we’ll talk through your specific setup.
FAQ
Do I need a job queue for a small MVP?
Usually not. If your requests are fast and you have no third-party calls or scheduled
work, a queue adds complexity without solving a real problem yet. Add one when a specific
request is slow or unreliable because of external work, not before.
What’s the difference between a job queue and a message queue?
In practice the terms overlap. A “job queue” usually refers to background tasks within
one application (send this email, resize this image), while a “message queue” more often
describes communication between separate services. The underlying mechanics — durable
messages, consumers, retries — are the same.
How do I stop a background job from running twice?
Make the job idempotent: check whether the outcome (email sent, charge made, record
updated) has already happened before doing it again, typically using a unique key stored
against the job or the record it affects.
Can background jobs fix a slow database query?
No. A queue moves slow work off the request path; it doesn’t make the work itself faster.
If the underlying job is slow because of a database query, that needs fixing separately —
see our guide on database schema design that survives growth.
Related: File Upload Architecture: A Founder’s Guide to Scaling Uploads — if uploads are one of the jobs you’re queuing, this covers presigned URLs, multipart uploads and processing pipelines in more depth.