Hello, I need help with my recent order...
',
attachments: [
{
filename: 'order-receipt.pdf',
contentType: 'application/pdf',
size: 45678,
contentId: '',
contentDisposition: 'attachment',
downloadUrl: 'https://inbound.new/api/e2/attachments/inbnd_abc123/parts/0'
}
]
}
},
endpoint: {
id: 'endp_xyz789',
name: 'Support Webhook',
type: 'webhook'
}
}
```
## Webhook Security
Always verify webhook requests before processing them to prevent unauthorized access.
### Verification Headers
Every webhook request includes security headers:
| Header | Description |
|--------|-------------|
| `X-Webhook-Verification-Token` | Unique verification token for your endpoint |
| `X-Endpoint-ID` | ID of the endpoint that triggered this webhook |
| `X-Webhook-Event` | Event type (e.g., `email.received`) |
| `X-Webhook-Timestamp` | ISO 8601 timestamp of when the webhook was sent |
### Verifying with the SDK
```typescript
import { Inbound, verifyWebhookFromHeaders } from 'inboundemail'
const inbound = new Inbound(process.env.INBOUND_API_KEY!)
export async function POST(request: Request) {
// Verify webhook authenticity before processing
const isValid = await verifyWebhookFromHeaders(request.headers, inbound)
if (!isValid) {
return new Response('Unauthorized', { status: 401 })
}
// Process the verified webhook payload
const payload: InboundWebhookPayload = await request.json()
const { email } = payload
console.log('Received verified email:', email.subject)
return new Response('OK', { status: 200 })
}
```
## Downloading Attachments
Each attachment includes a `downloadUrl` for direct file access:
```typescript
// Download attachments from webhook payload
for (const attachment of email.parsedData.attachments) {
const response = await fetch(attachment.downloadUrl, {
headers: {
'Authorization': `Bearer ${process.env.INBOUND_API_KEY}`
}
})
if (response.ok) {
const fileBuffer = await response.arrayBuffer()
// Process the file...
}
}
```
> **Note:** Authentication via API key in the Authorization header is required to download attachments.
# Better Auth
Source: https://inbound.new/docs/integrations/better-auth
Automatically send transactional emails for authentication events with Better Auth
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:
```bash theme={null}
npm install inboundemail better-auth
```
## Quick Start
### Server Setup
```typescript theme={null}
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:
```typescript theme={null}
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
```typescript theme={null}
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:
```typescript theme={null}
inboundEmailPlugin({
client: { apiKey: process.env.INBOUND_API_KEY! },
from: 'security@yourdomain.com',
events: {
'password-changed': {
template: (ctx) => ({
subject: `🔐 Password Changed - ${ctx.timestamp}`,
html: `
Password Updated
Hi ${ctx.name || 'there'},
Your password was changed on ${new Date(ctx.timestamp).toLocaleString()}.
${ctx.otherSessionsRevoked ? 'All other sessions have been signed out.
' : ''}
If this wasn't you, please contact support immediately.
`,
text: `Hi ${ctx.name || 'there'}, your password was changed...`,
}),
},
},
});
```
### Template Context by Event Type
Each event type receives different context data:
#### `password-changed`
```typescript theme={null}
{
email: string;
name?: string | null;
userId: string;
timestamp: string; // ISO 8601
otherSessionsRevoked: boolean;
}
```
#### `email-changed`
```typescript theme={null}
{
email: string; // Old email (notification sent here)
name?: string | null;
userId: string;
timestamp: string;
oldEmail: string;
newEmail: string;
}
```
#### `new-device-sign-in`
```typescript theme={null}
{
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`
```typescript theme={null}
{
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`
```typescript theme={null}
{
email: string;
name?: string | null;
userId: string;
timestamp: string;
token?: string;
resetUrl?: string;
}
```
#### `two-factor-enabled` / `two-factor-disabled`
```typescript theme={null}
{
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.
```typescript theme={null}
{
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.
```typescript theme={null}
{
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.
```typescript theme={null}
{
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.
```typescript theme={null}
{
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.).
```typescript theme={null}
{
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.
```typescript theme={null}
{
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:
```bash theme={null}
npm install @react-email/components
```
Then import and use the pre-built templates:
```tsx theme={null}
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: (
),
}),
},
'new-device-sign-in': {
template: (ctx) => ({
subject: 'New sign-in detected',
react: (
),
}),
},
},
});
```
### 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:
```typescript theme={null}
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:
```tsx theme={null}
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`:
```tsx theme={null}
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 (