23 Oct 2026 · 3 min read · Ravi Sanchala
When marketing emails took down our database
Newsletter campaigns kept crashing the database and web server. The cause wasn't a bug. It was volume, and the fix was a queue.
Some outages have a clear culprit: a bad deploy, a full disk, an expired certificate. This one looked random. Every so often the database or the Apache server would go down, recover, and go down again.
It turned out to line up with something nobody on the engineering side was watching: the marketing calendar.
The setup
This was one of 30+ projects I looked after for a software agency. The application sent marketing newsletters and product campaigns directly. When a campaign went out, the app looped through the recipient list, queried the database for each person, built the email and sent it, all inside the same application that served regular users.
For a few hundred recipients, that’s fine. For thousands, it isn’t.
The diagnosis
When we lined up the crashes with what the system was doing at the time, the pattern was clear. Every large send created a wave of database reads and long-running work inside the web application. The database couldn’t absorb it, and the web server choked waiting on it.
There was no bug to fix. The design simply put bulk work on the same path as user requests.
If a background job can take down your website, it isn’t really in the background.
The fix: move the volume off the hot path
We changed three things.
1. Put a queue in the middle. Instead of sending inline, the app now drops each email job onto a queue backed by Redis/Valkey. Adding a job is fast and cheap, so the application stays responsive no matter how big the campaign is.
2. Let workers do the sending. Separate worker processes pull jobs off the queue and hand them to the email provider, SendGrid in this case. They work at a steady pace instead of all at once.
3. Cache what every email needs. Data that was being fetched from the database for every recipient now comes from the cache. The database stops getting the same questions thousands of times in a row.
The details that make a queue trustworthy
A queue on its own just moves the problem. These are what make it reliable:
- Batching. Workers send in batches instead of one request per email, which is kinder to both your servers and the provider’s API.
- Rate limiting. Match the sending rate to your email provider’s limits. Going faster just earns you rejected requests.
- Retries with backoff. A failed send goes back on the queue and is tried again after a delay that grows each time. Temporary errors fix themselves.
- No duplicates. Each job carries an ID, so a retried job never sends the same email twice.
- A dead-letter list. Jobs that keep failing get set aside for a human to look at, instead of retrying forever.
The result
About 10,000 emails now go out in roughly five minutes. The servers stay up during campaigns, the website stays fast for regular users, and email open and click tracking keeps working because nothing about the provider integration changed.
The general lesson
Look for work in your application that:
- Grows with the size of a list (emails, exports, notifications, reports)
- Runs inside a web request or on the main app process
- Hits the database once per item
Each of those is a future outage. Move it to a queue before marketing schedules the big campaign, not after.