Email Setup & Customization Guide
This comprehensive guide covers setting up Resend for transactional emails and customizing email templates.
Table of Contents
- Important: Emails Are Not Auto-Triggered
- Initial Setup
- Customizing Email Templates
- Email Template Reference
- Previewing Emails
- Sending Emails
- Testing Emails
- Production Setup
- Wiring Up Automatic Emails
- Troubleshooting
Important: Emails Are Not Auto-Triggered
The email system is set up but not automatically wired to send emails. The templates and API endpoint exist, but you need to add the trigger points yourself.
What's included:
- Email templates in
/emails/ - Resend client in
/lib/resend.ts - Email API endpoint at
/api/email
What you need to do:
- Add
sendEmail()calls to your webhook handlers or signup flow - See Wiring Up Automatic Emails for step-by-step instructions
Initial Setup
Step 1: Create a Resend Account
- Go to resend.com and click "Start for Free"
- Sign up with your email or GitHub account
- Verify your email address
Step 2: Get Your API Key
- In the Resend dashboard, click API Keys in the sidebar
- Click Create API Key
- Name it (e.g., "SaaS Boilerplate Development")
- Select permissions: Full Access for development, or Sending Access for production
- Click Create
- Copy the key immediately - it's only shown once!
Step 3: Add to Environment Variables
Add to your .env.local:
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxx
Step 4: Test the Connection
Start your dev server and the console should show:
Resend initialized successfully
If you see "RESEND_API_KEY is not set", double-check your .env.local file.
Customizing Email Templates
Email templates are React components located in /emails/. They use React Email components.
Understanding the Template Structure
Every email template follows this structure:
// /emails/example.tsx
import {
Body,
Button,
Container,
Head,
Heading,
Hr,
Html,
Link,
Preview,
Section,
Text,
} from '@react-email/components';
import * as React from 'react';
// Props interface - defines what data the email needs
interface ExampleEmailProps {
userName?: string;
actionUrl?: string;
}
// Main component
export default function ExampleEmail({
userName = 'there', // Default values for preview
actionUrl = 'http://localhost:3000',
}: ExampleEmailProps) {
return (
<Html>
<Head />
<Preview>Preview text shown in email client list</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Hello {userName}!</Heading>
<Text style={text}>Your email content here.</Text>
<Button style={button} href={actionUrl}>
Click Here
</Button>
</Container>
</Body>
</Html>
);
}
// Styles
const main = { backgroundColor: '#f6f9fc' };
const container = { /* ... */ };
const h1 = { /* ... */ };
const text = { /* ... */ };
const button = { /* ... */ };
Step-by-Step: Editing the Welcome Email
Let's customize the welcome email (/emails/welcome.tsx):
1. Open the file
// /emails/welcome.tsx
2. Change the company name
Find and replace "SaaS Boilerplate" with your app name:
// Before
<Heading style={h1}>Welcome to SaaS Boilerplate!</Heading>
// After
<Heading style={h1}>Welcome to YourAppName!</Heading>
// Before
<Preview>Welcome to SaaS Boilerplate - Let's get started!</Preview>
// After
<Preview>Welcome to YourAppName - Let's get started!</Preview>
// Before
<Text style={footer}>
The SaaS Boilerplate Team
</Text>
// After
<Text style={footer}>
The YourAppName Team
</Text>
3. Customize the welcome message
// Before
<Text style={text}>
Thank you for signing up! We're excited to have you on board.
</Text>
// After
<Text style={text}>
Thanks for joining YourAppName! You're now part of a community of
10,000+ professionals improving their workflow.
</Text>
4. Update the getting started list
// Before
<ul style={list}>
<li style={listItem}>Complete your profile</li>
<li style={listItem}>Explore your dashboard</li>
<li style={listItem}>Check out our pricing plans</li>
</ul>
// After
<ul style={list}>
<li style={listItem}>Complete your profile in Settings</li>
<li style={listItem}>Create your first project</li>
<li style={listItem}>Invite your team members</li>
<li style={listItem}>Check out our quick start guide</li>
</ul>
5. Change the button text
// Before
<Button style={button} href={loginUrl}>
Go to Dashboard
</Button>
// After
<Button style={button} href={loginUrl}>
Start Your First Project
</Button>
6. Update footer links
// Before
<Text style={footerLinks}>
<Link href="#" style={link}>Website</Link>
{' • '}
<Link href="#" style={link}>Documentation</Link>
{' • '}
<Link href="#" style={link}>Support</Link>
</Text>
// After
<Text style={footerLinks}>
<Link href="https://yourapp.com" style={link}>Website</Link>
{' • '}
<Link href="https://yourapp.com/docs" style={link}>Help Center</Link>
{' • '}
<Link href="mailto:support@yourapp.com" style={link}>Contact Us</Link>
</Text>
7. Change colors (optional)
Update the style objects at the bottom of the file:
// Change primary button color
const button = {
backgroundColor: '#4F46E5', // Change to your brand color
borderRadius: '6px',
color: '#fff',
// ...
};
// Change heading color
const h1 = {
color: '#4F46E5', // Match your brand
// ...
};
Adding a Logo to Emails
import { Img } from '@react-email/components';
// In your template
<Container style={container}>
<Img
src="https://yourapp.com/images/logo.png"
width="150"
height="40"
alt="YourApp Logo"
style={{ margin: '0 auto 24px' }}
/>
<Heading style={h1}>Welcome!</Heading>
{/* ... */}
</Container>
Important: Use a full URL (https://...) for images in emails, not relative paths. Images should be hosted on your production domain.
Email Template Reference
Available Templates
| Template | File | Triggered By |
|----------|------|-------------|
| Welcome | /emails/welcome.tsx | User signup |
| Subscription Confirmed | /emails/subscription-confirmed.tsx | Successful payment |
| Subscription Canceled | /emails/subscription-canceled.tsx | Subscription cancellation |
| Payment Failed | /emails/payment-failed.tsx | Failed payment attempt |
Welcome Email (welcome.tsx)
Purpose: Sent immediately after a user signs up.
Variables:
| Variable | Type | Description |
|----------|------|-------------|
| userName | string | User's name (or "there" as fallback) |
| loginUrl | string | URL to the login/dashboard page |
Customization ideas:
- Add a promotional offer for new users
- Include links to getting started resources
- Add social proof (user count, testimonials)
Subscription Confirmed (subscription-confirmed.tsx)
Purpose: Sent when a user successfully subscribes to a paid plan.
Variables:
| Variable | Type | Description |
|----------|------|-------------|
| userName | string | User's name |
| planName | string | Name of the subscribed plan |
| amount | string | Formatted amount (e.g., "$29") |
| billingPeriod | string | "month" or "year" |
| dashboardUrl | string | URL to the dashboard |
Customization ideas:
- Add next steps for getting started with their plan
- Include key features of their plan
- Offer onboarding call for higher tiers
Subscription Canceled (subscription-canceled.tsx)
Purpose: Sent when a user cancels their subscription.
Variables:
| Variable | Type | Description |
|----------|------|-------------|
| userName | string | User's name |
| planName | string | Name of the canceled plan |
| endDate | string | When access ends |
| resubscribeUrl | string | URL to pricing page |
Customization ideas:
- Add a win-back offer/discount
- Ask for cancellation feedback
- Remind them of what they'll lose
Payment Failed (payment-failed.tsx)
Purpose: Sent when a recurring payment fails.
Variables:
| Variable | Type | Description |
|----------|------|-------------|
| userName | string | User's name |
| planName | string | Name of the plan |
| amount | string | Failed payment amount |
| updatePaymentUrl | string | URL to billing page |
Customization ideas:
- Create urgency (grace period details)
- Explain what happens if not resolved
- Provide multiple payment update options
Previewing Emails
Method 1: React Email Dev Server (Recommended)
- Install React Email globally:
npm install -g react-email
- Start the preview server:
cd /path/to/your/project
npx react-email dev --dir emails
-
Open http://localhost:3000 in your browser
-
Select an email template to preview
-
Edit the template file - changes appear instantly!
Method 2: Send a Test Email
Send a test email to yourself via the API:
// Create a test script: /scripts/test-email.ts
import { Resend } from 'resend';
import WelcomeEmail from '../emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY);
async function sendTestEmail() {
const { data, error } = await resend.emails.send({
from: 'onboarding@resend.dev', // Use your domain in production
to: 'your-email@example.com',
subject: 'Test Welcome Email',
react: WelcomeEmail({
userName: 'Test User',
loginUrl: 'http://localhost:3000/dashboard',
}),
});
if (error) {
console.error('Error:', error);
} else {
console.log('Email sent:', data);
}
}
sendTestEmail();
Run with:
npx ts-node scripts/test-email.ts
Sending Emails
From API Routes
// In any API route
import { sendEmail } from '@/lib/resend';
import WelcomeEmail from '@/emails/welcome';
await sendEmail({
to: user.email,
subject: 'Welcome to YourApp!',
react: WelcomeEmail({
userName: user.name,
loginUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
}),
});
From Stripe Webhooks
Emails are typically triggered from webhook handlers:
// In /app/api/stripe/webhooks/route.ts
// After successful payment:
await sendEmail({
to: userEmail,
subject: 'Your subscription is active!',
react: SubscriptionConfirmedEmail({
userName: userName,
planName: 'Pro',
amount: '$29',
billingPeriod: 'month',
dashboardUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
}),
});
Testing Emails
⚠️ Important: Domain Verification Required
Without a verified domain, Resend only allows sending emails to your own Resend account email address. To send to other recipients:
- Verify a domain in Resend Dashboard → Domains
- Update
EMAIL_FROMto use your verified domainFor testing without a verified domain, use
EMAIL_FROM=onboarding@resend.devand send only to your Resend account email.
Test Email Addresses
With verified domain: You can send to any email address:
- Your personal email
test+variant1@youremail.com(Gmail plus addressing)- Use Mailinator for disposable addresses
Without verified domain: You can only send to your Resend account email address.
Check Email Delivery
In the Resend dashboard:
- Go to Emails in the sidebar
- See delivery status for each email
- Click an email for detailed delivery info
Test Different Scenarios
Create test data for each email:
// Test welcome email
WelcomeEmail({ userName: 'John' })
WelcomeEmail({ userName: undefined }) // Test fallback
// Test payment failed
PaymentFailedEmail({
userName: 'John',
planName: 'Pro',
amount: '$29',
})
Production Setup
Step 1: Verify Your Domain
- In Resend dashboard, go to Domains
- Click Add Domain
- Enter your domain (e.g.,
yourapp.com) - Add the DNS records Resend provides:
| Type | Name | Value |
|------|------|-------|
| TXT | @ | v=spf1 include:_spf.resend.com ~all |
| TXT | resend._domainkey | (Provided by Resend) |
| TXT | _dmarc | v=DMARC1; p=none; |
- Wait for DNS propagation (usually 5-30 minutes)
- Click Verify
Step 2: Update Environment Variables
# Production .env
EMAIL_FROM="YourApp <noreply@yourapp.com>"
EMAIL_REPLY_TO=support@yourapp.com
Step 3: Update Email Templates
Ensure all templates use your domain for:
- Logo images
- Links
- Company name
- Reply-to address
Step 4: Test Production Emails
- Deploy to staging environment
- Test signup flow → Welcome email
- Test payment flow → Subscription confirmed email
- Test cancellation → Cancellation email
- Verify all links work
React Email Component Reference
Basic Components
import {
Html, // Root element
Head, // Document head
Body, // Email body
Container, // Centered container
Section, // Section wrapper
Text, // Paragraph text
Heading, // H1-H6 headings
Link, // Anchor links
Button, // CTA buttons
Img, // Images
Hr, // Horizontal rule
Preview, // Preview text (shows in email list)
} from '@react-email/components';
Usage Examples
// Text paragraph
<Text style={{ fontSize: '16px', color: '#333' }}>
Your paragraph content here.
</Text>
// Button with link
<Button
href="https://yourapp.com"
style={{
backgroundColor: '#000',
color: '#fff',
padding: '12px 24px',
borderRadius: '6px',
}}
>
Click Here
</Button>
// Image (must use absolute URL)
<Img
src="https://yourapp.com/logo.png"
width="150"
height="40"
alt="Logo"
/>
// Link
<Link href="https://yourapp.com" style={{ color: '#0066cc' }}>
Visit our website
</Link>
Rate Limits
| Plan | Daily Limit | Monthly Limit | |------|-------------|---------------| | Free | 100 emails | 3,000 emails | | Pro | 1,000+ | Based on plan |
Monitor your usage in the Resend dashboard.
Troubleshooting
Emails not being sent
Check 1: Is RESEND_API_KEY set?
# In your terminal
echo $RESEND_API_KEY
Check 2: Is the API key valid?
- Go to Resend dashboard → API Keys
- Ensure the key is active
Check 3: Check Resend logs
- Dashboard → Emails → Look for errors
Emails going to spam
Solution 1: Verify your domain
- Set up SPF, DKIM, and DMARC records
Solution 2: Improve email content
- Avoid spam trigger words
- Include unsubscribe link
- Use proper from address
Solution 3: Warm up your domain
- Start with low volume
- Gradually increase sending
Template not rendering correctly
Check 1: Valid JSX syntax?
- Look for unclosed tags
- Check for missing imports
Check 2: Styles applied correctly?
- Use inline styles (not CSS classes)
- Check style object syntax
Check 3: Variables passed correctly?
- Console.log the props
- Check for undefined values
Images not appearing
Problem: Using relative URLs
// Wrong
<Img src="/images/logo.png" />
// Right
<Img src="https://yourapp.com/images/logo.png" />
Problem: Image not publicly accessible
- Ensure image is in
/publicfolder - Deploy before testing production emails
"From" address errors
Problem: Sending from unverified domain
- Verify your domain in Resend
- Or use
onboarding@resend.devfor testing
Wiring Up Automatic Emails
To have emails sent automatically, you need to add sendEmail() calls at the appropriate trigger points.
Send Welcome Email on Signup
Add to your signup success handler or create a Supabase database trigger:
// Option 1: In your signup API route or after successful signup
import { sendEmail } from '@/lib/resend';
import WelcomeEmail from '@/emails/welcome';
// After user is created
await sendEmail({
to: user.email,
subject: 'Welcome to YourApp!',
react: WelcomeEmail({
userName: user.name || user.email,
loginUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
}),
});
Send Subscription Emails from Webhooks
Add to /app/api/stripe/webhooks/route.ts:
import { sendEmail } from '@/lib/resend';
import SubscriptionConfirmedEmail from '@/emails/subscription-confirmed';
import SubscriptionCanceledEmail from '@/emails/subscription-canceled';
import PaymentFailedEmail from '@/emails/payment-failed';
// In handleSubscriptionChange function, after upserting subscription:
if (subscription.status === 'active') {
// Get user email from profile
const { data: profile } = await getSupabaseAdmin()
.from('profiles')
.select('email, full_name')
.eq('id', userId)
.single();
if (profile?.email) {
await sendEmail({
to: profile.email,
subject: 'Your subscription is now active!',
react: SubscriptionConfirmedEmail({
userName: profile.full_name || 'there',
planName: 'Pro', // Get from price lookup
amount: '$29',
billingPeriod: 'month',
dashboardUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
}),
});
}
}
// In handleSubscriptionDeleted or when cancel_at is set:
// Send SubscriptionCanceledEmail
// In handleInvoicePaymentFailed:
// Send PaymentFailedEmail
Using the Email API Endpoint
Alternatively, call the email API endpoint from anywhere:
await fetch('/api/email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Include if INTERNAL_API_KEY is set:
// 'Authorization': `Bearer ${process.env.INTERNAL_API_KEY}`
},
body: JSON.stringify({
to: 'user@example.com',
template: 'welcome', // or 'subscription-confirmed', 'subscription-canceled', 'payment-failed'
data: {
userName: 'John',
// ... other template-specific data
},
}),
});
Email Files Quick Reference
| File | Purpose |
|------|---------|
| /emails/welcome.tsx | Welcome email template |
| /emails/subscription-confirmed.tsx | Payment success template |
| /emails/subscription-canceled.tsx | Cancellation template |
| /emails/payment-failed.tsx | Failed payment template |
| /lib/resend.ts | Resend client & sendEmail function |
| /app/api/email/route.ts | Email API endpoint |