Resend integration
Transactional email via Resend. Two template functions — verification OTP and password reset OTP. Inline CSS, spam-filter safe.
Setup
- Create a free account at resend.com
- Add and verify your sending domain in the Resend dashboard
- Create an API key: API Keys → Create API Key
- Set RESEND_FROM_EMAIL to a verified address on your domain (e.g. no-reply@yourdomain.com)
- Add RESEND_API_KEY and RESEND_FROM_EMAIL to .env.local
Email functions
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
export async function sendVerificationEmail({
to, name, otp
}: { to: string; name: string; otp: string }): Promise<ActionState<void>> {
try {
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to,
subject: `Verify your email — ${process.env.NEXT_PUBLIC_APP_NAME}`,
html: buildVerificationTemplate(name, otp),
text: `Your verification code is: ${otp}. It expires in 10 minutes.`,
})
return { success: true }
} catch {
return {
success: false,
error: 'Failed to send email. Please try again.',
}
}
}sendPasswordResetEmail follows the same pattern with a different subject line and email body copy.
Email templates
Templates use inline CSS only — external stylesheets are stripped by most email clients. The OTP code is rendered in a styled box:
- Background: #f3f4f6
- Border-radius: 8px
- Font-size: 32px, font-family: monospace
- Letter-spacing: 8px
- Centered with padding
Each email includes:
- A plain-text fallback (the text field in Resend's options)
- Max-width: 600px centered layout
- No images (improves deliverability)
- Clear expiry notice (10 minutes)
- Note for password reset: 'If you didn't request this, ignore this email'
Error handling
sendVerificationEmail and sendPasswordResetEmail both return ActionState<void>. On Resend API errors, they return:
{ success: false, error: 'Failed to send email. Please try again.' }Callers (server actions) propagate this ActionState back to the client component, which displays it as a toast error.
Customising templates
To change email content, edit the buildVerificationTemplate and buildPasswordResetTemplate functions in lib/email.ts. Both accept name and otp as parameters.
To change the sender name that appears in the recipient's inbox, update the from field:
from: `${process.env.NEXT_PUBLIC_APP_NAME} <${process.env.RESEND_FROM_EMAIL}>`
// e.g. "MyApp <no-reply@myapp.com>"