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.