# `createClient().auth.admin.email.template`

Normalized naming for template bindings:

```ts
createClient().auth.admin.email.template.list() -> /admin/email-template/list
createClient().auth.admin.email.template.get() -> /admin/email-template/get
createClient().auth.admin.email.template.create() -> /admin/email-template/create
createClient().auth.admin.email.template.update() -> /admin/email-template/update
createClient().auth.admin.email.template.delete() -> /admin/email-template/delete
```

This replaces the older flat naming form `auth.admin.emailTemplate.*`.

Email template payloads now support `eventType` for canonical event taxonomy mapping.

Email template payloads also support additive structured `variableBindings`
entries alongside legacy `variables`. Each binding is stored as an object with:

- `name`
- `source`
- `required` (optional boolean, defaults to `false`)

## Binding contract

`variableBindings` is additive. Existing `variables: string[]` behavior stays
supported for explicitly supplied render variables such as
`verification_url`, `reset_url`, `invitation_url`, and similar built-in flow
values.

Each `variableBindings` entry currently accepts:

```json
{
  "name": "organization_slug",
  "source": "athena.organizations.slug",
  "required": true
}
```

Rules:

- `name` must be unique within the template
- `source` must use `schema.table.column`
- unsupported or unsafe sources do not resolve and the renderer falls back to
  the built-in template body rather than sending arbitrary fetched data
- `required: true` means the binding is expected to resolve for that email flow

## Currently supported binding sources

The current backend implementation supports tenant-safe auth-native tables:

- `athena.users.<column>`
- `athena.user.<column>`
- `athena.organizations.<column>`
- `athena.organization.<column>`
- `athena.sessions.<column>`
- `athena.session.<column>`

Safe behavior notes:

- `athena.sessions.token` is explicitly blocked and is never exposed
- session tokens may be used only as routing context to derive `user_id` and
  `active_organization_id`
- organization invitation emails bind `athena.users.*` to the inviter user
  anchor because there may be no invited user row yet

## Render anchors by built-in flow

Current built-in flow anchoring:

- verification email: `user_id`
- password reset email: `user_id`
- change-email confirmation: `user_id`
- account deletion confirmation: `user_id`
- two-factor OTP: `user_id`
- organization invitation: `organization_id` plus inviter `user_id`

If a required anchor is unavailable for a binding, the custom template does not
get used for that render and Athena Auth falls back to the built-in template.

Canonical event catalog route (admin-authenticated):

```http
GET /admin/email-event-type/list
```

## Downstream updates still needed

### `athena-auth-ui`

Current UI state in `athena-auth-ui` still uses:

- `packages/heroui/src/components/auth/workspace/hooks.ts`
- `packages/heroui-native/src/components/auth/workspace/hooks.ts`
- `email-templates-card.tsx` comma-separated `variables` editing

Required follow-up:

- add a `variableBindings` array to the workspace input types
- replace the flat comma-separated field with structured rows for:
  - binding name
  - binding source
  - required toggle
- keep legacy `variables` editable for flow-supplied placeholders
- show native examples such as `athena.users.id` and
  `athena.organizations.slug`
- keep loading/editing behavior compatible with existing saved templates

### `athena-js`

Current SDK types still expose only `variables?: string[]` in
`src/auth/types.ts`.

Required follow-up:

- add a typed `variableBindings?: Array<{ name: string; source: string; required?: boolean }>`
  field to admin email-template create/update request interfaces
- expose the same field on any normalized admin email-template record type
- regenerate or update any derived auth admin method types that mirror
  `/admin/email-template/create` and `/admin/email-template/update`
- keep legacy `variables` in place for compatibility rather than replacing it
