The Email Problem Every SaaS Has
Users forget their password. They need to verify their email. A subscription renews. Someone invites a teammate. All of these require transactional email -- reliable, deliverable, templated.
Here's how to set up Resend in Next.js properly.
Setup
npm install resend react-email @react-email/components
// lib/email.ts
import { Resend } from 'resend'
export const resend = new Resend(process.env.RESEND_API_KEY)
export const FROM_EMAIL = 'Atlas <hello@whoffagents.com>'
export const REPLY_TO = 'support@whoffagents.com'
React Email Templates
// emails/WelcomeEmail.tsx
import {
Html, Head, Body, Container, Heading, Text, Button, Hr
} from '@react-email/components'
interface WelcomeEmailProps {
name: string
dashboardUrl: string
}
export default function WelcomeEmail({ name, dashboardUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Body style={{ backgroundColor: '#0a0a0a', fontFamily: 'sans-serif' }}>
<Container style={{ maxWidth: '600px', margin: '40px auto', padding: '40px' }}>
<Heading style={{ color: '#ffffff', fontSize: '24px' }}>
Welcome to whoffagents.com, {name}
</Heading>
<Text style={{ color: '#94a3b8', fontSize: '16px', lineHeight: '1.6' }}>
Your account is ready. Here's what to do next:
</Text>
<Button
href={dashboardUrl}
style={{
backgroundColor: '#0062b8',
color: '#ffffff',
padding: '12px 24px',
borderRadius: '8px',
textDecoration: 'none',
display: 'inline-block',
marginTop: '16px'
}}
>
Go to Dashboard
</Button>
<Hr style={{ borderColor: '#1e293b', margin: '32px 0' }} />
<Text style={{ color: '#475569', fontSize: '14px' }}>
You received this because you signed up at whoffagents.com.
</Text>
</Container>
</Body>
</Html>
)
}
Sending the Email
// lib/send-email.ts
import { resend, FROM_EMAIL } from './email'
import WelcomeEmail from '@/emails/WelcomeEmail'
export async function sendWelcomeEmail(to: string, name: string) {
const { data, error } = await resend.emails.send({
from: FROM_EMAIL,
to,
subject: `Welcome to whoffagents.com, ${name}`,
react: WelcomeEmail({ name, dashboardUrl: 'https://whoffagents.com/dashboard' })
})
if (error) {
console.error('Failed to send welcome email:', error)
throw new Error('Email send failed')
}
return data
}
Password Reset Flow
// app/api/auth/forgot-password/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { resend, FROM_EMAIL } from '@/lib/email'
import crypto from 'crypto'
export async function POST(req: NextRequest) {
const { email } = await req.json()
const user = await db.user.findUnique({ where: { email } })
// Always return success to prevent email enumeration
if (!user) return NextResponse.json({ success: true })
const token = crypto.randomBytes(32).toString('hex')
const expires = new Date(Date.now() + 60 * 60 * 1000) // 1 hour
await db.passwordResetToken.upsert({
where: { userId: user.id },
create: { userId: user.id, token, expires },
update: { token, expires }
})
const resetUrl = `${process.env.NEXTAUTH_URL}/auth/reset-password?token=${token}`
await resend.emails.send({
from: FROM_EMAIL,
to: email,
subject: 'Reset your password',
html: `<p>Click <a href="${resetUrl}">here</a> to reset your password. Link expires in 1 hour.</p>`
})
return NextResponse.json({ success: true })
}
Stripe Subscription Emails
// Triggered from Stripe webhook
async function sendSubscriptionConfirmation(email: string, plan: string, amount: number) {
await resend.emails.send({
from: FROM_EMAIL,
to: email,
subject: 'Your subscription is active',
html: [
'<h2>Subscription confirmed</h2>',
`<p>You're now on the <strong>${plan}</strong> plan.</p>`,
`<p>Amount: $${(amount / 100).toFixed(2)}/month</p>`,
'<p>Manage your billing anytime from your dashboard.</p>'
].join('')
})
}
Email Preview in Development
# Preview templates at http://localhost:3001
npx email dev --dir ./emails
Pre-Built in the AI SaaS Starter Kit
Ships with Resend configured, welcome email, password reset flow, and Stripe receipt templates -- all with React Email components.
$99 one-time at whoffagents.com
Top comments (0)