The 300-Email
Wall:
How a Marketing Blast
Became a Backend System
A 1,000+ user announcement, a 300/day API ceiling, and a marketing brief that quietly turned into a stateful, resumable engineering project. This is how a "simple email blast" became one of the most interesting puzzles I've shipped at Fixme.
When I was handed the brief for our latest marketing campaign, it felt like a home run waiting to happen. Our engineering team had just launched a transformative update to the Fixme app โ a new "Order Assistant" that automated tedious back-and-forth for vendors and gave customers a silky-smooth checkout. This wasn't just an update; it was a leveling up of our entire user experience.
The mission was clear: tell everyone. Not with a small in-app notification, but with a beautiful, compelling email announcement. One tailored for our hard-working vendors, and another for our growing customer base.
This is where the excitement met reality. As I mapped out the campaign, I ran into a wall โ a hard, numerical limit that turned a straightforward marketing blast into a complex engineering problem. Our email service provider, Brevo, gave us a generous but firm daily sending limit of 300 emails. Our user base? Over a thousand, and climbing.
We couldn't just email the first 300 people and call it a day. Every user deserved to know. It was a classic startup dilemma: big ambitions, lean resources. The project's scope immediately changed. It was no longer just a marketing campaign โ it was a technical challenge that required me to become a one-person machine: part designer, part marketer, part backend developer.
The First Hurdle: Designing an Email That Survives the Inbox
Every great campaign starts with great creative. My goal was an email that felt premium, modern, and worthy of a major tech update. I'm no full-time designer, but with a powerful online design tool I crafted what I thought were two perfect emails โ one for vendors, one for customers. Clean, on-brand, dynamic.
Feeling proud, I exported the HTML and set up a quick test send using our backend. I hit "send," waited a few seconds, and opened my Gmail.
The result was crushing.

Blank. Utterly, completely blank, save for a single line of footer text. All that design work, vanished.
I had just run headfirst into a brutal lesson every web developer learns eventually: email is not the web. My beautiful design was built on modern web technologies โ a <script> tag to run Tailwind CSS and <link> tags to pull in custom fonts. Email clients, for very good security and performance reasons, see that code and throw it straight in the trash. My template was being systematically dismantled before it ever had a chance to render.
My meticulously designed car had no engine. It was time to get under the hood.
Rebuilding the Engine: From Modern Code to Bulletproof HTML
The only way forward was to retreat into the past. I had to use a technique as old as email itself: inlining CSS. Instead of a central stylesheet, every single style โ every color, every font size, every pixel of padding โ had to be manually written into a style="" attribute on each individual HTML element.
This modern, clean code:
<div class="bg-white rounded-xl shadow-xl">Hello World</div>Had to become this verbose, but indestructible, code:
<div style="background-color: #ffffff; border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">Hello World</div>The thought of doing this by hand for two complex templates was daunting. So I turned to my secret weapon: an AI code assistant. I fed it my original HTML and styles, and it did the heavy lifting of converting thousands of lines into an email-safe format. I acted as the architect, guiding and refining the output until it was perfect.
The final HTML template โ now a self-contained beast โ was embedded directly into our Java Spring Boot application as a multi-line String. This was a key decision: it meant the application was entirely self-sufficient, with no external template files to manage.
private static final String CUSTOMER_HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Shopping on Fixme Just Got Smoother</title>
<style>
/* Only styles some clients *might* use, like gradients, remain here.
All layout and core styles are inlined below. */
.gradient-text {
background: linear-gradient(90deg, #4f46e5, #a855f7, #ec4899);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
</style>
</head>
<body style="margin: 0; padding: 0; background-color: #f8fafc;">
<div style="max-width: 42rem; margin: 2rem auto; padding: 1rem;">
<div style="background-color: #ffffff; border-radius: 0.75rem; ...">
<!-- ... fully inlined template ... -->
</div>
</div>
</body>
</html>
""";With the email itself finally fixed, I could now tackle the real beast: sending over 1,000 of them with a daily limit of 300.
Engineering a Campaign That Couldn't Fail
A simple for loop was out of the question. It would send 300 emails, then on the 301st the Brevo API would slam the door shut. The application would crash, and I'd be left sifting through logs to figure out who got the email and who didn't. Chaos.
I needed a system that was stateful, resumable, and idiot-proof (because I was the idiot who would be operating it). The solution was a batched sending engine with a persistent log. Every time I ran the program, it would know what it had already done and what was left to do.
I designed the logic using a flowchart before writing a line of code:

The sending engine flow
- Read master list โ load all 1,000+ users from
users.json. - Read sent log โ open
sent_promo_emails.txtand load every email already messaged. Empty on the first run. - Diff โ take the master list and remove anyone in the sent log.
- Check โ if pending is empty, the campaign is complete; print a success message and stop.
- Today's batch โ take the first 300 from pending.
- Process โ for each user: send the personalized email, immediately append their address to the sent log, pause 0.5s, repeat.
- Stop โ print a daily summary and exit.
The code that brought it to life
This logic was built into a PromotionalEmailer class. The key was the filter-and-loop mechanism. First, the core filtering โ this is where the system gets its "memory":
// Find everyone who still needs an email
List<ScrapedUser> pendingUsers = allUsers.stream()
// Safety check: valid email only
.filter(user -> user.getEmail() != null && !user.getEmail().isBlank())
// The magic: only keep users NOT in our sent log
.filter(user -> !sentEmails.contains(user.getEmail()))
.collect(Collectors.toList());Next, the loop that ensures we only process a safe amount and that every action is logged. The immediate logging (appendEmailToSentFile) is what makes the process resilient. If the program crashed after sending email #157, the log would have 157 names in it, and on the next run it would just pick up with #158.
// Today's batch โ capped at BATCH_SIZE of 300
List<ScrapedUser> batchToSend = pendingUsers.stream()
.limit(BATCH_SIZE)
.collect(Collectors.toList());
for (ScrapedUser user : batchToSend) {
// 1. Send the email
promoEmailService.sendTrackableEmail(
user.getEmail(), user.getFirstName(), subject, personalizedHtml
);
// 2. Persist state IMMEDIATELY โ this is what makes us crash-safe
appendEmailToSentFile(sentLogFile, user.getEmail());
// 3. Be a good API citizen
Thread.sleep(500);
}The Campaign in Action
With this system in place, my job became beautifully simple. Each morning, I would run the program from the command line.
On day 5 the program checked the log, saw every user from the master list was accounted for, and printed a single line: All users have already received the promotional email. Nothing to do.
It worked. Perfectly. We had messaged our entire user base without a single crash, duplicate, or API error.
What I Took From It
Every startup hits that point where big ambition meets tiny constraint. You either stall โ or you get scrappy.
This campaign taught me that even small limitations (like an email cap) can unlock smarter systems if you treat them as design prompts instead of blockers. I also learned โ again โ that beautiful code means nothing if it doesn't survive the inbox. And that the best tools are the ones that quietly do their job while everyone else moves on.
This system is still running. Quietly. Reliably. And honestly, that's the kind of win I'm most proud of.