> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Design Job Queue

> Designing a resilient background job queue for asynchronous tasks like welcome emails, using transactional outbox, visibility timeouts, idempotent workers, retries, and dead letter queues.

This lesson walks through designing a robust background job processing system for a common user flow: a mobile app sign-up that must create an account, return success to the user quickly, and send a welcome email asynchronously. We build the design incrementally, highlight failure modes in each naive approach, and show production-grade mitigations.

Why this matters: blocking user requests on slow external services leads to poor UX and brittle systems. Using queues, outboxes, visibility timeouts, idempotent workers, and dead-letter queues yields a resilient, observable pipeline for asynchronous work like emails, notifications, and other background tasks.

## Version 1 — The naive synchronous approach

Flow:

1. Mobile app submits sign-up details.
2. Account service creates the user in the database.
3. Account service calls the email service to send a welcome email.
4. Only after the email is sent does the service return success to the mobile app.

Problems:

* Perceived latency: the user must wait while the email provider responds.
* Fragility: if the email service is slow or down, sign-up requests may time out even though the account was created.
* Poor user experience and unexpected failure modes (account created, but user sees an error).

This approach works locally or in low-latency environments but does not scale or tolerate typical production failures.

## Version 2 — Add a queue (asynchronous processing)

Improve perceived latency and decouple account creation from email delivery:

Flow:

1. Account service persists the user to the database.
2. Account service enqueues a job to a queue (e.g., SQS) to send the welcome email.
3. The request returns success to the mobile app immediately.
4. Background workers consume jobs from the queue and send the emails.

Benefits:

* The user experience is fast because signup confirms immediately.
* Email sending can be retried and scaled independently from account creation.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/ts6-V-qkqu4w9od2/images/DevOps-Interview-Questions-and-Answers-Scenario-Based-Prep/System-Design/Design-Job-Queue/mobile-account-setup-sqs-lambda-email.jpg?fit=max&auto=format&n=ts6-V-qkqu4w9od2&q=85&s=3ed4c879d042b7db74bc905bb1c47d04" alt="A hand-drawn system architecture titled &#x22;V2: Add a Queue&#x22; showing a mobile app calling an account_setup service that writes to a database and pushes messages to an SQS queue. A Lambda worker consumes the queue (job) and triggers an email-send component." width="1920" height="1080" data-path="images/DevOps-Interview-Questions-and-Answers-Scenario-Based-Prep/System-Design/Design-Job-Queue/mobile-account-setup-sqs-lambda-email.jpg" />
</Frame>

Subtle consistency problem introduced:

* The account service now performs two operations: save the account and enqueue the job. If it saves the account but crashes before enqueuing, the account exists but the welcome email is never scheduled.

### Make the two actions atomic — Transactional Outbox

Use the transactional outbox pattern: write the email job to an outbox table inside the same database transaction that creates the account. A separate background publisher reads the outbox table and reliably publishes messages to the queue.

