Self-Hosted Email for SaaS: Why Your Boilerplate Should Own Its Email Stack
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.