Building a Serverless Newsletter System with Cloudflare

At BoxHero, we’re big believers in sharing what we learn.
Every month, we publish articles on our blog to help users get the most out of BoxHero and send a monthly newsletter that recaps those posts. That newsletter goes out to 30,000–40,000 users—specifically, people who joined in the past six months and opted in for marketing emails.

Like many companies, we started out using a commercial email tool (in our case, Mailchimp). But when we looked closer, we realized we were spending about $340 per month just to send a single newsletter. For a SaaS company with a large free user base, that didn’t sit right. So, we rolled up our sleeves and built our own system.
Here’s how we approached it, what worked, and what’s next.
The Old Way: Manual and Developer-Dependent
Before we built the new system, sending the newsletter looked like this:
- A developer ran a SQL query on AWS Aurora Postgres to pull the subscriber list.
- The list was then exported to a CSV file.
- A Node.js script on a small EC2 server read the CSV and sent emails one at a time.
- If the script failed, someone had to log into the server, check the logs, and restart it.

This process took anywhere between 30 to 60 minutes each month—not awful, but it always required engineering time. Worse, a small mistake could send the wrong email to tens of thousands of people.
We knew we could do better.
Our Goals
When we sat down to rethink this, we had a clear checklist:
- Enable the marketing team to send emails independently (no developer involvement)!
- Provide a web interface for editing and previewing templates.
- Eliminate the need for persistent servers (i.e. everything should run in the cloud).
- Keep operating costs low, since we only send once a month.

The Stack: Staying in the Cloudflare Ecosystem
We’re big Cloudflare fans, so we leaned on their stack to stay light and nimble:
Requirement | Tool |
---|---|
Run code without servers | Cloudflare Workers |
Store campaign data | D1 (SQLite for Workers) |
Read user data from Aurora | Hyperdrive |
Handle job queues | Cloudflare Queues |

Designing the System
1. Building the Email Template
We wanted the newsletter emails to look polished without a lot of design overhead:
- MJML gave us a solid, responsive base layout.
- Handlebars let us fill dynamic data into the template.
- When our marketing team pastes in a blog post URL, the system automatically pulls the title, image, and summary—but they can still edit the content as needed.
2. Campaign Workflow
We designed our email campaigns to move through four distinct states:
State | Description |
---|---|
Draft | Template is being written or edited. |
Ready | Template is finalized; recipient emails are pulled from Aurora into D1. |
In Progress | Emails are being sent. |
Completed | All emails have been processed, with results (success or fail) logged. |

3. Fetching the Target Audience
The Finalize API handles loading the recipient list:
- Connects to Aurora using Hyperdrive.
- Uses a read-only Postgres user that can only fetch email addresses.
- Pulls about 30,000 email addresses.
- Inserts them into D1 in batches of 1,000 (to stay within Worker sub-request limits).

4. Sending Emails at Scale
The Send API breaks the recipient list into small jobs, such as:
{ offset: 0, limit: 100 }
{ offset: 100, limit: 100 }
Those jobs go into a Cloudflare Queue, which can handle up to 10,000 emails per push.
A Queue Consumer Worker takes over from there:
- Pulls 100 target recipients from D1.
- Renders the email template with each user’s data.
- Sends the email.
- Logs the result (success or failure) back to D1, including any error details.
Once all jobs have been processed, the system updates the campaign status to Completed
to signal that the send is finished and results are fully recorded.

5. Security and Access Control
We also built the system with clear security boundaries.
- Access is restricted to BoxHero team members using Google Workspace accounts.
- A JSON Web Token (JWT) is issued at login, and every Hono API call validates the token.
- The Postgres user and D1 only store the minimum data needed for the newsletter.

6. Developer Experience and Frontend
We made several choices to improve the developer workflow:
- The Cloudflare Vite plugin gave us fast local development with hot reload.
- We built the frontend with React, React Router v7, Tailwind, and Shadcn—keeping it light and maintainable.
One limitation:
- D1 currently lacks transaction support, but given the tool’s limited use (only one person would run it every month), we accepted the small risk.
The Results
- The marketing team can now run our monthly newsletters independently, without engineering support.
- Our usage is around 30,000 requests per month, well within the 10 million included, making it practically free.
- We gained practical experience combining Workers, Queues, D1, and Hyperdrive into a real-world production workflow. (And honestly, we had fun doing it!)

What’s Next
We suspect many SaaS teams face similar challenges when it comes to managing bulk emails.
Our next step is to refine the code and release a “Click to Deploy” version, so anyone can spin up their own bulk-email tool in minutes. This version will include configuration options for things like sender email, templates, audience filters, and basic reporting—teams can adapt it to their needs.
We’re also exploring ways to improve the system itself: adding features like delivery analytics, better error handling, and support for additional email providers. As Cloudflare’s platform continues to evolve, we expect to take advantage of new capabilities to make the system even more robust and scalable.

Final Thoughts
For SaaS companies, email remains one of the most effective ways to engage users, share product updates, and drive retention.
By building a serverless newsletter system, we:
• freed up engineering resources,
• and significantly reduced costs.
This project not only solved an immediate challenge but also gave our team hands-on experience working with Cloudflare’s stack in a production environment. It challenged us to think carefully about scalability, reliability, and developer experience.
We’re glad we took the leap to build in-house, and we believe this approach has value for many SaaS teams looking to regain control over their infrastructure.
If you’re facing similar challenges, we encourage you to explore Cloudflare’s tools. (You might become a fan, just like we did.)