> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Email OTP sign-in

> auth.email.sendOtp and auth.email.verifyOtp — mint a Dynamic user JWT with a one-time email code

Methods on the `email` flow of a [createAuthClient](/node/reference/auth/create-auth-client) client. `sendOtp` emails the user a one-time code; `verifyOtp` exchanges it for a Dynamic user JWT.

## sendOtp

### Function Signature

```typescript theme={"system"}
auth.email.sendOtp(email: string): Promise<{ verificationUUID: string }>
```

### Parameters

* **`email`** (`string`) - The user's email address. Dynamic sends the one-time code to it.

### Returns

* **`verificationUUID`** (`string`) - Identifies this verification attempt. Pass it to `verifyOtp` together with the code.

## verifyOtp

### Function Signature

```typescript theme={"system"}
auth.email.verifyOtp(params: {
  verificationUUID: string;
  code: string;
  sessionPublicKey?: string;
}): Promise<AuthResult>
```

### Parameters

* **`verificationUUID`** (`string`) - The value returned by `sendOtp`.
* **`code`** (`string`) - The one-time code the user received by email. Collect it through your own channel; never log or persist it.
* **`sessionPublicKey`** (`string`, optional) - Compressed P-256 public key hex from [generateSessionKeyPair](/node/reference/auth/generate-session-key-pair). Binds the session key into the minted JWT — required if you will use a session signer with [authenticateJwt](/node/reference/auth/authenticate-jwt) for `backUpToDynamic` flows.

### Returns

* **`Promise<AuthResult>`** - `{ jwt, expiresAt, userId }`. Pass `jwt` to `authenticateJwt`; never log it.

## Example

```typescript theme={"system"}
import { createAuthClient, generateSessionKeyPair } from '@dynamic-labs-wallet/node';

const auth = createAuthClient({ environmentId: 'your-environment-id' });
const { publicKeyHex, privateKeyJwk } = await generateSessionKeyPair();

const { verificationUUID } = await auth.email.sendOtp('user@example.com');

// The user receives the code by email; collect it through your own channel.
const { jwt, expiresAt } = await auth.email.verifyOtp({
  verificationUUID,
  code: otpCodeFromUser,
  sessionPublicKey: publicKeyHex,
});
```

## Error Handling

```typescript theme={"system"}
try {
  const result = await auth.email.verifyOtp({ verificationUUID, code, sessionPublicKey });
} catch (error) {
  // - Wrong or expired code (401): "email OTP sign-in failed: the code or
  //   signature is invalid or expired"
  // - MFA-required account: "account requires MFA, which the headless auth
  //   client does not support"
  // - Cookie-auth environment: sign-in returned no JWT in the response body
  console.error('Sign-in failed:', error);
}
```

`sendOtp` and `verifyOtp` are rate limited per IP address (429 when exceeded) — see [Rate Limit Policies](/overview/rate-limits).

## Related Functions

* [`createAuthClient()`](/node/reference/auth/create-auth-client) - Create the auth client
* [Wallet sign-in](/node/reference/auth/wallet-sign-in) - Private-key-based alternative
* [`authenticateJwt()`](/node/reference/auth/authenticate-jwt) - Use the minted JWT
