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

# Email Authentication

> Email auth is a powerful feature to couple with [sub-organizations](/concepts/sub-organizations) for your users. This approach empowers your users to authenticate their Turnkey in a simple way (via email!), while minimizing your involvement: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to.

{/* This is an internal note from Traian: this is a leftover, not referenced anywhere, we can probably delete it */}

Our [Demo Embedded Wallet](https://wallet.tx.xyz) application serves an example of how email auth functionality might be integrated. We encourage you to try it (and check out the [code](https://github.com/tkhq/demo-embedded-wallet)) before diving into your own implementation.

## Prerequisites

Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email auth on behalf of suborgs. 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'"
}
```

## Helper packages

* We have released open-source code to create target encryption keys, decrypt auth credentials, and sign Turnkey activities. We've deployed this as a static HTML page hosted on `auth.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the auth credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
* We have also built a package to help you insert this iframe and interact with it in the context of email auth: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)

In the rest of this guide we'll assume you are using these helpers.

## Email Auth step-by-step

Here's a diagram summarizing the email auth flow step-by-step ([direct link](/assets/files/email_auth_steps-d4f7ca188fd3fb9a2de5557b4cf39c7b.png)):

<Frame>
  <img src="https://mintcdn.com/turnkey-0e7c1f5b-moeo-sync/xVLryuqvwBS6Tv6N/images/embedded-wallets/img/email_auth_steps.png?fit=max&auto=format&n=xVLryuqvwBS6Tv6N&q=85&s=42a72ed7624f7039d881a372d27ce72f" alt="email auth steps" width="6644" height="3208" data-path="images/embedded-wallets/img/email_auth_steps.png" />
</Frame>

Let's review these steps in detail:

