The SaaS Founder's Guide to Background Jobs and Workers | Deployxa

Background jobs keep your SaaS fast by moving slow tasks off the request path. Here is the founder's guide to when and how to add them.

← Back to Dispatch Articles
Engineering Log

The SaaS Founder's Guide to Background Jobs and Workers

Background jobs keep your SaaS fast by moving slow tasks off the request path. Here is the founder's guide to when and how to add them.

The SaaS Founder's Guide to Background Jobs and Workers

Key Facts

  • Direct answer: The direct answer is that background jobs are tasks that run outside the request-response cycle, typically via a job queue (e.g., BullMQ for Node.js, Celery for Python). The request enqueues the job (which takes milliseconds), returns immediately (the user sees a success message), and a background worker picks up the job and processes it (which can take seconds or minutes).

  • When to Add Background Jobs: Add background jobs when any request takes more than 1 second.

  • How Background Jobs Work: The architecture has three components.

  • When NOT to Add Background Jobs: Do not add background jobs for tasks that complete in under 500ms.

As your SaaS grows, some tasks take longer than a user is willing to wait: sending emails, generating reports, processing AI requests, resizing images. If these tasks run during the request (synchronously), the user sees a loading spinner for 10, 30, or 60 seconds, which leads to churn. Background jobs solve this by moving slow tasks off the request path: the request returns immediately, and the task runs in the background. This article is the founder's guide to when and how to add background jobs.

The direct answer is that background jobs are tasks that run outside the request-response cycle, typically via a job queue (e.g., BullMQ for Node.js, Celery for Python). The request enqueues the job (which takes milliseconds), returns immediately (the user sees a success message), and a background worker picks up the job and processes it (which can take seconds or minutes). The user is not kept waiting. For more on background workers, see our article on running BullMQ background workers.

When to Add Background Jobs

Add background jobs when any request takes more than 1 second. Common scenarios:

  • Email sending. Sending an email via SMTP or an API (e.g., Resend, SendGrid) takes 500ms-3s. If you send emails synchronously, the user waits 3s for the signup to complete. With a background job, the signup completes instantly, and the email is sent in the background.
  • Report generation. Generating a PDF report (e.g., a monthly summary) can take 5-30s. With a background job, the user clicks "Generate Report," sees a "generating" message, and receives the report via email when it is done.
  • AI processing. Calling an LLM API (e.g., OpenAI) can take 5-60s. With a background job, the user submits the request, sees a "processing" message, and receives the result when it is done.
  • Image processing. Resizing, compressing, or watermarking images can take 1-10s per image. With a background job, the user uploads the image, sees an "uploading" message, and the image is processed in the background.
  • Webhook processing. When a Stripe webhook arrives, processing it (e.g., updating the database, sending an email) can take 1-5s. With a background job, the webhook responds immediately (200), and the processing happens in the background. For more, see our article on the webhook reliability gap.

How Background Jobs Work

The architecture has three components:

  1. The producer. Your web app enqueues a job (e.g., "send welcome email to user 123"). The enqueue operation takes milliseconds (it just adds a message to the queue).
  1. The queue. A message broker (typically Redis) stores the job until a worker is available. The queue ensures jobs are not lost if the app crashes.
  1. The consumer (worker). A separate process (the worker) picks up jobs from the queue and processes them. The worker can run in the same container (via node-cron or BullMQ) or in a separate container.

For more on this architecture, see our article on running BullMQ background workers.

How to Implement Background Jobs

For Node.js (BullMQ)

const { Queue, Worker } = require('bullmq');
const IORedis = require('ioredis');

const connection = new IORedis(process.env.REDIS_URL);

// Producer: enqueue a job
const emailQueue = new Queue('email', { connection });
app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await emailQueue.add('welcome', { userId: user.id });
  res.json({ status: 'ok' }); // Returns immediately
});

// Consumer: process jobs
const worker = new Worker('email', async (job) => {
  const user = await User.findById(job.data.userId);
  await sendEmail(user.email, 'Welcome!', '...');
}, { connection, concurrency: 5 });

For Python (Celery)

from celery import Celery

celery = Celery('tasks', broker=process.env.REDIS_URL)

@app.route('/signup', methods=['POST'])
def signup():
    user = create_user(request.json)
    send_welcome_email.delay(user.id)  # Enqueue
    return jsonify({'status': 'ok'})  # Returns immediately

@celery.task
def send_welcome_email(user_id):
    user = get_user(user_id)
    send_email(user.email, 'Welcome!', '...')

When NOT to Add Background Jobs

Do not add background jobs for tasks that complete in under 500ms. Background jobs add complexity (a queue, a worker, Redis), and for fast tasks, the overhead of enqueuing and dequeuing is not worth it. Use background jobs only for tasks that take more than 1 second.

Common Pitfalls and Troubleshooting

The first pitfall is not handling job failures. If a job fails (e.g., the email service is down), the job should be retried (with exponential backoff) and eventually moved to a dead-letter queue. The fix is to configure retries and dead-letter queues in your job library.

The second pitfall is not making jobs idempotent. If a job is retried (because it failed the first time), it might be executed twice, which can cause issues (e.g., sending two welcome emails). The fix is to make jobs idempotent (e.g., check if the email was already sent before sending).

The third pitfall is not monitoring the queue. If the queue grows (jobs are enqueued faster than they are processed), jobs are delayed, which degrades the user experience. The fix is to monitor the queue length and to alert when it grows beyond a threshold.

The fourth pitfall is running the worker in the same process as the web server. If the worker crashes, the web server crashes too (and vice versa). The fix is to run the worker in a separate process (or a separate container).

The fifth pitfall is not using a persistent queue. If the queue is in-memory (not Redis), jobs are lost when the app restarts. The fix is to use Redis (or another persistent message broker) as the queue backend.

Conclusion: Keep Users Waiting, Not Your App

Background jobs keep your SaaS fast by moving slow tasks off the request path. By adding background jobs for tasks that take more than 1 second (email sending, report generation, AI processing, image processing, webhook processing), you keep your users happy (instant responses) and your app fast. The key is to add background jobs when the product demands it — not before.

Ready to add background jobs? Identify the slow tasks in your SaaS, set up a job queue (BullMQ or Celery), and move the slow tasks to background workers. For more, see running BullMQ background workers and how to scale your SaaS from MVP to first customers. Explore our free developer tools to speed up your workflow.

Ready to deploy with Deployxa?

Deploy your apps globally with automatic SSL and AI diagnostics.

Start Free Now