> ## Documentation Index
> Fetch the complete documentation index at: https://turnkey-0e7c1f5b-onramp-quick-fix.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Email Auth & Recovery

> Email Authentication enables users to authenticate and recover their Turnkey accounts using email-based verification. There are two methods of email authentication:

#### One-Time Password

* Uses a 6-9 digit or bech32 alphanumeric one-time password sent via email
* Simple, and familiar user experience

#### One-Time Password Sandbox Environment

To test OTP codes in our sandbox environment you can use the following:

* `alphanumeric` must be set to `false`
* `otpLength` must be set to `6`
* Email: [user@example.com](mailto:user@example.com)
* OTP Code: `000000`

**Credential Bundle**

* Sends an encrypted API key credential directly via email
* Alternative method for specific use cases
* More secure, but requires copying the full credential to the client

Both methods provide users with an expiring API key for authentication or account recovery.

## Core Mechanism

Email Authentication is built with expiring API keys as the foundation. The two delivery mechanisms are:

### OTP-based Method

The authentication process happens in two steps:

<Steps>
  <Step>
    A 6-9 digit or alphanumeric OTP code is sent to the user's verified email
    address
  </Step>

  <Step>
    Upon verification of the correct code, an API key credential is generated
    and encrypted for the client
  </Step>
</Steps>

### Credential Bundle Method

**Note:** on web, this method is only supported with the legacy iframe-based flow, if no hard requirement to use encrypted bundles, we suggest to use the IndexedDB-based OTP flow instead.

The API key credential is encrypted and delivered directly through email to the user.
Once the credential is live on the client side (within the context of an iframe), it is readily available to stamp (authenticate) requests. See the [enclave to end-user secure channel](/security/enclave-secure-channels) for more info on how we achieve secure delivery.

## Prerequisites

Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email auth and create suborganizations. Check out our [Quickstart guide](/getting-started/quickstart) if you need help getting started. To allow an API user to initiate email auth, you'll need the following policy in your main organization:

```json
{
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<YOUR_API_USER_ID>')",
  "condition": "(activity.resource == 'AUTH' && activity.action == 'CREATE') || (activity.resource == 'ORGANIZATION' && activity.action == 'CREATE')"
}
```

For OTP Auth signup and login flows you will need a user with the following policy

```json
{
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<YOUR_API_USER_ID>')",
  "condition": "(activity.resource == 'AUTH' && activity.action == 'CREATE') || (activity.resource == 'OTP' && activity.action == 'CREATE') || (activity.resource == 'OTP' && activity.action == 'VERIFY') || (activity.resource == 'ORGANIZATION' && activity.action == 'CREATE')"
}
```

## User Experience

### OTP-based Authentication Flow

The flow begins with a new activity of type `ACTIVITY_TYPE_INIT_OTP` using the parent organization id with these parameters:

