Documentation Index
Fetch the complete documentation index at: https://inbound.new/docs/llms.txt
Use this file to discover all available pages before exploring further.
This plugin is in beta and may not be stable.
The Inbound Better Auth plugin automatically sends transactional emails for important authentication events like password changes, new device sign-ins, and account creation.
Installation
The plugin is included in the inboundemail package in version 0.20.0 and above. You’ll also need better-auth installed:
npm install inboundemail better-auth
Quick Start
Server Setup
import { betterAuth } from 'better-auth';
import { inboundEmailPlugin } from 'inboundemail/better-auth';
export const auth = betterAuth({
// ... your Better Auth config
plugins: [
inboundEmailPlugin({
client: { apiKey: process.env.INBOUND_API_KEY! },
from: 'security@yourdomain.com',
}),
],
});
Client Setup (Optional)
For proper type inference on the client side:
import { createAuthClient } from 'better-auth/react';
import { inboundEmailClientPlugin } from 'inboundemail/better-auth/client';
export const authClient = createAuthClient({
plugins: [inboundEmailClientPlugin()],
});
Supported Events
The plugin automatically sends emails for these authentication events:
| Event | Description | Default |
|---|
password-changed | User changed their password | Enabled |
email-changed | User changed their email address | Enabled |
new-device-sign-in | Sign-in from a new device/browser | Enabled |
account-created | New account registration | Enabled |
password-reset-requested | Password reset was requested | Disabled |
two-factor-enabled | 2FA was enabled on account | Enabled |
two-factor-disabled | 2FA was disabled on account | Enabled |
Configuration Options
inboundEmailPlugin({
// Required: Inbound client or API key
client: { apiKey: process.env.INBOUND_API_KEY! },
// Or pass an existing client instance:
// client: new Inbound({ apiKey: '...' }),
// Required: Default "from" address for all emails
from: 'security@yourdomain.com',
// Optional: Include device info in new sign-in emails (default: true)
includeDeviceInfo: true,
// Optional: Configure specific events
events: {
'password-changed': {
enabled: true,
from: 'alerts@yourdomain.com', // Override from address
template: customTemplate, // Custom template function
},
'password-reset-requested': {
enabled: true, // Enable this disabled-by-default event
},
},
// Optional: Lifecycle hooks
onBeforeSend: async (event, context, email) => {
// Return false to cancel sending
return true;
},
onAfterSend: async (event, context, result) => {
console.log(`Sent ${event} email:`, result.id);
},
onError: async (event, context, error) => {
console.error(`Failed to send ${event} email:`, error);
},
});
Custom Templates
You can customize the email content for any event type:
inboundEmailPlugin({
client: { apiKey: process.env.INBOUND_API_KEY! },
from: 'security@yourdomain.com',
events: {
'password-changed': {
template: (ctx) => ({
subject: `🔐 Password Changed - ${ctx.timestamp}`,
html: `
<h1>Password Updated</h1>
<p>Hi ${ctx.name || 'there'},</p>
<p>Your password was changed on ${new Date(ctx.timestamp).toLocaleString()}.</p>
${ctx.otherSessionsRevoked ? '<p>All other sessions have been signed out.</p>' : ''}
<p>If this wasn't you, please contact support immediately.</p>
`,
text: `Hi ${ctx.name || 'there'}, your password was changed...`,
}),
},
},
});
Template Context by Event Type
Each event type receives different context data:
password-changed
{
email: string;
name?: string | null;
userId: string;
timestamp: string; // ISO 8601
otherSessionsRevoked: boolean;
}
email-changed
{
email: string; // Old email (notification sent here)
name?: string | null;
userId: string;
timestamp: string;
oldEmail: string;
newEmail: string;
}
new-device-sign-in
{
email: string;
name?: string | null;
userId: string;
timestamp: string;
ipAddress?: string | null;
userAgent?: string | null;
device?: {
browser?: string; // e.g., "Chrome", "Safari"
os?: string; // e.g., "macOS", "Windows"
type?: 'desktop' | 'mobile' | 'tablet' | 'unknown';
};
location?: {
city?: string;
country?: string;
};
}
account-created
{
email: string;
name?: string | null;
userId: string;
timestamp: string;
method: 'email' | 'social' | 'magic-link' | 'passkey';
provider?: string; // e.g., "google", "github" for social
}
password-reset-requested
{
email: string;
name?: string | null;
userId: string;
timestamp: string;
token?: string;
resetUrl?: string;
}
two-factor-enabled / two-factor-disabled
{
email: string;
name?: string | null;
userId: string;
timestamp: string;
method?: string; // e.g., "totp"
}
Organization Events
These events are triggered when using Better Auth’s organization plugin.
organization-invitation-sent
Sent to the invitee when they are invited to join an organization.
{
email: string; // Invitee's email
inviterName?: string | null;
inviterEmail?: string | null;
organizationName: string;
organizationId: string;
role: string;
inviteLink?: string;
timestamp: string;
expiresAt?: string;
}
organization-invitation-accepted
Sent when a user accepts an organization invitation.
{
email: string;
name?: string | null;
userId: string;
organizationName: string;
organizationId: string;
role: string;
timestamp: string;
notifyEmail: string; // Org admin to notify
}
organization-member-removed
Sent to a user when they are removed from an organization.
{
email: string;
name?: string | null;
userId: string;
organizationName: string;
organizationId: string;
removedByName?: string | null;
reason?: string;
timestamp: string;
}
organization-role-changed
Sent when a member’s role in an organization is updated.
{
email: string;
name?: string | null;
userId: string;
organizationName: string;
organizationId: string;
previousRole: string;
newRole: string;
changedByName?: string | null;
timestamp: string;
}
Social Account Events
These events are triggered when users link or unlink social/OAuth accounts.
social-account-linked
Sent when a user connects a social account (Google, GitHub, etc.).
{
email: string;
name?: string | null;
userId: string;
providerName: string; // e.g., "Google", "GitHub"
providerId: string; // e.g., "google", "github"
providerAccountId?: string;
timestamp: string;
}
social-account-unlinked
Sent when a user disconnects a social account.
{
email: string;
name?: string | null;
userId: string;
providerName: string;
providerId: string;
timestamp: string;
Pre-built React Email Templates
The SDK includes beautiful, responsive React Email templates for all auth events. Install @react-email/components to use them:
npm install @react-email/components
Then import and use the pre-built templates:
import { inboundEmailPlugin } from 'inboundemail/better-auth';
import {
BetterAuthPasswordChanged,
BetterAuthEmailChanged,
BetterAuthNewDeviceSignin,
BetterAuthMagicLink,
BetterAuthPasswordReset,
BetterAuthVerifyEmail,
} from 'inboundemail/better-auth/react-email';
inboundEmailPlugin({
client: { apiKey: process.env.INBOUND_API_KEY! },
from: 'security@yourdomain.com',
events: {
'password-changed': {
template: (ctx) => ({
subject: 'Your password was changed',
react: (
<BetterAuthPasswordChanged
userEmail={ctx.email}
timestamp={new Date(ctx.timestamp).toLocaleString()}
appName="My App"
supportEmail="support@myapp.com"
/>
),
}),
},
'new-device-sign-in': {
template: (ctx) => ({
subject: 'New sign-in detected',
react: (
<BetterAuthNewDeviceSignin
userEmail={ctx.email}
deviceInfo={{
browser: ctx.device?.browser,
os: ctx.device?.os,
ipAddress: ctx.ipAddress ?? undefined,
timestamp: new Date(ctx.timestamp).toLocaleString(),
}}
appName="My App"
/>
),
}),
},
},
});
Available Templates
Authentication Templates
| Template | Description | Props |
|---|
BetterAuthPasswordChanged | Password change notification | userEmail, timestamp, appName, supportEmail, logoUrl, secureAccountLink |
BetterAuthEmailChanged | Email change notification | oldEmail, newEmail, appName, supportEmail, logoUrl, revertLink |
BetterAuthNewDeviceSignin | New device login alert | userEmail, deviceInfo, appName, supportEmail, logoUrl, secureAccountLink |
BetterAuthMagicLink | Magic link sign-in | magicLink, userEmail, appName, expirationMinutes, logoUrl |
BetterAuthPasswordReset | Password reset email | resetLink, userEmail, appName, expirationMinutes, logoUrl |
BetterAuthVerifyEmail | Email verification OTP | verificationCode, userEmail, appName, expirationMinutes, logoUrl |
Organization Templates
| Template | Description | Props |
|---|
BetterAuthOrganizationInvitation | Organization invitation | inviterName, inviterEmail, organizationName, role, inviteLink, expiresAt, appName, logoUrl |
BetterAuthOrganizationMemberJoined | Member joined notification | memberName, memberEmail, organizationName, role, timestamp, appName, logoUrl |
BetterAuthOrganizationMemberRemoved | Member removed notification | userName, organizationName, removedByName, reason, timestamp, appName, supportEmail, logoUrl |
BetterAuthOrganizationRoleChanged | Role change notification | userName, organizationName, previousRole, newRole, changedByName, timestamp, appName, supportEmail, logoUrl |
Social Account Templates
| Template | Description | Props |
|---|
BetterAuthSocialAccountLinked | Social account connected | userName, userEmail, providerName, timestamp, appName, supportEmail, logoUrl, secureAccountLink |
BetterAuthSocialAccountUnlinked | Social account disconnected | userName, userEmail, providerName, timestamp, appName, supportEmail, logoUrl, secureAccountLink |
Customizing the Design System
The betterAuthDesignSystem export provides a complete design token system you can use for consistent styling:
import { betterAuthDesignSystem } from 'inboundemail/better-auth/react-email';
const ds = betterAuthDesignSystem;
// Colors
ds.colors.background.outside // '#FFFFFF' - outer background
ds.colors.background.inside // '#FFFFFF' - inner background
ds.colors.text.primary // '#121212' - headings
ds.colors.text.secondary // '#444444' - body text
ds.colors.text.tertiary // '#666666' - muted text
ds.colors.text.quaternary // '#767676' - footer text
ds.colors.border.main // '#E7E5E4' - borders
// Typography
ds.typography.fontFamily.sans // 'Gesit, Inter, -apple-system, ...'
ds.typography.heading // { fontSize: '20px', fontWeight: 600, ... }
ds.typography.body // { fontSize: '14px', fontWeight: 400, ... }
ds.typography.small // { fontSize: '12px', ... }
// Buttons
ds.buttons.primary.backgroundColor // '#121212'
ds.buttons.primary.color // '#FFFFFF'
ds.buttons.secondary.backgroundColor // '#FFFFFF'
// Spacing
ds.spacing.xs // '4px'
ds.spacing.sm // '8px'
ds.spacing.md // '12px'
ds.spacing.lg // '16px'
ds.spacing.xl // '24px'
// Card styling
ds.card.backgroundColor // '#FFFFFF'
ds.card.padding // '20px'
ds.card.border // { color: '#E7E5E4', style: 'solid', width: '1px' }
You can override these tokens when creating your own templates:
const myDesignSystem = {
...betterAuthDesignSystem,
colors: {
...betterAuthDesignSystem.colors,
text: {
...betterAuthDesignSystem.colors.text,
primary: '#1a1a2e', // Custom dark blue
},
},
};
Custom React Email Templates
Create your own templates using @react-email/components:
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
import { betterAuthDesignSystem } from 'inboundemail/better-auth/react-email';
interface MyCustomEmailProps {
userName: string;
actionUrl: string;
}
export const MyCustomEmail = ({ userName, actionUrl }: MyCustomEmailProps) => {
const ds = betterAuthDesignSystem;
return (
<Html>
<Head />
<Preview>Action required for your account</Preview>
<Tailwind>
<Body style={{ backgroundColor: '#F5F5F4', fontFamily: ds.typography.fontFamily.sans }}>
<Container className="mx-auto my-[40px] max-w-[600px]">
<Section
style={{
backgroundColor: ds.card.backgroundColor,
borderColor: ds.colors.border.main,
border: '1px solid',
padding: ds.card.padding,
}}
>
<Heading style={{ color: ds.colors.text.primary }}>
Hello, {userName}!
</Heading>
<Text style={{ color: ds.colors.text.secondary }}>
Please take action on your account.
</Text>
<Button
href={actionUrl}
style={{
backgroundColor: ds.buttons.primary.backgroundColor,
color: ds.buttons.primary.color,
padding: '12px 24px',
textDecoration: 'none',
}}
>
Take Action
</Button>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
Then use it in your plugin configuration:
import { MyCustomEmail } from './emails/my-custom-email';
inboundEmailPlugin({
client: { apiKey: process.env.INBOUND_API_KEY! },
from: 'security@yourdomain.com',
events: {
'password-changed': {
template: (ctx) => ({
subject: 'Password Changed',
react: <MyCustomEmail userName={ctx.name ?? 'User'} actionUrl="https://myapp.com/security" />,
}),
},
},
});
Disabling Events
To disable specific events:
inboundEmailPlugin({
client: { apiKey: process.env.INBOUND_API_KEY! },
from: 'security@yourdomain.com',
events: {
'account-created': { enabled: false },
'new-device-sign-in': { enabled: false },
},
});
How It Works
The plugin uses Better Auth’s after hooks to intercept successful authentication operations:
- Password changes: Hooks into
/change-password endpoint
- Email changes: Hooks into
/change-email endpoint
- Sign-ins: Hooks into
/sign-in/email, /sign-in/social, /sign-in/magic-link, /sign-in/passkey
- Sign-ups: Hooks into
/sign-up/email, /sign-up/social
- 2FA changes: Hooks into
/two-factor/enable, /two-factor/disable
New device detection works by tracking unique combinations of IP address and user agent per user. The first sign-in from a new device/browser triggers a notification email.
TypeScript Support
All types are exported for full TypeScript support:
import type {
InboundEmailPluginOptions,
AuthEventType,
AuthEventContext,
EmailContent,
EmailTemplateFunction,
PasswordChangedContext,
EmailChangedContext,
NewDeviceSignInContext,
AccountCreatedContext,
} from 'inboundemail/better-auth';