patternjavascriptMajor
Email queue with retry logic for reliable delivery
Viewed 0 times
email queuebullmqjob queueretry emailbackground jobsemail reliability
Node.jsRedis
Problem
Sending emails synchronously in request handlers causes slow API responses and no resilience to SMTP/ESP failures. If the ESP is down or rate-limiting, emails are silently dropped with no retry.
Solution
Move email sending to a background job queue (BullMQ, pg-boss, or similar). The request handler enqueues the email job and returns immediately. Workers process the queue asynchronously with exponential backoff retries and dead-letter queues for failed jobs.
Why
Email sending is an I/O operation that can take 200ms to several seconds. Queuing decouples the API response time from email delivery and provides retry resilience. BullMQ supports delayed jobs, rate limiting, and job prioritization.
Gotchas
- Idempotency is critical — if a job is retried, you may send the same email twice; use a unique idempotency key per email and check before sending
- BullMQ requires Redis — ensure Redis availability in your infrastructure
- Dead-letter queues should alert on-call when emails fail permanently; do not silently discard them
- Rate limit queue workers to stay within your ESP's sending limits (e.g., SendGrid's 100 emails/second on free tier)
Code Snippets
BullMQ email queue with retry
import { Queue, Worker } from 'bullmq';
import IORedis from 'ioredis';
const connection = new IORedis(process.env.REDIS_URL);
export const emailQueue = new Queue('emails', { connection });
// Enqueue from request handler
await emailQueue.add('send-welcome', { to: 'user@example.com', name: 'Alice' }, {
attempts: 5,
backoff: { type: 'exponential', delay: 2000 },
});
// Worker process
const worker = new Worker('emails', async (job) => {
const { to, name } = job.data;
await resend.emails.send({
from: 'Brand <no-reply@example.com>',
to,
subject: 'Welcome!',
html: `<p>Hi ${name}, welcome!</p>`,
});
}, { connection });
worker.on('failed', (job, err) => {
console.error(`Email job ${job.id} failed after ${job.attemptsMade} attempts:`, err);
});Context
Production applications sending transactional emails at any significant volume
Revisions (0)
No revisions yet.