* `otpType`: specify `"OTP_TYPE_EMAIL"`
* `contact`: user's email address
* `emailCustomization`: optional parameters for customizing emails
* `userIdentifier`: optional parameter for rate limiting SMS OTP requests per user.
  We recommend generating this server-side based on the user's IP address or public key.
  See the [OTP Rate Limits](#otp-rate-limits) section below for more details.
* `alphanumeric`: optional parameter for making this code bech32 alphanumeric or not. default: true
* `otpLength`: optional parameter for selecting the length of the OTP. default: 9
* `expirationSeconds`: optional validity window (defaults to 5 minutes)
* `sendFromEmailAddress` : optional custom email address from which to send the OTP email
* `sendFromEmailSenderName` : optional custom sender name for use with sendFromEmailAddress; if left empty, will default to ‘Notifications’
* `replyToEmailAddress` : optional custom email address to use as reply-to

After receiving the OTP, users complete OTP verification with `ACTIVITY_TYPE_VERIFY_OTP` using the parent organization id which returns a verificationToken JWT:

* `otpId`: ID from the init activity
* `otpCode`: the 6-9 digit or alphanumeric code received via email
* `expirationSeconds`: optional validity window (defaults to 1 hour)

After receiving the verification token, users complete OTP authentication flow with with `ACTIVITY_TYPE_OTP_LOGIN` using the sub orgazanition ID associated with the contact from the first step:

* `publicKey`: public key to add to organization data associated with the signing key in IndexedDB or SecureStorage.
* `verificationToken`: JWT returned from successfull `VERIFY_OTP` activity
* `expirationSeconds`: optional validity window (defaults to 15 minutes)
* `invalidateExisting`: optional boolean to invalidate previous login sessions

<Frame>
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/turnkey-0e7c1f5b-onramp-quick-fix/images/authentication/img/auth_otp_email.png" alt="auth otp email" />
</Frame>

### OTP Rate Limits

In order to safeguard users, Turnkey enforces rate limits for OTP auth activities. If a `userIdentifier` parameter is provided, the following limits are enforced:

* 3 requests per 3 minutes per unique `userIdentifier`
* 3 retries max per code, after which point that code will be locked
* 3 active codes per user, each with a 5 minute TTL

### Credential Bundle Authentication Flow

This alternative method uses `ACTIVITY_TYPE_EMAIL_AUTH` with these parameters:

* `email`: user's email address (must match their registered email)
* `targetPublicKey`: public key for credential encryption
* `apiKeyName`: optional name (defaults to `Email Auth - <Timestamp>`)
* `expirationSeconds`: optional validity window (defaults to 15 minutes)
* `emailCustomization`: optional parameters for customizing emails
* `invalidateExisting`: optional boolean to invalidate previous Email Auth API keys

<Frame>
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/turnkey-0e7c1f5b-onramp-quick-fix/images/authentication/img/auth_email.png" alt="auth email" />
</Frame>

### Recovery Flow

**Note:** this is legacy flow, if starting fresh please use the above email auth methods also for recovery purposes

For account recovery scenarios, users can initiate a recovery-specific flow using the `ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY` activity type, which requires:

* `email`: the email of the user needing recovery (must match their registered email)
* `targetPublicKey`: the public key for recovery credential encryption

The recovery process consists of two phases:

1. **Initiation**: Generates a temporary recovery credential and sends it via email
2. **Finalization**: User decrypts the recovery credential and uses it to sign an `ACTIVITY_TYPE_RECOVER_USER` activity, which can add new authenticators to regain account access

<Frame>
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/turnkey-0e7c1f5b-onramp-quick-fix/images/authentication/img/auth_email.png" alt="auth email" />
</Frame>

## Email Customization

We offer customization for the following:

* `appName`: the name of the application. This will be used in the email's subject, e.g. `Sign in to ${appName}`
* `logoUrl`: a link to a PNG with a max width of 340px and max height of 124px
* `magicLinkTemplate`: a template for the URL to be used in the magic link button, e.g. `https://dapp.xyz/%s`. The auth bundle will be interpolated into the `%s`

```js
// Sign and submits the EMAIL_AUTH activity
const response = await client.emailAuth({
  type: "ACTIVITY_TYPE_EMAIL_AUTH",
  timestampMs: String(Date.now()),
  organizationId: <sub-organization-id>,
  parameters: {
    email: <user-email>,
    targetPublicKey: <iframe-public-key>,
    apiKeyName: <optional-api-key-name>,
    expirationSeconds: <optional-api-key-expiration-in-seconds>,
    emailCustomization: {
      appName: <optional-your-app-name>,
      logoUrl: <optional-your-logo-png>,
      magicLinkTemplate: <optional-magic-link>
    }
  },
});
```

### Email Templates

We also support custom HTML email templates for [Enterprise](https://www.turnkey.com/pricing) clients on the **Scale** tier. This allows you to inject arbitrary data from a JSON string containing key-value pairs. In this case, the `emailCustomization` variable may look like:

```js
...
emailCustomization: {
  templateId: <HTML-template-stored-in-turnkey>,
  templateVariables: "{\"username\": \"alice and bob\"}"
}
...
```

In this specific example, the value `alice and bob` can be interpolated into the email template using the key `username`. The use of such template variables is purely optional.

Here's an example of a custom HTML email containing an email auth bundle:

<Frame>
  ![dynamic email auth
  example](https://mintlify.s3.us-west-1.amazonaws.com/turnkey-0e7c1f5b-onramp-quick-fix/images/embedded-wallets/img/email-auth-example-dynamic.png)
</Frame>

If you are interested in implementing bespoke, fully-customized email templates, please reach out to [hello@turnkey.com](mailto:hello@turnkey.com).

## Authorization

Authorization is managed through our [policy engine](/concepts/policies/overview):

### Authentication

Both OTP-based and credential bundle authentication activities:

* Can be performed by [root users](/concepts/overview#users) and users with proper policy authorization
* Require the respective feature to be enabled in the organization and sub-organization
* Can target any user in the organization or sub-organizations

Specifically:

* For OTP-based auth: `ACTIVITY_TYPE_INIT_OTP`, `ACTIVITY_TYPE_VERIFY_OTP` and `ACTIVITY_TYPE_OTP_LOGIN`
* For credential bundle auth: `ACTIVITY_TYPE_EMAIL_AUTH`

### Recovery

* `ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY`:

  * Initiates the recovery process
  * Requires proper authorization via policies
  * Can target any user in the organization or sub-organizations

* `ACTIVITY_TYPE_RECOVER_USER`:

  * Must be signed by the recovery credential received via email
  * Users can add credentials to their own user when authenticated
  * Recovery credentials expire after 15 minutes
  * Only the most recent recovery credential remains valid
  * Users can add new authenticators to regain account access when authenticated with a recovery credential

<Frame>
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/turnkey-0e7c1f5b-onramp-quick-fix/images/authentication/img/diagrams/email_auth_authorization.png" alt="email auth authorization" />
</Frame>

## Implementation in Sub-organizations

Both authentication methods and recovery work seamlessly with [sub-organizations](/concepts/sub-organizations).

### Example Implementations

* [OTP Auth Example](https://github.com/tkhq/sdk/tree/main/examples/otp-auth)
* [Email Auth Example](https://github.com/tkhq/sdk/tree/main/examples/email-auth)
* [Email Recovery Example](https://github.com/tkhq/sdk/tree/main/examples/email-recovery)
* [Demo Embedded Wallet](https://wallet.tx.xyz) ([code](https://github.com/tkhq/demo-embedded-wallet))

For implementation details:

<CardGroup>
  <Card title="Sub-Organization Email Auth Guide" href="/embedded-wallets/sub-organization-auth" icon="file-lines" iconType="solid" horizontal />

  <Card title="Sub-Organization Recovery Guide" href="/embedded-wallets/sub-organization-recovery" icon="file-lines" iconType="solid" horizontal />
</CardGroup>

## Implementation in Organizations

For organizations accessed via dashboard:

1. Ensure the required features are enabled:

   * `FEATURE_NAME_OTP_EMAIL_AUTH` for OTP-based authentication
   * `FEATURE_NAME_EMAIL_AUTH` for credential bundle authentication
   * Recovery features if needed

2. Users initiating the request must have appropriate permissions

## Opting Out

Organizations can disable email-based features if their security model requires it:

Use `ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE` to disable:

* `FEATURE_NAME_OTP_EMAIL_AUTH` for OTP-based authentication
* `FEATURE_NAME_EMAIL_AUTH` for credential bundle authentication
* `FEATURE_NAME_EMAIL_RECOVERY` for recovery

When creating sub-organizations, use:

* `disableOtpEmailAuth` parameter for OTP-based authentication
* `disableEmailAuth` parameter for credential bundle authentication
* `disableEmailRecovery` parameter for recovery

## Implementation Notes[​](#implementation-notes "Direct link to Implementation Notes")

* Users are limited to:

  * 10 long-lived API keys
  * 10 expiring API keys (oldest are discarded when limit is reached)

### For Top-level Organizations

* Both authentication methods are disabled by default
* Must be enabled via `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE`

### For Sub-organizations

* Both authentication methods are enabled by default
* Can be disabled during creation using `CreateSubOrganizationIntentV7` activity parameters

Example of enabling OTP-based Email Auth:

```bash
turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization_feature --body '{
        "timestampMs": "'"$(date +%s)"'000",
        "type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
        "organizationId": "<YOUR-ORG-ID>",
        "parameters": {
                "name": "FEATURE_NAME_OTP_EMAIL_AUTH"
        }
}' --organization <YOUR-ORG-ID>
```

Example of enabling credential bundle Email Auth:

```bash
turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization_feature --body '{
        "timestampMs": "'"$(date +%s)"'000",
        "type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
        "organizationId": "<YOUR-ORG-ID>",
        "parameters": {
                "name": "FEATURE_NAME_EMAIL_AUTH"
        }
}' --organization <YOUR-ORG-ID>
```
