# AWS SES Plugin

`@quickback-dev/better-auth-aws-ses` is a Better Auth plugin that sends transactional emails via AWS SES. It handles AWS Signature V4 signing, HTML/text email templates, and rate limiting — designed to work in Cloudflare Workers (no Node.js `aws-sdk` dependency).

> **Cloudflare Email is the default.** As of the Cloudflare Email Sending public beta, the zero-config `send_email` binding is Quickback's default transport for Cloudflare deploys — no API keys, no SES setup. See [Email Configuration](/compiler/config#email-configuration). Use AWS SES when you need it (non-Cloudflare runtimes, existing SES infra, or specific deliverability/compliance requirements).

## Installation

```bash
npm install @quickback-dev/better-auth-aws-ses
```

This plugin is wired up only when you explicitly opt in via `email.provider: 'aws-ses'` in your Quickback config. Without that, `email.provider` defaults to `'cloudflare'` and the Cloudflare Email helper is used instead.

## Email Types

The plugin handles 8 email types:

| Endpoint | When Sent |
|----------|-----------|
| `/ses/send-verification` | Signup email verification |
| `/ses/send-password-reset` | Password reset request |
| `/ses/send-magic-link` | Passwordless sign-in link |
| `/ses/send-otp` | One-time password code |
| `/ses/send-welcome` | Post-signup welcome email |
| `/ses/send-organization-invitation` | Team/org invite with role |
| Combo auth | Magic link + OTP in a single email |
| Delete account | Account deletion confirmation |

### Combo Auth Mode

When `useComboAuthEmail: true` is set, the plugin sends a single email that includes both a magic link button and an OTP code. Users can choose either method to authenticate.

## AWS Signature V4

The plugin signs requests to SES using AWS Signature V4 with the Web Crypto API (`crypto.subtle`), making it compatible with Cloudflare Workers without any AWS SDK dependency.

## Templates

Each email type has both HTML and plain text templates. Templates include:

- Responsive HTML layout with MSO (Microsoft Outlook) support
- Security warning banners for sensitive operations
- Fallback plain text versions for all email clients
- XSS prevention via HTML escaping and URL sanitization

### Per-Type Template Overrides

Override the default subject + HTML + text for any OTP type without forking the package. Drop a `.ts` file into `quickback/email-templates/` and reference it from `plugins.emailOtp.templates`:

```typescript
// quickback/quickback.config.ts
defineAuth("better-auth", {
  plugins: {
    emailOtp: {
      templates: {
        'forget-password': './email-templates/forget-password',
        'welcome':         './email-templates/welcome',
      },
    },
  },
});
```

Each template module default-exports a function that returns `{ subject, html, text }`:

```typescript
// quickback/email-templates/welcome.ts
interface Input {
  email: string;
  otp: string;
  type: 'sign-in' | 'email-verification' | 'forget-password' | 'welcome';
  name?: string;
  appName: string;
  appUrl: string;
  supportEmail?: string;
}

export default function welcomeTemplate({ otp, name, appName, appUrl }: Input) {
  const greeting = name ? `Hi ${name},` : 'Hi,';
  return {
    subject: `Welcome to ${appName} — set your password`,
    html: `
      <h1>Welcome to ${appName}</h1>
      <p>${greeting} your account is ready. Use this code to set your password and sign in:</p>
      <p style="font-size:32px;letter-spacing:8px"><b>${otp}</b></p>
      <p>Visit <a href="${appUrl}/set-password">${appUrl}/set-password</a> to finish setup.</p>
    `,
    text: `${greeting}\n\nYour ${appName} code: ${otp}\n\nFinish setup at ${appUrl}/set-password`,
  };
}
```

Recognized keys:

| Key | When it fires |
|-----|---------------|
| `'sign-in'` | OTP sign-in flow |
| `'email-verification'` | Signup email verification |
| `'forget-password'` | Password reset request — and admin-bootstrap if `welcome` isn't set |
| `'welcome'` | **Synthetic** — routed when `forget-password` fires for a user with no credential row yet (the admin-plugin → bootstrap flow). Without it, those sends fall through to `forget-password`. |

Missing keys fall through to the built-in defaults from `@quickback-dev/better-auth-aws-ses`. The compiler stages each referenced file into `src/email-templates/<name>.ts` and wires the default import in `src/lib/auth.ts` — the template function is called inline from `sendVerificationOTP`, so it has access to whatever modules the file imports.

> **Provider scope.** Overrides are only honored when `email.provider: 'aws-ses'`. The Cloudflare Email path uses the helper's own template pipeline and ignores `plugins.emailOtp.templates`.

## Configuration

### Server Plugin

```typescript
import { awsSESPlugin } from "@quickback-dev/better-auth-aws-ses";

const auth = betterAuth({
  plugins: [
    awsSESPlugin({
      region: env.AWS_SES_REGION,
      accessKeyId: env.AWS_ACCESS_KEY_ID,
      secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
      fromEmail: env.EMAIL_FROM,
      fromName: env.EMAIL_FROM_NAME || "My App",
      replyTo: env.EMAIL_REPLY_TO,
      appName: env.APP_NAME || "My App",
      appUrl: env.APP_URL,
      useComboAuthEmail: true, // Optional: combine magic link + OTP
    }),
  ],
});
```

### Client Plugin

```typescript
import { awsSESClientPlugin } from "@quickback-dev/better-auth-aws-ses/client";

const authClient = createAuthClient({
  plugins: [awsSESClientPlugin()],
});
```

## Required Environment Variables

| Variable | Description |
|----------|-------------|
| `AWS_ACCESS_KEY_ID` | AWS access key (use Wrangler secrets) |
| `AWS_SECRET_ACCESS_KEY` | AWS secret key (use Wrangler secrets) |
| `AWS_SES_REGION` | SES region (e.g., `us-east-2`) |
| `EMAIL_FROM` | Sender email address (must be SES-verified) |

## Optional Variables

| Variable | Description |
|----------|-------------|
| `EMAIL_FROM_NAME` | Sender display name |
| `EMAIL_REPLY_TO` | Reply-to address (defaults to `EMAIL_FROM`) |
| `APP_NAME` | Application name shown in email templates |
| `APP_URL` | Application URL for links in emails |
| `BETTER_AUTH_URL` | Auth API URL (for verification/reset links) |

## Rate Limiting

The plugin includes basic in-memory rate limiting per recipient:

- Configurable hourly and daily limits
- Per-recipient tracking
- For production, consider using an external store (Redis, KV)

```typescript
awsSESPlugin({
  // ...
  rateLimit: {
    maxEmailsPerHour: 10,
    maxEmailsPerDay: 50,
  },
})
```

## Error Handling

- Email failures return `false` (don't throw) — signup/login flows continue even if email delivery fails
- Invalid or placeholder AWS credentials are detected at startup
- Detailed console logging for debugging delivery issues

## See Also

- [Combo Auth Plugin](/stack/auth/plugins/combo-auth) — Combined magic link + OTP authentication
- [Auth Configuration](/compiler/config/providers) — Configuring auth plugins
