# Auth · Email

> Email registration, sign-in, recovery and verification, plus every account mail the module sends.

Email as an identifier and email as a channel. The first half of this reference registers, signs in and recovers
accounts by address; the second half sends the mail that makes those flows work.

## Challenge, then complete

Registration, sign-in and recovery are each a pair. A `POST …/challenge` mails a link or a code; the
matching `POST` redeems what came back and returns a session. Nothing is created until the second call,
which is what makes the address proven rather than merely typed.

Every challenge answers `200` whether or not the address has an account. That is deliberate: an endpoint
that answered differently would be a way to find out which addresses are registered.

## Sending mail directly

The `notification` routes are admin-guarded and send one specific message each. All of them take
`?preview=true`, which renders the message and returns it as HTML instead of sending it — the way to see
what a template produces without mailing anybody.

## Not implemented

Three routes under `…/verification` answer `501`. They are documented here because a
`501` you were not expecting costs more time than one you were told about. Verifying an address on an
existing account works through [the identity routes](/docs/auth-identity).

## List email addresses

`GET /api/auth/email`

Guard: admin

Every verified address on the deployment, paged by cursor. Unverified ones live at `/api/auth/email/unverified`.

### Parameters

- `prefix` _string_ — Narrow to addresses starting with this. Query parameter.
- `cursor` _string_ — Continue from a previous page. Query parameter.
- `limit` _number_ — Page size. Query parameter.

### Returns

The page of addresses, and a cursor when there is more.

### Request (cURL)

```
curl "https://example.com/api/auth/email?limit=2" \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "addresses": ["ada@example.com", "grace@example.com"],
  "cursor": null
}
```

## Look up the identity behind an address

`GET /api/auth/email/:address`

Guard: open

Resolves an address to the identity that registered it. `404` when nothing claims it — which is the answer a registration form is checking for.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.

### Returns

`{ identity }`, or `404`.

### Request (cURL)

```
curl https://example.com/api/auth/email/ada%40example.com
```

### Response

```json
{
  "identity": {
    "id": "01J2788XY98FDTA410C8S716CD",
    "kind": "member",
    "displayName": "Ada Lovelace"
  }
}
```

## Check whether an address is registered

`HEAD /api/auth/email/:address`

Guard: open

The same lookup with no body — the right call for an availability check.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.

### Returns

`200` when registered, `404` when free.

### Request (cURL)

```
curl -I https://example.com/api/auth/email/ada%40example.com
```

### Response

```json
{}
```

## List unverified addresses

`GET /api/auth/email/unverified`

Guard: session

Addresses that have been offered but not yet proven — a registration begun and not finished.

### Parameters

- `cursor` _string_ — Continue from a previous page. Query parameter.
- `limit` _number_ — Page size. Query parameter.

### Returns

The page of unverified addresses, and a cursor when there is more.

### Request (cURL)

```
curl https://example.com/api/auth/email/unverified \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "addresses": ["grace@example.com"],
  "cursor": null
}
```

## Check the unverified listing

`HEAD /api/auth/email/unverified`

Guard: session

The same call with no body.

### Returns

`200`.

### Request (cURL)

```
curl -I https://example.com/api/auth/email/unverified \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{}
```

## Read an unverified address record

`GET /api/auth/email/unverified/:address`

Guard: session

What is known about an address that has been offered but not proven — when it was seen, and what it was offered for.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.

### Returns

`{ record }`. `record` is `null` when nothing has been recorded.

### Request (cURL)

```
curl https://example.com/api/auth/email/unverified/grace%40example.com \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "record": {
    "address": "grace@example.com",
    "challenged": 1767225600
  }
}
```

## Check for an unverified record

`HEAD /api/auth/email/unverified/:address`

Guard: session

The same read with no body.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.

### Returns

`200`.

### Request (cURL)

```
curl -I https://example.com/api/auth/email/unverified/grace%40example.com \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{}
```

## Begin email registration

`POST /api/auth/email/:address/registration/challenge`

Guard: open

Mails a registration challenge to the address. Answers `200` whether or not the address is already taken, so it cannot be used to find out which addresses have accounts.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `kind` _enum_ — The authentication payload the challenge is for.
  - One of: `email`
- `method` _enum_ — How the challenge is delivered — a link or a code.
  - One of: `verification`, `code`
- `displayName` _string_ — Carried through to the account when the flow completes.