* Guarantees: either both the account row and the outbox entry are committed, or neither are — eliminating the “account created but no email scheduled” failure mode.
* Reference: Transactional Outbox pattern — [https://microservices.io/patterns/data/transactional-outbox.html](https://microservices.io/patterns/data/transactional-outbox.html)

## Version 3 — Visibility timeout and at-least-once delivery

Another failure mode: a worker dequeues a job and crashes while sending the email. If the message is removed/marked handled for that worker, the email gets lost and is never retried.

Queue systems solve this with a visibility timeout: when a worker receives a message, the message is hidden for a configurable interval (e.g., 30s). The worker deletes the message only after successful completion. If the worker crashes, the message becomes visible again and another worker can retry.

This provides reliability but introduces at-least-once delivery semantics: the same job may be redelivered and processed multiple times. Workers must tolerate duplicates.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/ts6-V-qkqu4w9od2/images/DevOps-Interview-Questions-and-Answers-Scenario-Based-Prep/System-Design/Design-Job-Queue/visibility-timeout-account-setup-sqs-lambda.jpg?fit=max&auto=format&n=ts6-V-qkqu4w9od2&q=85&s=9d5701b9ff42ea33a858aec8a6e2fea5" alt="A hand-drawn style architecture diagram titled &#x22;V3: VISIBILITY TIMEOUT.&#x22; It shows a mobile app calling an account_setup service that writes to a database/outbox, enqueues a job to SQS, and a Lambda worker processes the queue to send an email." width="1920" height="1080" data-path="images/DevOps-Interview-Questions-and-Answers-Scenario-Based-Prep/System-Design/Design-Job-Queue/visibility-timeout-account-setup-sqs-lambda.jpg" />
</Frame>

<Callout icon="lightbulb" color="#1CB2FE">
  Make workers idempotent: include a unique job ID with each job. When a worker completes the work, record the job ID (for example, in a `processed_jobs` table with a unique constraint). On receiving a job, check whether that job ID was already processed and skip duplicate processing if so. Idempotency is the standard pattern to handle at-least-once delivery.
</Callout>

## Handling persistent failures — Dead Letter Queues (DLQ)

Some jobs will keep failing (invalid addresses, permanent external errors, malformed payloads). Retrying forever is not useful. Configure a maximum receive count; when exceeded, move the message to a dead letter queue (DLQ) for manual inspection and remediation.

DLQs let engineers:

* Inspect failing payloads and root causes.
* Reprocess messages after fixing data or configuration.
* Avoid clogging the main queue with repeatedly failing work.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/ts6-V-qkqu4w9od2/images/DevOps-Interview-Questions-and-Answers-Scenario-Based-Prep/System-Design/Design-Job-Queue/handle-failure-outbox-sqs-lambda-dlq.jpg?fit=max&auto=format&n=ts6-V-qkqu4w9od2&q=85&s=34467fbf8d4dc1dcd4200351c80f04bc" alt="A hand-drawn style system architecture diagram titled &#x22;V1: HANDLE FAILURE&#x22; showing a mobile app calling an account_setup service that writes to a database with an outbox table. A separate process enqueues messages to SQS, a Lambda worker consumes them to send email, and failures go to a dead-letter queue for engineer investigation." width="1920" height="1080" data-path="images/DevOps-Interview-Questions-and-Answers-Scenario-Based-Prep/System-Design/Design-Job-Queue/handle-failure-outbox-sqs-lambda-dlq.jpg" />
</Frame>

## Failure modes and mitigations (quick reference)

| Failure mode | Symptom | Mitigation |
| - | - | - |
| Account created but no job enqueued | User exists; no email sent | Use a `transactional outbox` so account + outbox write are atomic |
| Worker crashes mid-send and message is removed | Email not sent and not retried | Use queue visibility timeout and explicit delete after success |
| Duplicate processing after retry | Email sent twice | Make workers idempotent using unique `job_id` and `processed_jobs` record |
| Persistent or repeated failures | Endless retries waste resources | Configure a DLQ with a reasonable max receive count and alerting |
| Slow external email provider | High latency on sign-up | Asynchronous queueing to decouple user response from email delivery |

## Recommended checklist

* Use a transactional outbox to make DB writes + enqueue atomic.
* Enqueue jobs to a durable queue (e.g., Amazon SQS, RabbitMQ, Google Pub/Sub).
* Use visibility timeouts and require workers to explicitly delete messages after success.
* Design workers to be idempotent (unique job IDs + processed-records or dedupe logic).
* Configure a dead letter queue and business-appropriate retry/backoff policy.
* Add monitoring, alerting, and observability for queue depth, DLQ growth, and processing latency.

Following these patterns produces a robust, observable, and production-ready background job system suitable for sending emails and other asynchronous workloads.

## Links and references

* Transactional Outbox pattern — [https://microservices.io/patterns/data/transactional-outbox.html](https://microservices.io/patterns/data/transactional-outbox.html)
* Amazon SQS visibility timeout — [https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html)
* Amazon SQS dead-letter queues — [https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html)
* Idempotence (Wikipedia) — [https://en.wikipedia.org/wiki/Idempotence](https://en.wikipedia.org/wiki/Idempotence)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/devops-interview-prep/module/ef53ec43-96e9-4d1b-8a6e-e6eb97b0d0dc/lesson/5a5679cb-1a1c-46f5-a4a2-75c46c984978" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.