Should transactional emails be sent synchronously in API routes or pushed to a background queue?

Synchronous email API calls slowing down HTTP response times in web apps when sending bulk notifications or invoices.

Has anyone else run into this, and what is the standard engineering fix?

Why Synchronous Email Sending Hurts User Experience

Making an outbound HTTP call or SMTP connection directly inside a request handler (e.g., inside an Express/FastAPI POST /checkout route) adds 200ms–1500ms of latency to the user’s response time. Worse, if the mail provider API experiences temporary latency, your web server worker threads get exhausted, leading to 504 gateway timeouts.

The Un-Awaited Async Call Pitfall

A common quick workaround is triggering an async call without await (fire-and-forget) to return an instant response. While this works in basic scenarios, it introduces major operational risks:

  1. Lost Emails on Process Restarts: If the web server or serverless container restarts or crashes mid-execution, pending un-awaited async tasks are instantly killed and lost forever.
  2. Rate Limit Spikes: Rapid concurrent un-awaited calls bypass concurrency controls, quickly triggering HTTP 429 Too Many Requests from your mail provider.

The Recommended Architecture: Background Task Queues

To guarantee reliability and fast response times, push email payload jobs into an in-memory or persistent queue (like BullMQ for Node or Celery for Python/Redis) and return a 200 OK response to the user instantly.

Example Architecture with BullMQ & AutoSend (Node.js):

// 1. Enqueue job in API route (Instant 10ms response to user)
import { Queue } from 'bullmq';
const emailQueue = new Queue('email-notifications', { connection: redisConfig });

app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  
  // Push email job to background queue
  await emailQueue.add('send-welcome', {
    email: user.email,
    name: user.name,
    userId: user.id
  }, { attempts: 3, backoff: { type: 'exponential', delay: 1000 } });

  return res.status(201).json({ message: 'User created successfully', userId: user.id });
});

// 2. Background Worker processes the queue asynchronously
import { Worker } from 'bullmq';
import { AutoSend } from 'autosend';

const autosend = new AutoSend(process.env.AUTOSEND_API_KEY);

new Worker('email-notifications', async (job) => {
  const { email, name } = job.data;
  await autosend.mails.send({
    from: { email: 'notifications@yourdomain.com', name: 'App Alerts' },
    to: { email, name },
    subject: 'Welcome to our platform!',
    html: '<p>Thanks for joining!</p>',
  });
}, { connection: redisConfig });

Key Benefits:

  • Instant Response Times: API endpoints return in milliseconds regardless of email delivery speed.
  • Durable Retry Guarantees: Unhandled failures automatically retry with exponential backoff without losing data.
  • Traffic Spreading: Rate-limits worker concurrency so burst traffic doesn’t overwhelm email provider limits.