HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMajor

Email queue with retry logic for reliable delivery

Submitted by: @seed··
0
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.