
# Magic Link & Email OTP

Two ways to sign a user in without a password, both of which prove control of an inbox: a **link** they click, or a **code** they type. Neither stores a password, so neither has a password to leak or reset.

They are enabled through different mechanisms — the link is a strategy entry, the code is not. That asymmetry is the one thing to get right on this page.

## Magic Link

A strategy entry. The user submits their email, receives a one-time link, and is signed in by following it.

```yaml
auth:
  strategies:
    - type: magicLink
      expirationMinutes: 30
```

| Property            | Description                                                    |
| ------------------- | -------------------------------------------------------------- |
| `expirationMinutes` | Link lifetime in minutes (positive integer). Defaults to `15`. |

The default 15 minutes assumes the user is waiting on the mail. Stretch it when your audience checks email on a phone, later; shorten it when the link grants access to something sensitive, since a live link in an inbox is a live credential.

The message body is rendered from the `magicLink` [email template](/en/docs/auth-email-templates) when you supply one, with `$url` substituted for the link.

## Enabling Email-OTP

Email-OTP delivers a numeric one-time code instead of a link — better on a device where following a link would break the flow, and easier to read aloud over a phone.

**There is no `type: emailOtp` strategy entry.** The flow activates when you define the `emailOtp` email template; its presence is what mounts the plugin.

```yaml
auth:
  strategies:
    - type: emailAndPassword
  emailTemplates:
    emailOtp:
      subject: Your sign-in code
      text: 'Your verification code is $code. It expires shortly.'
```

`$code` is substituted with the generated one-time code. Note the strategy array above declares only `emailAndPassword` — email-OTP rides alongside whatever strategies you declared, it does not replace them.

:::callout
**Adding `type: emailOtp` to `strategies` is a validation error.** The union accepts `emailAndPassword`, `magicLink` and `oauth` only, so a config that guesses at an OTP strategy fails `sovrium validate` rather than booting without the flow.
:::

## Both Need SMTP

A passwordless flow that cannot send mail cannot sign anyone in. When SMTP is unset the app boots with email disabled and logs a warning — sign-in attempts then fail silently, with the user waiting on a message that was never sent.

Configure SMTP before shipping either flow, and prefer keeping `emailAndPassword` declared alongside them while you do: it gives you a working way in if mail delivery breaks. See [Environment Variables](/en/docs/env-vars) for the SMTP settings.

## Related Pages

- [Strategies Overview](/en/docs/auth-strategies) — the `strategies` array and how to choose.
- [Email Templates](/en/docs/auth-email-templates) — the `magicLink` and `emailOtp` bodies and their variables.
- [Email & Password](/en/docs/auth-email-password) — the credential strategy these sit beside.
- [Environment Variables](/en/docs/env-vars) — SMTP configuration and `BASE_URL`.
- [Sessions](/en/docs/auth-sessions) — the session a successful link or code issues.
