Version 1 — The naive synchronous approach
Flow:- Mobile app submits sign-up details.
- Account service creates the user in the database.
- Account service calls the email service to send a welcome email.
- Only after the email is sent does the service return success to the mobile app.
- 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).
Version 2 — Add a queue (asynchronous processing)
Improve perceived latency and decouple account creation from email delivery: Flow:- Account service persists the user to the database.
- Account service enqueues a job to a queue (e.g., SQS) to send the welcome email.
- The request returns success to the mobile app immediately.
- Background workers consume jobs from the queue and send the emails.
- The user experience is fast because signup confirms immediately.
- Email sending can be retried and scaled independently from account creation.

- 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
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.
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.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.

Failure modes and mitigations (quick reference)
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.
Links and references
- Transactional Outbox pattern — https://microservices.io/patterns/data/transactional-outbox.html
- Amazon SQS visibility timeout — 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
- Idempotence (Wikipedia) — https://en.wikipedia.org/wiki/Idempotence