> ## Documentation Index
> Fetch the complete documentation index at: https://docs-consumer.voucher.allride.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Email OTP links

> Require visitors of a Redemption Link to verify their email address with a one-time code sent through your own SMTP server.

An email OTP link (`requireEmailOtp: true`, mode `email-otp`) asks each visitor for an email address and sends a 6-digit one-time code to it. After verifying the code, the visitor can redeem, and the platform finds or creates a [Redemption Entity](https://docs-consumer.voucher.allride.io/consumer/redemption-entities.md) for that address, so per-entity limits apply per person. This is the Email OTP Gate.

## Requirements

You can create an `email-otp` link only when all three hold:

1. the Email OTP Gate is turned on (`emailOtpLinksEnabled`);
2. your SMTP configuration is `verified`;
3. you have an `active` email template for the purpose `redemption-link-otp`.

Otherwise creating the link answers 422 [`email-otp-not-configured`](https://docs-consumer.voucher.allride.io/errors/catalog#email-otp-not-configured), and `more.missing` lists what is missing: `email-otp-disabled`, `smtp-not-verified` or `no-active-template`.

Check the state before you create links with [`GET /consumer/v1/email-otp-settings/readiness`](https://docs-consumer.voucher.allride.io/api-reference/consumer-email-otp-readiness-get). It needs only `manage-consumer-redemption-links`, so link-creating integrations can call it:

```bash theme={null}
curl "$VOUCHER_API_BASE_URL/consumer/v1/email-otp-settings/readiness" \
  -H "Authorization: Bearer $VOUCHER_API_KEY"
```

```json theme={null}
{
  "data": {
    "resource": "email-otp-readiness",
    "ready": false,
    "missing": ["smtp-not-verified"],
    "_links": {
      "self": { "href": "https://api.voucher.allride.io/consumer/v1/email-otp-settings/readiness", "type": "application/json" }
    }
  }
}
```

Every other operation on this page needs `manage-consumer-email`. It can be granted to API keys; dashboard users need a recent sign-in for the SMTP configuration and for switching the gate.

## Configure SMTP

OTP emails are sent through **your** SMTP server, from your address. Save the configuration with [`PUT /partner/v1/smtp-configuration`](https://docs-consumer.voucher.allride.io/api-reference/partner-smtp-configuration-replace):

```bash theme={null}
curl -X PUT "$VOUCHER_API_BASE_URL/partner/v1/smtp-configuration" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "host": "smtp.example-transit.example.com",
    "port": 587,
    "security": "starttls",
    "username": "voucher-mailer",
    "password": "use-your-smtp-password",
    "fromEmail": "no-reply@example.com",
    "fromName": "Example Transit vouchers",
    "replyTo": null
  }'
```

* Ports: **465** with `security: implicit-tls`, or **587** or **2525** with `starttls`. Port 25 is not allowed, and a port that does not match `security` fails with the constraint `security-port-mismatch`.
* The host must resolve to a public address. Private, loopback, link-local and cloud metadata addresses, and hosts that do not resolve, answer 422 [`smtp-host-not-allowed`](https://docs-consumer.voucher.allride.io/errors/catalog#smtp-host-not-allowed).
* The `password` is write-only: it is encrypted and never returned (`passwordSet` tells you one is stored). On later PUTs you can omit it to keep the stored one.
* The first PUT answers 201; later PUTs replace the configuration and answer 200.
* Emails leave from AllRide's egress IP addresses. If your SMTP server filters senders, allow them; see [Network and IP addresses](https://docs-consumer.voucher.allride.io/guides/network.md).

Every save sets the status to `unverified`. Then verify with [`POST /partner/v1/smtp-configuration:verify`](https://docs-consumer.voucher.allride.io/api-reference/partner-smtp-configuration-verify): the platform sends a test email in the background, to the requesting dashboard user or, for an API key, to your partner's technical contact (422 [`smtp-test-recipient-missing`](https://docs-consumer.voucher.allride.io/errors/catalog#smtp-test-recipient-missing) when none is set). Pass an `emailTemplateId` to receive that template rendered with sample data. Poll [`GET /partner/v1/smtp-configuration`](https://docs-consumer.voucher.allride.io/api-reference/partner-smtp-configuration-get) until `verificationCompletedAt` is at or after `verificationRequestedAt`, then read the result: `status: verified`, or `lastError` with the server's reply. Only one verification runs at a time (409 [`smtp-verification-in-progress`](https://docs-consumer.voucher.allride.io/errors/catalog#smtp-verification-in-progress)).

Saving new credentials does not interrupt live `email-otp` links: they keep sending through the new configuration while it is `unverified`, but you cannot create new `email-otp` links until it is verified again.

## Customise the email template

A default template is generated from your Workspace Branding when your partner is set up, so you can start with it. To use your own HTML:

1. Create a draft with [`POST /partner/v1/email-templates`](https://docs-consumer.voucher.allride.io/api-reference/partner-email-templates-create): upload `subject`, `htmlBody` (one HTML document per locale, at most 100 KB each, including your default locale) and optionally `textBody`, or send `generateFromBranding: true` to generate a fresh draft from your current branding.
2. Check `validation` in the response and render a preview with [`GET …/{emailTemplateId}/preview?locale=de`](https://docs-consumer.voucher.allride.io/api-reference/partner-email-templates-preview). The preview uses sample data (code `123456`, 10 minutes).
3. Edit drafts with [`PATCH`](https://docs-consumer.voucher.allride.io/api-reference/partner-email-templates-update) until `valid` is `true`.
4. Activate with [`POST …/{emailTemplateId}:activate`](https://docs-consumer.voucher.allride.io/api-reference/partner-email-templates-activate). The previous active template is archived in the same step, and the next OTP email uses the new one.

Templates use Handlebars variables:

| Variable | Required | Value |
| - | - | - |
| `{{otpCode}}` | Yes | The 6-digit code |
| `{{expiresInMinutes}}` | Yes | Minutes until the code expires (10) |
| `{{partnerName}}` | No | Your partner name |
| `{{logoUrl}}` | No | Your email logo (or logo) URL |
| `{{primaryColor}}` | No | Your primary colour |
| `{{supportEmail}}` | No | The support email of your landing page |
| `{{linkName}}` | No | The Redemption Link's `name` |
| `{{locale}}` | No | `de` or `en` |

Only the helpers `if`, `unless` and `each` are allowed. Triple braces, partials, decorators and dotted paths are refused with 422 [`template-invalid`](https://docs-consumer.voucher.allride.io/errors/catalog#template-invalid). Scripts and remote images from other hosts than AllRide's asset host are reported in `validation.htmlFindings` and block activation. A template is activated only when no required variable is missing, no unknown variable is used and it renders. [`GET /partner/v1/email-template-contracts/redemption-link-otp`](https://docs-consumer.voucher.allride.io/api-reference/partner-email-template-contracts-get) returns this contract for editors.

Active templates cannot be edited (409 [`email-template-not-editable`](https://docs-consumer.voucher.allride.io/errors/catalog#email-template-not-editable)): create a new draft and activate it. The active template cannot be archived while the gate is on (409 [`email-template-in-use`](https://docs-consumer.voucher.allride.io/errors/catalog#email-template-in-use)).

## Turn on email OTP links

When SMTP is verified and a template is active, turn the gate on with [`PATCH /consumer/v1/email-otp-settings`](https://docs-consumer.voucher.allride.io/api-reference/consumer-email-otp-settings-update):

```bash theme={null}
curl -X PATCH "$VOUCHER_API_BASE_URL/consumer/v1/email-otp-settings" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "emailOtpLinksEnabled": true }'
```

Then create links with `requireEmailOtp: true` and no entity (see [Redemption links](https://docs-consumer.voucher.allride.io/consumer/redemption-links.md#link-modes)). Set `maxRedemptions` (per verified person) and `maxTotalRedemptions` (for the whole link).

Turning the gate off is always allowed. Existing `email-otp` links stay `active`, but visitors can no longer request codes; visitors who already verified can redeem until their session ends. `activeEmailOtpLinkCount` in [`GET /consumer/v1/email-otp-settings`](https://docs-consumer.voucher.allride.io/api-reference/consumer-email-otp-settings-get) tells you how many links are affected.

## Session length

After a visitor verifies their email, they stay signed in on the landing page for **30 minutes** by default. Set your own length between 15 and 1440 minutes (24 hours) with `sessionDurationMinutes`, in the dashboard under **Email verification** or with the API:

```bash theme={null}
curl -X PATCH "$VOUCHER_API_BASE_URL/consumer/v1/email-otp-settings" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sessionDurationMinutes": 240 }'
```

The new length applies to visitors who verify after the change; visitors who already verified keep their end time. When the session ends, the page tells the visitor how long sessions last and asks them to verify again. Links without email verification (`anonymous` and `entity-bound`) do not use this setting: their visitors stay on the page as long as the link is valid. Values outside 15 to 1440 answer 400 [`data-failed-validation`](https://docs-consumer.voucher.allride.io/errors/catalog#data-failed-validation).

## What the visitor does

On the landing page the visitor enters an email address, receives the code, and enters it. Verification finds or creates the entity for that address (the [`redemption-entity-created`](https://docs-consumer.voucher.allride.io/webhooks/events/redemption-entity-created) webhook is sent for new ones, with the email); a second link for the same address reuses the same entity. A blocked entity cannot redeem.

## Limits

| Limit | Value | Visitor sees |
| - | - | - |
| Code validity | 10 minutes | Request a new code |
| Wrong codes per code | 5; the 5th locks the code | Request a new code |
| Code requests per email address and link | 5 per hour | "Try again later" |
| Code requests per IP address | 30 per hour | "Try again later" |
| Resend cooldown | 1 request per 60 seconds per visitor session | "Try again later" |
| Code requests per email address across all your links | 10 per 24 hours | No visible error; no further email is sent |
| Failed verifications per email address across all your links | 20 per 24 hours | No visible error; codes stop verifying until the window passes |
| Visitor session after verification | Your `sessionDurationMinutes` (default 30, 15 to 1440) | Verify again |

The last two limits are silent on purpose, so nobody can use your SMTP server to flood one address or learn that an address is being targeted. When you turned on bot protection for your landing page, every code request also passes a Cloudflare Turnstile check.

## Related

* [Redemption links](https://docs-consumer.voucher.allride.io/consumer/redemption-links.md)
* [Landing pages and custom domains](https://docs-consumer.voucher.allride.io/consumer/landing-pages.md)
* [Network and IP addresses](https://docs-consumer.voucher.allride.io/guides/network.md)
* [Get email OTP readiness](https://docs-consumer.voucher.allride.io/api-reference/consumer-email-otp-readiness-get.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.