Configure transactional email providers using split-package architecture
LaunchSaaS provides email support via a split-package design. The core @launchsaas/email package defines the interface and Email service; each provider is a separate package that you install only when needed.
Updated: 2026-03-15
Architecture
@launchsaas/email ← interface + Email class (always needed)
@launchsaas/email-resend ← Resend (recommended, works serverless)
@launchsaas/email-nodemailer ← Nodemailer (SMTP, Node.js only)You only add the provider package you actually use. This keeps your bundle lean and avoids pulling in SDKs you don't need.
LaunchSaaS ships with @launchsaas/email-resend as the default. In your
own app, install whichever provider you prefer — Resend, Nodemailer, or a
custom package.
Setting Up Email
1. Install the provider package
In your app directory pick one provider:
# Resend (recommended — works on serverless and edge)
pnpm add @launchsaas/email-resend
# Nodemailer / SMTP (Node.js runtime only)
pnpm add @launchsaas/email-nodemailer2. Add env variables
Add the provider's keys export to your app's env.ts so the env vars are validated at startup.
// src/env.ts
import { keys as emailKeys } from "@launchsaas/email-resend";
export const env = createEnv({
extends: [
// ... other keys
emailKeys,
],
// ...
});Then set the variables in your .env:
# Resend
RESEND_API_KEY="re_..."
RESEND_FROM_EMAIL="[email protected]"
# — or — Nodemailer (SMTP)
SMTP_HOST="smtp.example.com"
SMTP_PORT="587"
SMTP_USER="[email protected]"
SMTP_PASS="your-password"
SMTP_FROM_EMAIL="[email protected]"
SMTP_SECURE="false"3. Initialize in instrumentation.ts
Open src/instrumentation.ts and uncomment the block for your chosen provider:
// src/instrumentation.ts
export async function register() {
// Option A — Resend (recommended)
if (process.env.NEXT_RUNTIME === "nodejs") {
const { Email } = await import("@launchsaas/email");
const { ResendEmailProvider } = await import("@launchsaas/email-resend");
Email.init(ResendEmailProvider.create());
}
// Option B — Nodemailer (SMTP)
// if (process.env.NEXT_RUNTIME === "nodejs") {
// const { Email } = await import("@launchsaas/email");
// const { NodemailerEmailProvider } = await import("@launchsaas/email-nodemailer");
// Email.init(NodemailerEmailProvider.create());
// }
}The dynamic import() inside register() ensures provider SDKs are only loaded on the Node.js server runtime and benefit from tree-shaking.
4. Verify it works
Start your dev server and trigger an email (e.g., sign up to get a verification email). Check the Resend dashboard or your SMTP logs.
Resend: Domain Verification (Production)
To send from your own domain instead of the Resend test address:
- Go to Domains in the Resend Dashboard
- Click Add Domain and enter your domain (e.g.,
yourdomain.com) - Add the DNS records Resend provides (MX, TXT/SPF, DKIM) to your DNS provider
- Wait for propagation (up to 48 hours) then click Verify
- Update your env:
RESEND_FROM_EMAIL="[email protected]"
Email Templates
LaunchSaaS ships example templates in src/components/email/ that you can copy into your own app and customize:
| Template | When sent |
|---|---|
email-verification.tsx | On user signup |
reset-password.tsx | Password reset flow |
magic-link.tsx | Passwordless login |
payment-completed.tsx | Payment confirmation |
welcome.tsx | Post-verification welcome |
i18n Support
Templates automatically use the user's saved locale (falling back to the request locale, then en). Translations live in messages/[locale].json under the Email namespace:
{
"Email": {
"hello": "Hello, {name}",
"emailVerification": {
"title": "Verify your email",
"heading": "Email Verification",
"action": "Verify Email",
"content": "Please click the button below to verify your email address."
}
}
}Customizing Templates
Each template receives name and t (translation function) as props:
import type { EmailTemplateProps } from "@/lib/features/email/template";
export function EmailVerificationTemplate({ name, t }: EmailTemplateProps) {
return (
<>
<p>{t("hello", { name })}</p>
<p>{t("emailVerification.content")}</p>
</>
);
}Custom Email Provider
To integrate a provider not shipped with LaunchSaaS, create a new email-xxx package:
1. Create the package
mkdir -p packages/email-myservice/srcpackages/email-myservice/package.json:
{
"name": "@launchsaas/email-myservice",
"version": "0.1.0",
"private": true,
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@launchsaas/email": "workspace:*",
"@launchsaas/errors": "workspace:*",
"@t3-oss/env-nextjs": "^0.13.10",
"my-email-sdk": "^1.0.0",
"zod": "^4.0.0"
}
}packages/email-myservice/src/keys.ts:
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const keys = createEnv({
server: {
MYSERVICE_API_KEY: z.string().optional(),
MYSERVICE_FROM_EMAIL: z.string().optional(),
},
experimental__runtimeEnv: {},
});packages/email-myservice/src/provider.ts:
import type { EmailProvider, SendEmailOptions } from "@launchsaas/email";
export class MyServiceEmailProvider implements EmailProvider {
readonly name = "myservice";
static create(): MyServiceEmailProvider {
return new MyServiceEmailProvider();
}
async send(options: SendEmailOptions): Promise<void> {
// Call my-email-sdk here
}
}packages/email-myservice/src/index.ts:
export { MyServiceEmailProvider } from "./provider";
export { keys } from "./keys";2. Register with pnpm workspace
Add to pnpm-workspace.yaml if it uses a glob that doesn't already cover it (e.g., packages/*).
3. Wire up in your app
pnpm add @launchsaas/email-myserviceAdd keys to env.ts and initialize in instrumentation.ts:
// instrumentation.ts
export async function register() {
const { Email } = await import("@launchsaas/email");
const { MyServiceEmailProvider } =
await import("@launchsaas/email-myservice");
Email.init(MyServiceEmailProvider.create());
}Disabling Email
To disable email entirely, simply don't call Email.init() in instrumentation.ts. Email.enabled() will return false and email-dependent features (magic link, email verification) will be skipped automatically.
If you leave email disabled, also set magicLink: false in your features
config to avoid confusing users with a sign-in option that silently fails.
Provider Comparison
| Provider | Package | Runtime | Free tier |
|---|---|---|---|
| Resend | @launchsaas/email-resend | Any (HTTP) | 100/day |
| Nodemailer | @launchsaas/email-nodemailer | Node.js only | Depends on SMTP host |
| Custom | @launchsaas/email-xxx | Custom | — |