Self-Hosted Email for SaaS: Why Your Boilerplate Should Own Its Email Stack

By Aziz Ali
emailself-hostedsaas-boilerplateplunkindie-hacker

Every SaaS boilerplate tutorial sends you to Resend or SendGrid. You sign up, grab an API key, and paste it in. Simple enough — until you're sending 50,000 transactional emails a month and suddenly you're staring at a $90 bill that scales with your growth. I got tired of that model fast.

Self-hosted email for SaaS isn't some advanced ops concern. With the right tooling, it's a 20-minute setup that saves you money on day one and never surprises you with a bill at 3am.

Why Most Boilerplates Get Email Wrong

The default boilerplate advice is: "use a managed email service." And look, I get it — managed services have great deliverability dashboards and SDKs. But there's a real problem buried in that model: you're paying a per-email tax on your own product's growth.

Here's what the math looks like on a typical SaaS:

Emails/month Resend (Pro) SendGrid (Essentials) Self-Hosted (Plunk)
10,000 $20 $19.95 ~$5 (VPS)
50,000 $50 $29.95 ~$5 (VPS)
100,000 $90 $89.95 ~$5 (VPS)
500,000 $400+ $249+ ~$10 (bigger VPS)

The self-hosted cost is nearly flat. That's the point. As you grow, your email costs don't grow with you.

On top of cost, there's the vendor lock-in issue. When you depend on a third-party email API, you're trusting their uptime, their pricing model, and their API stability. I've had Resend webhooks silently fail for 6 hours. I've seen email providers change their pricing mid-year. Self-hosting removes that dependency entirely.

Plunk: The Self-Hosted Email Layer Built for Developers

I'm not going to recommend running Postfix on a VPS yourself — that's a weekend of suffering for no good reason. Instead, I use Plunk, an open-source transactional email platform you can self-host on any cloud provider.

Plunk gives you:

  • A clean REST API (nearly identical to Resend's API, so migrations are trivial)
  • Event tracking and email analytics built-in
  • Contact management for marketing emails
  • A nice dashboard UI you can host alongside it

Here's the full architecture of how it fits into a TanStack Start SaaS:

graph TD
    A[TanStack Start App] -->|HTTP POST| B[Plunk API]
    B --> C[SMTP Server / SES]
    C --> D[User Inbox]
    B --> E[Email Logs & Analytics]
    A -->|Trigger| F[Welcome Email]
    A -->|Trigger| G[Password Reset]
    A -->|Trigger| H[Stripe Invoice]
    F & G & H --> B

Your app talks to Plunk's API. Plunk handles the queue, logging, and delivery via SMTP or Amazon SES (which costs fractions of a cent per email). You control everything.

Setting Up Self-Hosted Email in a TanStack Start App

Here's the actual code. I'm using the Plunk JavaScript SDK — but the raw HTTP API works identically if you'd rather keep dependencies minimal.

Install the SDK:

bun add @plunk/node

Create an email client utility:

// src/lib/email.ts
import Plunk from "@plunk/node";

const plunk = new Plunk(process.env.PLUNK_API_KEY!);

export async function sendWelcomeEmail(to: string, name: string) {
  return plunk.emails.send({
    to,
    subject: "Welcome to your app 👋",
    body: `
      <h1>Hey ${name},</h1>
      <p>You're in. Here's what to do next...</p>
    `,
  });
}

export async function sendPasswordResetEmail(to: string, resetUrl: string) {
  return plunk.emails.send({
    to,
    subject: "Reset your password",
    body: `
      <p>Click the link below to reset your password. It expires in 30 minutes.</p>
      <a href="${resetUrl}">Reset password →</a>
    `,
  });
}

Call it from a TanStack Start server function:

// src/routes/api/auth/register.ts
import { createServerFn } from "@tanstack/start";
import { sendWelcomeEmail } from "~/lib/email";
import { db } from "~/lib/db";
import { users } from "~/db/schema";

export const registerUser = createServerFn("POST", async (input: { email: string; name: string }) => {
  const user = await db.insert(users).values({
    email: input.email,
    name: input.name,
  }).returning();

  // Fire and forget — don't let email failure block signup
  sendWelcomeEmail(input.email, input.name).catch(console.error);

  return { user: user[0] };
});

That's it. The pattern is dead simple: email is a side effect, not a blocker. If Plunk is temporarily down, your user still registers. You just log the error and move on.

Deploying Plunk: Simpler Than You Think

You have two solid options:

Option 1: Railway (recommended for indie hackers) Plunk has a one-click Railway template. You get a running instance in under 5 minutes, connected to a managed PostgreSQL database. Total cost: ~$5/month.

Option 2: Docker on any VPS

docker run -d \
  -e DATABASE_URL="postgresql://..." \
  -e NEXT_PUBLIC_API_URI="https://email.yourapp.com" \
  -e SECRET_KEY="your-secret-here" \
  -p 3000:3000 \
  useplunk/plunk

Point your DNS at it, set up an Nginx reverse proxy with SSL via Certbot, and you're live.

For email delivery, I recommend routing through Amazon SES. SES costs $0.10 per 1,000 emails — dramatically cheaper than any managed email API. Plunk connects directly to SES via SMTP credentials, so it's a one-time config.

The Bigger Picture: Own Your Stack

Email is one piece of a larger principle I care about: avoiding vendor lock-in in your SaaS stack. Every third-party dependency is a potential price hike, API deprecation, or outage you can't control.

The move that's worked well for me as an indie hacker building on a tight budget is to own the pieces that scale linearly with usage. Email is the obvious one — it grows directly with your user base. Auth is another, which is why I use Better-Auth over Clerk (no per-MAU pricing). If you want to understand the full trade-off on email services, I also wrote a detailed breakdown of Resend vs Plunk that covers deliverability, pricing, and where managed services still make sense.

FAQ

Is self-hosted email deliverability as good as Resend or SendGrid? Yes, if you route through a reputable SMTP relay like Amazon SES or Mailgun. Plunk doesn't handle delivery itself — it delegates to your SMTP provider. SES has excellent deliverability and near-zero cost at scale.

What about GDPR and data residency? Self-hosting gives you complete control over where email data is stored and processed. You pick the region, you own the data. Managed services make this much harder to control.

Do I need devops experience to self-host Plunk? Not really. If you can deploy a Docker container or click through a Railway template, you can run Plunk. The hard parts (DNS, SPF/DKIM records) are the same regardless of which email solution you use.

What happens if my Plunk instance goes down? Your transactional emails queue up or fail silently — same behavior you'd get with any email API being unavailable. Use the fire-and-forget pattern I showed above and log failures. For critical emails like password resets, implement retry logic.

Can Plunk handle marketing emails too? Yes — Plunk has contact lists and broadcast email support, not just transactional sends. It covers both use cases from a single self-hosted instance.


If you want to skip the setup entirely, BetterStarter ships with Plunk pre-wired alongside Stripe, Better-Auth, and Drizzle ORM — all on TanStack Start with Bun. One codebase, no recurring SaaS tool bills beyond your own hosting. $99, one-time.