LogoLaunchSaaS

Email

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-nodemailer

2. 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:

  1. Go to Domains in the Resend Dashboard
  2. Click Add Domain and enter your domain (e.g., yourdomain.com)
  3. Add the DNS records Resend provides (MX, TXT/SPF, DKIM) to your DNS provider
  4. Wait for propagation (up to 48 hours) then click Verify
  5. 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:

TemplateWhen sent
email-verification.tsxOn user signup
reset-password.tsxPassword reset flow
magic-link.tsxPasswordless login
payment-completed.tsxPayment confirmation
welcome.tsxPost-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/src

packages/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-myservice

Add 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

ProviderPackageRuntimeFree tier
Resend@launchsaas/email-resendAny (HTTP)100/day
Nodemailer@launchsaas/email-nodemailerNode.js onlyDepends on SMTP host
Custom@launchsaas/email-xxxCustom

References

Next Steps