1
0 Comments

I stopped renting email tools and rolled a real ESP into my SaaS backend. Here's what it actually took.

The stack I kept building for every SaaS I shipped:

Mailchimp or Loops for campaigns: $50 to $99/mo
Postmark or Resend for transactional: $10 to $50/mo
Customer-io or a homegrown drip system for onboarding: $100+/mo or your weekends

Plus glue code to wire billing events into all three, plus a sync job to keep audience lists in sync with your user table, plus a separate merge tag setup in each tool because none of them know your user model.

For a solo founder pre-revenue, that's $15 to $250/mo out the door before you have paying customers. And you still own the integration mess. Every time you add a new billing event, you're updating it in three places.

I got tired of it. So when I started building BuildBase, the React/Next.js SaaS backend SDK I've been shipping with my brother, we decided the email layer wasn't going to be a separate concern. It was going to live inside the same SDK as auth, workspaces, billing, and workflows.

Two years of build later, the last missing piece shipped this week. Wanted to write up what "a real ESP inside your backend" actually means, because I underestimated it badly when we started.

What we thought would take a month

Transactional email is easy. Send a request to an API, get a message ID back, done. If that's all you need, buy Postmark and move on.

Campaign email is where it gets hard. Here's what we ended up needing:

  • Template system with three types: HTML (drag-drop editor via Unlayer), Markdown, and plain text
  • Campaign builder with audience targeting, drafts, scheduling, and bulk send
  • Bring-your-own sender across SMTP, Google, Microsoft, and Mailgun, because the ESP shouldn't dictate your sending reputation
  • Per-template merge tags plus organization-level merge tags for shared values like app name, support email, legal URLs
  • Open tracking via 1x1 pixel, click tracking via link wrapping
  • Unsubscribe groups with preference management (not just a global unsubscribe)
  • Sender account management with DNS verification for SPF/DKIM
  • BullMQ queue with rate limiting (100 per 5 minutes), 5 retries with exponential backoff
  • Background workers for campaign stats, unsubscribe tracking, IP enrichment
  • Full audit trail so you can debug why an email didn't send
  • A dead letter queue for permanently failed sends

That list is the difference between "we send email" and "we run an ESP." Every item on it was a week of work I didn't budget for.

The design lesson I want to share

The last piece shipped this week: organization-level merge tags. Values like {{appName}} or {{supportEmail}} or {{privacyPolicyUrl}} that you set once at the org level and every email template renders automatically.

The interesting decision was around delete behavior. First instinct: let users delete tags. Obvious.

Wrong. If you delete a merge tag that's referenced in a live template, the email renders with a broken placeholder and you don't find out until a customer emails support asking why the subject line says "Welcome to {{undefined}}."

So we made tags immutable. You can archive them, which hides them from the picker and flags them on save if you're editing a template that still references them, but existing templates keep rendering fine. Nothing breaks silently.

Same reasoning applied to the slug. Once a tag is created, the {{key}} is locked forever. Rename requires archive + recreate.

If you're building anything template-driven, steal this pattern. Every variable I've ever nuked in production has come back to bite me. Archive-not-delete costs nothing and prevents an entire class of production incidents.

Was building it worth it?

Honest answer: the ROI hasn't landed yet because BuildBase is still pre first external paying customer. So I can't tell you "we saved $X/mo across N customers."

What I can tell you:

  1. We run four of our own paid products on it (PlugNode, AgentCenter, Imejis, RemoteWait). Every welcome email, password reset, campaign, and drip on those products goes through this system. That's real production traffic. Not scaled, but real.

  2. Merge tags referencing user, workspace, and subscription data don't need a sync job. The template rendering context has direct database access. Every ESP I've paid for required either a batch sync or a real-time webhook to keep audience data current. That's a class of bug that just doesn't exist in this architecture.

  3. One dashboard for the customer. If BuildBase becomes the SaaS backend layer for another founder, they get campaigns, transactional, drips, and audience management in the same place they manage auth and billing. That's the pitch, and it only works because the ESP is native, not bolted on.

What I'd tell anyone at the same fork

If your product only needs transactional email, buy Postmark. It's cheap, deliverable, and someone else handles the deliverability wars with Gmail. Don't build.

If your product needs campaigns + drips + transactional wired to your billing events + audience data that stays in sync with your user table, the integration cost of renting three tools is higher than the sticker price suggests. Build it, or pick a backend that has it built in.

Still missing on my side: bounce webhook handling for hard-bounce suppression, and the RFC 8058 List-Unsubscribe header for Gmail/Yahoo bulk-sender compliance. Both are on the near-term list. I'd rather ship honest than ship overclaimed.

If you want to see it, BuildBase is at https://buildbase.app.

Happy to answer questions on the architecture, the deliverability tradeoffs, or the build-vs-buy math in the comments.

posted to Icon for group Building in Public
Building in Public
on July 15, 2026
Trending on Indie Hackers
641 downloads, 2 sales, and I still don't know why User Avatar 131 comments I sent 43 cold emails with my own tool. 17 replied. 1 paid. Here’s the unofficial launch. User Avatar 95 comments I built for one user. Myself. User Avatar 67 comments My AI agent quoted a client a price we killed months ago. So I built Engram. User Avatar 34 comments Got our first paid customers from an unexpected channel User Avatar 28 comments I came up with a great idea for a solo Vibe Coding project, and I'm testing it out right now User Avatar 22 comments