### Returns

`200`. The challenge itself arrives by mail.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/registration/challenge \
  -H "content-type: application/json" \
  -d '{ "kind": "email", "method": "verification", "email": "ada@example.com" }'
```

### Response

```json
{
  "ok": true
}
```

## Complete email registration

`POST /api/auth/email/:address/registration`

Guard: open

Redeems the token from the challenge mail and creates the account, signed in. This is passwordless registration: the address is proven before the identity exists.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `verification` _string_ **Required** — The token from the challenge mail.

### Returns

A session — identity, identity token and refresh token — with both cookies set.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/registration \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "identity": { "id": "01J2788XY98FDTA410C8S716CD", "kind": "member" },
  "identityToken": "eyJhbGciOi…",
  "refreshToken": "eyJhbGciOi…"
}
```

## Begin email sign-in

`POST /api/auth/email/:address/signin/challenge`

Guard: open

Mails a magic link or a code to the address. Answers `200` whether or not the address has an account.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `kind` _enum_ — The authentication payload the challenge is for.
  - One of: `email`
- `method` _enum_ — How the challenge is delivered — a link or a code.
  - One of: `verification`, `code`
- `displayName` _string_ — Carried through to the account when the flow completes.

### Returns

`200`. The challenge itself arrives by mail.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/signin/challenge \
  -H "content-type: application/json" \
  -d '{ "kind": "email", "method": "verification", "email": "ada@example.com" }'
```

### Response

```json
{
  "ok": true
}
```

## Complete email sign-in

`POST /api/auth/email/:address/signin`

Guard: open

Redeems the token from the mail and returns a session.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `verification` _string_ **Required** — The token from the challenge mail.

### Returns

A session — identity, identity token and refresh token — with both cookies set.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/signin \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "identity": { "id": "01J2788XY98FDTA410C8S716CD", "kind": "member" },
  "identityToken": "eyJhbGciOi…",
  "refreshToken": "eyJhbGciOi…"
}
```

## Begin email recovery

`POST /api/auth/email/:address/recovery/challenge`

Guard: open

Mails a recovery challenge. This is the way back in for somebody who has lost their passphrase.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `kind` _enum_ — The authentication payload the challenge is for.
  - One of: `email`
- `method` _enum_ — How the challenge is delivered — a link or a code.
  - One of: `verification`, `code`
- `displayName` _string_ — Carried through to the account when the flow completes.

### Returns

`200`. The challenge itself arrives by mail.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/recovery/challenge \
  -H "content-type: application/json" \
  -d '{ "kind": "email", "method": "verification", "email": "ada@example.com" }'
```

### Response

```json
{
  "ok": true
}
```

## Complete email recovery

`POST /api/auth/email/:address/recovery`

Guard: open

Redeems the token and returns a session, far enough in to set a new passphrase at `POST /api/auth/identity/:id/passphrase`.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `verification` _string_ **Required** — The token from the challenge mail.

### Returns

A session — identity, identity token and refresh token — with both cookies set.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/recovery \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "identity": { "id": "01J2788XY98FDTA410C8S716CD", "kind": "member" },
  "identityToken": "eyJhbGciOi…",
  "refreshToken": "eyJhbGciOi…"
}
```

## Check an address’s verification state

`GET /api/auth/email/:address/verification`

Guard: open

`Not implemented.` Answers `501`.

Whether an address is verified is answerable today from the directories:
`HEAD /api/auth/email/:address` finds a verified one, and
`GET /api/auth/email/unverified/:address` finds one still waiting.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.

### Returns

`501`.

### Request (cURL)

```
curl https://example.com/api/auth/email/ada%40example.com/verification
```

### Response

```json
{
  "status": 501,
  "statusText": "Not Implemented"
}
```

## Send a verification challenge

`POST /api/auth/email/:address/verification/challenge`

Guard: open

`Not implemented.` Answers `501`, with the challenge result echoed back.

The working path for verifying an address on an existing account is
`POST /api/auth/identity/:id/email/challenge` then
`POST /api/auth/identity/:id/email/verify`. To send the mail directly, admins have
`POST /api/auth/email/:address/notification/verification/challenge`.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.

### Returns

`501`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/verification/challenge \
  -H "content-type: application/json" \
  -d '{}'
