Send Better Auth emails with Resend
Better Auth is a framework-agnostic authentication library for TypeScript. It relies on transactional email for password resets, email verification, magic links, and one-time codes, all of which you can deliver with Resend.
Prerequisites
Section titled “Prerequisites”Before you start, you'll need:
- A Resend API key
- A verified domain
- A project with Better Auth installed
Install the Resend SDK
Section titled “Install the Resend SDK”npm install resendpnpm add resendyarn add resendAdd your API key to the environment:
RESEND_API_KEY=re_xxxxxxxxxSend password reset and verification emails
Section titled “Send password reset and verification emails”Better Auth calls your sendResetPassword and sendVerificationEmail hooks when a user requests a reset or needs to confirm their address. Send the email with Resend inside each hook.
import { betterAuth } from 'better-auth';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
sendResetPassword: ({ user, url }) => {
void resend.emails.send({
from: 'Acme <onboarding@example.com>',
to: user.email,
subject: 'Reset your password',
html: `Click <a href="${url}">here</a> to reset your password.`,
});
},
},
emailVerification: {
sendVerificationEmail: ({ user, url }) => {
void resend.emails.send({
from: 'Acme <onboarding@example.com>',
to: user.email,
subject: 'Verify your email address',
html: `Click <a href="${url}">here</a> to verify your email.`,
});
},
},
});Send magic links (optional)
Section titled “Send magic links (optional)”If you use the magic link plugin, send the sign-in link through the sendMagicLink hook.
import { betterAuth } from 'better-auth';
import { magicLink } from 'better-auth/plugins';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export const auth = betterAuth({
plugins: [
magicLink({
sendMagicLink: ({ email, url }) => {
void resend.emails.send({
from: 'Acme <onboarding@example.com>',
to: email,
subject: 'Sign in to Acme',
html: `Click <a href="${url}">here</a> to sign in.`,
});
},
}),
],
});Send one-time codes (optional)
Section titled “Send one-time codes (optional)”The email OTP plugin sends a one-time password (OTP), a numeric code, instead of a link. The type argument tells you which flow triggered the request so you can tailor the subject line.
import { betterAuth } from 'better-auth';
import { emailOTP } from 'better-auth/plugins';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export const auth = betterAuth({
plugins: [
emailOTP({
sendVerificationOTP: ({ email, otp, type }) => {
void resend.emails.send({
from: 'Acme <onboarding@example.com>',
to: email,
subject:
type === 'sign-in' ? 'Your sign-in code' : 'Your verification code',
html: `Your code is <strong>${otp}</strong>.`,
});
},
}),
],
});Customize email templates
Section titled “Customize email templates”For richer, reusable emails, author your templates with React Email and pass the rendered component to Resend's react field, or reference a Resend template by id.
import { ResetPasswordEmail } from './emails/reset-password';
sendResetPassword: ({ user, url }) => {
void resend.emails.send({
from: 'Acme <onboarding@example.com>',
to: user.email,
subject: 'Reset your password',
react: ResetPasswordEmail({ url }),
});
},Debugging email delivery
Section titled “Debugging email delivery”When a flow doesn't behave as expected, check both sides:
- Better Auth logs show the hook invocation and any errors thrown while sending.
- Resend Logs show the delivery side: accepted, delivered, opened, bounced.
Together, you get end-to-end visibility across the entire delivery chain.