Email Service
Better Auth Infrastructure provides a managed transactional email service with pre-built templates for common authentication flows. Send verification emails, password resets, invitations, and more without managing email infrastructure.
Overview
The email service offers:
- Pre-built, professionally designed email templates
- Multiple provider support (AWS SES, SendGrid, Resend)
- Type-safe template variables
- No infrastructure to manage
- Deliverability optimization
Installation
The email service is included in the @better-auth/infra package:
import { sendEmail, createEmailSender } from "@better-auth/infra";Quick Start
Send a Single Email
import { sendEmail } from "@better-auth/infra";
await sendEmail({
template: "verify-email",
to: "user@example.com",
variables: {
verificationUrl: "https://yourapp.com/verify?token=abc123",
userEmail: "user@example.com",
userName: "John",
appName: "Your App",
},
});Create a Reusable Sender
import { createEmailSender } from "@better-auth/infra";
const emailSender = createEmailSender({
apiKey: process.env.BETTER_AUTH_API_KEY,
apiUrl: process.env.BETTER_AUTH_API_URL,
});
// Send multiple emails
await emailSender.send({
template: "reset-password",
to: "user@example.com",
variables: {
resetLink: "https://yourapp.com/reset?token=xyz",
userEmail: "user@example.com",
},
});Available Templates
verify-email
Sends an email verification link to new users.
await sendEmail({
template: "verify-email",
to: "user@example.com",
variables: {
verificationUrl: "https://yourapp.com/verify?token=abc",
userEmail: "user@example.com",
verificationCode: "123456", // Optional: for code-based verification
userName: "John", // Optional
appName: "Your App", // Optional
expirationMinutes: "60", // Optional
},
});reset-password
Sends a password reset link.
await sendEmail({
template: "reset-password",
to: "user@example.com",
variables: {
resetLink: "https://yourapp.com/reset?token=xyz",
userEmail: "user@example.com",
userName: "John", // Optional
appName: "Your App", // Optional
expirationMinutes: "60", // Optional
},
});change-email
Confirms an email address change request.
await sendEmail({
template: "change-email",
to: "newemail@example.com",
variables: {
confirmationLink: "https://yourapp.com/confirm-email?token=abc",
newEmail: "newemail@example.com",
currentEmail: "oldemail@example.com",
userName: "John", // Optional
appName: "Your App", // Optional
expirationMinutes: "60", // Optional
},
});sign-in-otp
Sends a one-time password for passwordless sign-in.
await sendEmail({
template: "sign-in-otp",
to: "user@example.com",
variables: {
otpCode: "123456",
userEmail: "user@example.com",
appName: "Your App", // Optional
expirationMinutes: "10", // Optional
},
});verify-email-otp
Sends an OTP code for email verification.
await sendEmail({
template: "verify-email-otp",
to: "user@example.com",
variables: {
otpCode: "123456",
userEmail: "user@example.com",
appName: "Your App", // Optional
expirationMinutes: "10", // Optional
},
});reset-password-otp
Sends an OTP code for password reset.
await sendEmail({
template: "reset-password-otp",
to: "user@example.com",
variables: {
otpCode: "123456",
userEmail: "user@example.com",
appName: "Your App", // Optional
expirationMinutes: "10", // Optional
},
});magic-link
Sends a magic link for passwordless authentication.
await sendEmail({
template: "magic-link",
to: "user@example.com",
variables: {
magicLink: "https://yourapp.com/auth/magic?token=abc",
userEmail: "user@example.com",
appName: "Your App", // Optional
expirationMinutes: "15", // Optional
},
});two-factor
Sends a two-factor authentication code.
await sendEmail({
template: "two-factor",
to: "user@example.com",
variables: {
otpCode: "123456",
userEmail: "user@example.com",
userName: "John", // Optional
appName: "Your App", // Optional
expirationMinutes: "5", // Optional
},
});invitation
Sends an organization invitation.
await sendEmail({
template: "invitation",
to: "newmember@example.com",
variables: {
inviteLink: "https://yourapp.com/invite?token=abc",
inviterName: "John Smith",
inviterEmail: "john@company.com",
organizationName: "Acme Corp",
role: "Member",
appName: "Your App", // Optional
expirationDays: "7", // Optional
},
});application-invite
Sends an application-level invitation (inviting users to the platform).
await sendEmail({
template: "application-invite",
to: "newuser@example.com",
variables: {
inviteLink: "https://yourapp.com/join?token=abc",
inviterName: "John Smith",
inviterEmail: "john@company.com",
inviteeEmail: "newuser@example.com",
appName: "Your App", // Optional
expirationDays: "7", // Optional
},
});delete-account
Sends account deletion confirmation.
await sendEmail({
template: "delete-account",
to: "user@example.com",
variables: {
deletionLink: "https://yourapp.com/confirm-delete?token=abc",
userEmail: "user@example.com",
userName: "John", // Optional
appName: "Your App", // Optional
expirationMinutes: "60", // Optional
},
});stale-account-user
Notifies a user that their dormant account was accessed.
await sendEmail({
template: "stale-account-user",
to: "user@example.com",
variables: {
userEmail: "user@example.com",
daysSinceLastActive: "90",
loginTime: "February 20, 2026, 3:45 PM UTC",
userName: "John", // Optional
appName: "Your App", // Optional
loginLocation: "New York, US", // Optional
loginDevice: "Chrome on Windows", // Optional
loginIp: "192.168.1.1", // Optional
},
});stale-account-admin
Notifies an admin about dormant account reactivation.
await sendEmail({
template: "stale-account-admin",
to: "admin@yourapp.com",
variables: {
userEmail: "user@example.com",
userId: "user_123",
adminEmail: "admin@yourapp.com",
daysSinceLastActive: "90",
loginTime: "February 20, 2026, 3:45 PM UTC",
userName: "John", // Optional
appName: "Your App", // Optional
loginLocation: "New York, US", // Optional
loginDevice: "Chrome on Windows", // Optional
loginIp: "192.168.1.1", // Optional
},
});Configuration
EmailConfig
interface EmailConfig {
apiKey?: string; // Your Better Auth Infrastructure API key
apiUrl?: string; // Custom API URL (optional)
}Environment Variables
The email service automatically reads from environment variables:
BETTER_AUTH_API_KEY=your_api_key_here
BETTER_AUTH_API_URL=https://api.betterauth.com # OptionalResponse Format
SendEmailResult
interface SendEmailResult {
success: boolean;
messageId?: string; // Email provider message ID
error?: string; // Error message if failed
}Example Usage
const result = await sendEmail({
template: "verify-email",
to: "user@example.com",
variables: {
verificationUrl: "https://yourapp.com/verify?token=abc",
userEmail: "user@example.com",
},
});
if (result.success) {
console.log("Email sent:", result.messageId);
} else {
console.error("Failed to send email:", result.error);
}Plan Requirements
| Feature | Starter | Pro | Business | Enterprise |
|---|---|---|---|---|
| Transactional Email | - | Yes | Yes | Yes |
Transactional email is available on Pro plans and above.
Integration with Better Auth
The email service integrates seamlessly with Better Auth's authentication flows. Here's a complete example:
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "@better-auth/infra";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
async sendResetPassword({ user, url }) {
await sendEmail({
template: "reset-password",
to: user.email,
variables: {
resetLink: url,
userEmail: user.email,
userName: user.name,
appName: "Your App",
},
});
},
},
emailVerification: {
sendOnSignUp: true,
async sendVerificationEmail({ user, url }) {
await sendEmail({
template: "verify-email",
to: user.email,
variables: {
verificationUrl: url,
userEmail: user.email,
userName: user.name,
appName: "Your App",
},
});
},
},
plugins: [
organization({
async sendInvitationEmail(data) {
const inviteLink = `https://yourapp.com/accept-invitation/${data.id}`;
await sendEmail({
template: "invitation",
to: data.email,
variables: {
inviteLink,
inviterName: data.inviter.user.name,
inviterEmail: data.inviter.user.email,
organizationName: data.organization.name,
role: data.role,
appName: "Your App",
},
});
},
}),
],
});Avoid awaiting the email sending in production to prevent timing attacks. On serverless platforms, use waitUntil or similar to ensure the email is sent without blocking the response.
Security Plugin (sentinel)
The `sentinel()` plugin provides comprehensive security and abuse protection for your authentication system. It detects and prevents various attack vectors including credential stuffing, impossible travel, free trial abuse, and more.
SMS Service
Better Auth Infrastructure provides a managed SMS service for sending OTP codes for phone verification and two-factor authentication. Send verification codes without managing SMS providers.