Email Setup

Configure Resend or Nodemailer for transactional emails. Gmail setup, DNS records (SPF, DKIM, DMARC), and 6 React Email templates.

LaunchFst uses a factory pattern for email delivery. Set EMAIL_PROVIDER to switch providers. When unset, emails log to the console — useful for local dev without any email service.

ProviderEnv valueBest for
ResendresendProduction — best deliverability, analytics dashboard
NodemailernodemailerGmail, custom SMTP, local testing with Mailtrap
Console fallback(unset)Local dev — logs to console, nothing actually sends

Resend is the easiest way to send transactional emails with excellent deliverability. Free tier: 3,000 emails/month.

1

Add environment variables

EMAIL_PROVIDER="resend"
RESEND_API_KEY="re_xxxxxxxxx"
EMAIL_FROM="YourSaaS <noreply@yourdomain.com>"

Get your API key: resend.com → API Keys → Create Key.

2

Verify your domain

In the Resend dashboard → Domains → Add Domain. Resend gives you DNS records to add. See the DNS Records section below for exactly what to add.

EMAIL_FROM must use your verified domain — not gmail.com or outlook.com.

3

Test it

Trigger a signup to send the welcome email, or run:

pnpm email:preview

Opens the React Email preview server at http://localhost:3001.


Provider 2: Nodemailer (Gmail / Custom SMTP)

Nodemailer works with any SMTP server: Gmail, Mailtrap, Amazon SES, SendGrid, or your own.

EMAIL_PROVIDER="nodemailer"
SMTP_HOST="smtp.yourprovider.com"
SMTP_PORT="587"
SMTP_USER="your@email.com"
SMTP_PASS="yourpassword"
SMTP_SECURE="false"   # true for port 465
EMAIL_FROM="YourSaaS <noreply@yourdomain.com>"

Gmail Setup

Gmail requires an App Password — your regular Google password won't work.

1

Enable 2-Step Verification

Go to myaccount.google.com/security → Turn on 2-Step Verification.

This is required before you can create App Passwords.

2

Create an App Password

Go to myaccount.google.com/apppasswords.

  • App name: YourSaaS (or anything)
  • Click Create
  • Copy the 16-character password — spaces don't matter, remove them
3

Set environment variables

EMAIL_PROVIDER="nodemailer"
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_USER="you@gmail.com"
SMTP_PASS="abcdefghijklmnop"
SMTP_SECURE="false"
EMAIL_FROM="YourSaaS <you@gmail.com>"

Gmail limits: 500 emails/day for regular Gmail, 2,000/day for Google Workspace. For production, use Resend or Amazon SES instead.

Mailtrap (Development Testing)

Mailtrap catches all emails in a test inbox — nothing actually sends. Perfect for development.

1

Sign up and get credentials

Sign up at mailtrap.ioEmail TestingInboxesMy InboxShow Credentials → select Nodemailer integration.

2

Set environment variables

EMAIL_PROVIDER="nodemailer"
SMTP_HOST="sandbox.smtp.mailtrap.io"
SMTP_PORT="587"
SMTP_USER="your-mailtrap-username"
SMTP_PASS="your-mailtrap-password"
SMTP_SECURE="false"
EMAIL_FROM="YourSaaS <noreply@yoursaas.com>"

Other SMTP Providers

ProviderSMTP_HOSTSMTP_PORTNotes
Gmailsmtp.gmail.com587Requires App Password (see above)
Outlook/Hotmailsmtp-mail.outlook.com587Use your Microsoft password
Amazon SESemail-smtp.us-east-1.amazonaws.com587Requires IAM SMTP credentials
Resend (via SMTP)smtp.resend.com465Username: resend, password: your API key
SendGridsmtp.sendgrid.net587Username: apikey, password: your API key
Postmarksmtp.postmarkapp.com587Use your server API token as both user and pass
Mailgunsmtp.mailgun.org587Use SMTP credentials from Mailgun dashboard

Sending Emails

import { sendTemplateEmail } from "@/lib/email"
import { WelcomeEmail } from "@/components/core/emails/welcome"

await sendTemplateEmail(
  "user@example.com",
  WelcomeEmail,
  { name: "Alice", appName: "YourSaaS", appUrl: "https://yoursaas.com" },
  "Welcome to YourSaaS!"
)

Renders the React Email component to HTML and sends it via the configured provider.

getEmailProvider directly

import { getEmailProvider } from "@/lib/email"

await getEmailProvider().sendEmail({
  to: "user@example.com",
  subject: "Hello",
  html: "<p>Hello world</p>",
})

Guard with isEmailConfigured

import { isEmailConfigured } from "@/lib/email"