<Steps>
  <Step>
    User on `yoursite.xyz` clicks "auth", and a new auth UI is shown. We recommend this auth UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to successfully authenticate. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:

    ```js
    const iframeStamper = new IframeStamper({
    iframeUrl: "https://auth.turnkey.com",
    // Configure how the iframe element is inserted on the page
    iframeContainerId: "your-container",
    iframeElementId: "turnkey-iframe",
    });

    // Inserts the iframe in the DOM. This creates the new encryption target key
    const publicKey = await iframeStamper.init();
    ```
  </Step>

  <Step>
    Your code receives the iframe public key and shows the auth form, and the user enters their email address.
  </Step>

  <Step>
    Your app can now create and sign a new `EMAIL_AUTH` activity with the user email and the iframe public key in the parameters. Optional arguments include a custom name for the API key, and a specific duration (denoted in seconds) for it. Note: you'll need to retrieve the sub-organization ID based on the user email.
  </Step>

  <Step>
    Email is received by the user.
  </Step>

  <Step>
    User copies and pastes their auth code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe. In order to enable persistent sessions, save the auth code in local storage:

    ```js
    window.localStorage.setItem("BUNDLE", bundle);
    ```

    See [Email Customization](#email-customization) below to use a magic link instead of a one time code.
  </Step>

  <Step>
    Your app injects the auth code into the iframe for decryption:

    ```js
    await iframeStamper.injectCredentialBundle(code);
    ```
  </Step>

  <Step>
    At this point, the user is authenticated!
  </Step>

  <Step>
    Your app should use [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new activity, e.g. `CREATE_WALLET`:

    ```js
       // New client instantiated with our iframe stamper
       const client = new TurnkeyClient(
       { baseUrl: "https://api.turnkey.com" },
       iframeStamper,
       );

       // Sign and submits the CREATE_WALLET activity
       const response = await client.createWallet({
       type: "ACTIVITY_TYPE_CREATE_WALLET",
       timestampMs: String(Date.now()),
       organizationId: authResponse.organizationId,
       parameters: {
          walletName: "Default Wallet",
          accounts: [
             {
             curve: "CURVE_SECP256K1",
             pathFormat: "PATH_FORMAT_BIP32",
             path: "m/44'/60'/0'/0/0",
             addressFormat: "ADDRESS_FORMAT_ETHEREUM",
             },
          ],
       },
       });
    ```
  </Step>

  <Step>
    User navigates to a new tab.
  </Step>

  <Step>
    Because the code was also saved in local storage (step 6), it can be injected into the iframe across different tabs, resulting in a persistent session. See our [Demo Embedded Wallet](https://wallet.tx.xyz) for a [sample implementation](https://github.com/tkhq/demo-embedded-wallet/blob/942ccc97de7f9289892b1714b10f3a21afec71b3/src/providers/auth-provider.tsx#L150-L171), specifically dealing with sharing the iframeStamper across components.

    ```js
    const code = window.localStorage.getItem("BUNDLE");
    await iframeStamper.injectCredentialBundle(code);
    ```
  </Step>

  <Step>
    Again, the user is authenticated, and able to initiate activities!
  </Step>

  <Step>
    Just like step 8, the iframeStamper can be used to sign another activity.

    ```js
    const client = new TurnkeyClient(
    { baseUrl: "https://api.turnkey.com" },
    iframeStamper,
    );

    // Sign and submits a SIGN_TRANSACTION activity
    const response = await client.signTransaction({
    type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
    timestampMs: String(Date.now()),
    organizationId: authResponse.organizationId,
    parameters: {
       signWith: "0x...",
       type: "TRANSACTION_TYPE_ETHEREUM",
       unsignedTransaction: "unsigned-tx",
    },
    });
    ```
  </Step>
</Steps>

Congrats! You've succcessfully implemented Email Auth!

## Integration notes

### 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>
    }
  },
});
```

### Bespoke email templates

We also support custom HTML email templates for [**Enterprise**](https://www.turnkey.com/pricing) clients. 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>
  <img src="https://mintcdn.com/turnkey-0e7c1f5b-moeo-sync/xVLryuqvwBS6Tv6N/images/embedded-wallets/img/email-auth-example-dynamic.png?fit=max&auto=format&n=xVLryuqvwBS6Tv6N&q=85&s=1c02091fa8447424d4e6fdebd90d2f8d" alt="dynamic email auth example" width="1422" height="536" data-path="images/embedded-wallets/img/email-auth-example-dynamic.png" />
</Frame>

<Warning>
  [**Bespoke HTML templates**](https://docs.turnkey.com/embedded-wallets/sub-organization-auth#bespoke-email-templates) and [**custom email sender domains**](https://docs.turnkey.com/embedded-wallets/sub-organization-auth#custom-email-sender-domain) are available to [**Enterprise clients**](https://www.turnkey.com/pricing) on the **Scale tier** or higher.
</Warning>

### Custom email sender domain

[Enterprise](https://www.turnkey.com/pricing) clients  can also customize the email sender domain. To get set up, please reach out to your Turnkey rep to get started but here is what you'll be able to configure:

```js
message InitOtpAuthIntent {
  // Optional custom email address from which to send the OTP email
  optional string send_from_email_address = "notifs@mail.domain.com";

  // Optional custom sender name (e.g. "MyApp Notifications")
  optional string send_from_email_sender_name = "MyApp Notifications";

  // Optional reply-to email address
  optional string reply_to_email_address = "reply@mail.domain.com";
}
```

Please keep in mind that:

* Email has to be from a pre-whitelisted domain
* If there is no `send_from_email_address` or it's invalid, the other two fields are ignored
* If `send_from_email_sender_name` is absent, it defaults to "Notifications" (again, ONLY if `send_from_email_address` is present and valid)
* If `reply_to_email_address` is absent, then there is no reply-to added. If it is present, it must ALSO be from a valid, whitelisted domain, but it doesn't have to be the same email address as the `send_from_email_address` one (though once again, this first one MUST be present, or the other two feature are ignored)

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

### Credential validity checks

By default, if a Turnkey request is signed with an expired credential, the API will return a 401 error. If you'd like to validate an injected credential, you can specifically use the `whoami` endpoint:

```js
const whoamiResponse = await client.getWhoami({
  organizationId,
});
```

A valid response indicates the credential is still live; otherwise, an error including `unable to authenticate: api key expired` will be thrown.