```

### Response

```json
{
  "status": 501,
  "statusText": "Not Implemented"
}
```

## Redeem a verification

`POST /api/auth/email/:address/verification/verify`

Guard: open

`Not implemented.` Answers `501`, with the verify result echoed back.

Use `POST /api/auth/identity/:id/email/verify`, which is the same act against an account that exists.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `verification` _string_ — The token from the challenge mail.

### Returns

`501`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/verification/verify \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "status": 501,
  "statusText": "Not Implemented"
}
```

Sends account mail to an address. Every route in this family takes `?preview=true`, which renders the
message and returns it as HTML instead of sending it — the way to see what a template produces without mailing
anybody.

## Send an arbitrary message

`POST /api/auth/email/:address/notification`

Guard: admin

The open form: you supply the message. The routes below are the named notices, which build their own.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `subject` _string_ **Required** — The subject line.
- `text` _string_ — The plain-text body.
- `html` _string_ — The HTML body.
- `from` _string_ — Defaults to the deployment's sender.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "subject": "Hello", "text": "Hello, Ada." }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the registration challenge mail

`POST /api/auth/email/:address/notification/registration/challenge`

Guard: admin

The mail that carries the link or code a new account is proven with. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `verification` _string_ — The token to embed in the link.
- `displayName` _string_ — Who to greet.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/registration/challenge \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the registration success mail

`POST /api/auth/email/:address/notification/registration/success`

Guard: admin

The welcome mail, sent once an account exists. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `displayName` _string_ — Who to greet.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/registration/success \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "displayName": "Ada Lovelace" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the sign-in challenge mail

`POST /api/auth/email/:address/notification/signin/challenge`

Guard: admin

The magic link or code that signs somebody in. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `verification` _string_ — The token to embed in the link.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/signin/challenge \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the verification challenge mail

`POST /api/auth/email/:address/notification/verification/challenge`

Guard: admin

Asks the holder of an address to prove it. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `verification` _string_ — The token to embed in the link.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/verification/challenge \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the address verified mail

`POST /api/auth/email/:address/notification/verification/verified`

Guard: admin

Confirms an address has been proven. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/verification/verified \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{}'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the session created mail

`POST /api/auth/email/:address/notification/session/created`

Guard: admin

Tells the address a new session was opened — the "new sign-in" mail. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `jti` _string_ — The session that was created.
- `agent` _string_ — The browser and platform it was opened from.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/session/created \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "jti": "01J2788XY98FDTA410C8S716CE", "agent": "Chrome on macOS" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the session invalidated mail

`POST /api/auth/email/:address/notification/session/invalidated`

Guard: admin

Tells the address a session was ended. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `jti` _string_ — The session that was ended.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/session/invalidated \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "jti": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the suspension imposed mail

`POST /api/auth/email/:address/notification/suspension/imposed`

Guard: admin

Tells the address its account has been suspended. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `reason` _string_ — What to tell them.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/suspension/imposed \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Unusual activity" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the suspension lifted mail

`POST /api/auth/email/:address/notification/suspension/lifted`

Guard: admin

Tells the address its account is usable again. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `reason` _string_ — What to tell them.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/suspension/lifted \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Review complete" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the termination warning mail

`POST /api/auth/email/:address/notification/termination/warning`

Guard: admin

Warns that the account is scheduled for deletion. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `reason` _string_ — What to tell them.
- `scheduled` _number_ — When termination runs, as a Unix timestamp.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/termination/warning \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Inactive for 24 months", "scheduled": 1767830400 }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the termination executed mail

`POST /api/auth/email/:address/notification/termination/executed`

Guard: admin

Tells the address its account has been deleted. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.
- `reason` _string_ — What to tell them.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/termination/executed \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Requested by the account holder" }'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```

## Send the address unassigned mail

`POST /api/auth/email/:address/notification/unassigned`

Guard: admin

Tells an address it is no longer attached to an account — sent to the old address after a change, so a takeover is visible to the person who lost it. Takes `?preview=true` to render the message as HTML instead of sending it.

### Parameters

- `address` _string_ **Required** — Path parameter, URL-encoded.
- `preview` _boolean_ — Render and return the HTML instead of sending. Query parameter.

### Returns

The send result — or the rendered HTML, with `?preview=true`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/email/ada%40example.com/notification/unassigned \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{}'
```

### Response

```json
{
  "sent": true,
  "id": "01J2788XY98FDTA410C8S716CE"
}
```