if (isEmailConfigured()) {
  await sendTemplateEmail(...)
}

Returns false when EMAIL_PROVIDER is not set — useful for non-critical emails you want to skip in development.


Email Templates

All 6 templates live in components/core/emails/ and are built with @react-email/components.

1. welcome.tsx — WelcomeEmail

Sent on signup. Shows 3 getting-started steps and a "Go to Dashboard" button.

Props:

{
  name: string        // User's display name
  appName?: string    // Defaults to "YourSaaS"
  appUrl?: string     // Defaults to "http://localhost:3000"
}

Triggered by: POST /api/auth/signup


2. password-reset.tsx — PasswordResetEmail

Contains a reset link with a 1-hour expiry.

Props:

{
  name: string        // User's display name
  resetUrl: string    // Full reset URL with token
  appName?: string
  appUrl?: string
}

Triggered by: POST /api/auth/forgot-password


3. org-invitation.tsx — OrgInvitationEmail

Sent when a member is invited to an organization. Invitation expires in 7 days.

Props:

{
  inviterName: string  // Name of the person sending the invite
  orgName: string      // Organization name
  role: string         // Role being granted (ADMIN, MEMBER)
  inviteUrl: string    // Full accept URL with token
  appName?: string
  appUrl?: string
}

Triggered by: POST /api/organizations/[orgId]/invitations


4. subscription-confirmed.tsx — SubscriptionConfirmedEmail

Sent after a successful payment webhook confirms a new subscription.

Props:

{
  name: string              // User's display name
  planName: string          // e.g. "Pro"
  billingPeriod: string     // e.g. "Monthly" or "Annual"
  nextRenewalDate: string   // Human-readable date string
  appName?: string
  appUrl?: string
}

5. receipt.tsx — ReceiptEmail

Payment receipt with amount, date, plan, and payment method.

Props:

{
  name: string           // User's display name
  amount: string         // e.g. "$29.00"
  date: string           // e.g. "March 31, 2026"
  planName: string       // e.g. "Pro (Monthly)"
  paymentMethod: string  // e.g. "Visa •••• 4242"
  appName?: string
  appUrl?: string
}

6. _layout.tsx — EmailLayout

The base layout used by all templates. Renders a header bar with the app name, a white content area, and a footer.

Props:

{
  preview: string        // Text shown in email client inbox preview snippet
  children: ReactNode    // Template-specific content
  appName?: string
  appUrl?: string
}

All templates wrap their content in <EmailLayout> — do the same when adding new templates.


Adding a New Template

  1. Create components/core/emails/my-template.tsx:
import { Text, Section } from "@react-email/components"
import { EmailLayout } from "./_layout"

interface MyEmailProps {
  name: string
  appName?: string
  appUrl?: string
}

export function MyEmail({ name, appName = "YourSaaS", appUrl = "http://localhost:3000" }: MyEmailProps) {
  return (
    <EmailLayout preview="Your inbox preview text here" appName={appName} appUrl={appUrl}>
      <Text>Hello {name}!</Text>
    </EmailLayout>
  )
}

export default MyEmail
  1. Send it:
import { sendTemplateEmail } from "@/lib/email"
import { MyEmail } from "@/components/core/emails/my-template"

await sendTemplateEmail(user.email, MyEmail, { name: user.name }, "Your subject line")

Previewing Templates

pnpm email:preview

Opens the React Email development server at http://localhost:3001. All templates in components/core/emails/ are listed and previewable in your browser.


DNS Records (Production Deliverability)

Without proper DNS records, your emails will land in spam. These records prove you own the domain and are authorized to send from it.

You need three types: SPF, DKIM, and DMARC.

Where to Add DNS Records

Add records in your domain registrar or DNS provider:

  • Cloudflare → DNS → Add Record
  • Namecheap → Advanced DNS → Add Record
  • Vercel Domains → DNS Records tab
  • GoDaddy → DNS Management → Add Record

DNS changes can take up to 48 hours to propagate, but usually work within 5–30 minutes.


SPF Record

SPF tells receiving mail servers which services are authorized to send email from your domain.

TypeName/HostValue
TXT@See below

Resend only:

v=spf1 include:_spf.resend.com -all

Gmail only:

v=spf1 include:_spf.google.com -all

Resend + Gmail:

v=spf1 include:_spf.resend.com include:_spf.google.com -all

Amazon SES:

v=spf1 include:amazonses.com -all

You can only have one SPF record per domain. Combine multiple senders into a single record using multiple include: statements.


DKIM Record

DKIM adds a cryptographic signature to every outgoing email, proving it hasn't been tampered with.

If using Resend:

Resend provides 3 CNAME records in the dashboard → Domains → your domain:

TypeName/HostValue
CNAMEresend._domainkey(provided by Resend)
CNAMEresend2._domainkey(provided by Resend)
CNAMEresend3._domainkey(provided by Resend)

If using Gmail/Google Workspace:

Google Admin → Apps → Gmail → Authenticate Email → Generate New Record. Add the TXT record Google gives you:

TypeName/HostValue
TXTgoogle._domainkey(provided by Google Admin)

DMARC Record

DMARC tells receiving servers what to do when SPF or DKIM checks fail. It also sends you aggregate reports about your email sending patterns.

TypeName/HostValue
TXT_dmarcSee below

Start with monitoring (recommended for first 2–4 weeks):

v=DMARC1; p=none; rua=mailto:you@yourdomain.com; pct=100

Reports are sent to you but no emails are blocked. Use this to verify everything is working.

After confirming — switch to quarantine:

v=DMARC1; p=quarantine; rua=mailto:you@yourdomain.com; pct=100

Failed emails go to spam.

Maximum protection:

v=DMARC1; p=reject; rua=mailto:you@yourdomain.com; pct=100

Failed emails are rejected outright.

Replace you@yourdomain.com with an address where you want to receive DMARC reports.


Complete DNS Example (Resend)

Here's what a fully configured domain looks like:

TypeNameValue
TXT@v=spf1 include:_spf.resend.com -all
CNAMEresend._domainkey(from Resend dashboard)
CNAMEresend2._domainkey(from Resend dashboard)
CNAMEresend3._domainkey(from Resend dashboard)
TXT_dmarcv=DMARC1; p=none; rua=mailto:you@yourdomain.com; pct=100

Verify Your DNS Records

Using terminal:

# Check SPF
dig TXT yourdomain.com

# Check DKIM (Resend example)
dig CNAME resend._domainkey.yourdomain.com

# Check DMARC
dig TXT _dmarc.yourdomain.com

Using online tools:

  • MXToolbox SuperTool — comprehensive DNS check
  • mail-tester.com — send a test email, get a spam score out of 10
  • Resend Dashboard → Domains — shows green checkmarks when records are verified

Testing Email

Full Test Checklist

EmailHow to TriggerWhat to Verify
WelcomeSign up with a new accountArrives, links work, branding correct
Password resetClick "Forgot password"Arrives, link works, token expires after 1 hour
Org invitationOrg settings → Invite memberArrives, accept link works
Subscription confirmedComplete a test checkoutArrives with correct plan name

Gmail Aliases for Testing

Gmail ignores dots and supports + aliases — all of these deliver to you@gmail.com but create separate app accounts:

you+test1@gmail.com
you+test2@gmail.com
y.o.u@gmail.com

Common Issues

ProblemCauseFix
"Invalid login" with GmailApp password wrong or 2FA not enabledCreate new App Password at myaccount.google.com/apppasswords
Emails go to spamMissing DNS recordsAdd SPF, DKIM, DMARC records
"Connection refused"Wrong SMTP_HOST or SMTP_PORTCheck your provider's SMTP settings
"Greeting never received"Port/secure mismatchPort 587 + SMTP_SECURE=false, or port 465 + SMTP_SECURE=true
Email not arrivingSpam folderCheck spam; then check terminal logs for errors
EMAIL_FROM rejectedAddress doesn't match verified domainResend: EMAIL_FROM must use your verified domain
Console fallback runsEMAIL_PROVIDER not setSet EMAIL_PROVIDER="resend" or "nodemailer" in .env.local

Environment Variables Reference

VariableRequiredDescription
EMAIL_PROVIDERNo*"resend" or "nodemailer". Unset = console only.
EMAIL_FROMYes (if set)Sender address, e.g. YourSaaS <noreply@yourdomain.com>
RESEND_API_KEYResend onlyAPI key from resend.com
SMTP_HOSTNodemailer onlye.g. smtp.gmail.com
SMTP_PORTNodemailer only587 (default) or 465
SMTP_USERNodemailer onlySMTP username / email
SMTP_PASSNodemailer onlySMTP password or App Password
SMTP_SECURENodemailer only"true" for port 465, "false" for port 587

Production Recommendations

StageProviderReason
Local devNone (console) or MailtrapNo config needed; Mailtrap catches real sends
StagingGmail or MailtrapQuick setup, verify templates work
ProductionResend or Amazon SESReliable deliverability, analytics, no daily limits

Always use a custom domain in production (noreply@yoursaas.com, not yoursaas@gmail.com). Custom domains with proper DNS records have significantly better inbox placement.

Demo Mode — Explore freely. Some actions are restricted. demo@launchfst.dev / demo1234

Get LaunchFst →