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

# Templates and OTP

> Create versioned templates, preview and test them, send from a template, and use the built-in one-time-code challenge flow.

<Tip>
  **Live.** This area is documented as current, user-reliable behavior.
</Tip>

## Goal

Use reusable email templates and OTP challenges without storing plaintext OTP codes in your application.

## Prerequisites

* A StackShift API key
* A verified sender domain for production sends

## Workflow

<Steps>
  <Step>
    Create a template with name, slug, subject, html, and/or text.
  </Step>

  <Step>
    Preview with data before sending so missing variables are visible.
  </Step>

  <Step>
    Send by template slug or id and optionally pin versionId.
  </Step>

  <Step>
    Use OTP send and verify for challenge-based authentication flows.
  </Step>

  <Step>
    Inspect OTP challenges by to, purpose, status, cursor, and limit.
  </Step>
</Steps>

## Template lifecycle

* Templates have active, archived, or deleted status.
* Every create or content update creates template version data with subject, html, text, variables, and versionNumber.
* A template can expose activeVersion and versions in detail responses.
* Preview renders subject, html, and text and returns missingVariables.
* Test send returns messageId and status.

## Create, preview, and send a template

```ts theme={null}
await stackshift.mail.templates.create({
  name: 'Welcome email',
  slug: 'welcome-email',
  subject: 'Welcome, {{firstName}}',
  html: '<h1>Welcome, {{firstName}}</h1>',
  text: 'Welcome, {{firstName}}',
})

const preview = await stackshift.mail.templates.preview('welcome-email', {
  data: { firstName: 'Ada' },
})

const sent = await stackshift.mail.sendTemplate({
  template: 'welcome-email',
  from: 'noreply@example.com',
  to: 'ada@example.net',
  data: { firstName: 'Ada' },
  idempotencyKey: 'welcome_user_123_v1',
})

console.log(preview.missingVariables, sent.id)
```

## OTP challenge flow

```ts theme={null}
const challenge = await stackshift.mail.otp.send({
  to: 'ada@example.net',
  from: 'security@example.com',
  purpose: 'login',
  expiresIn: '10m',
  codeLength: 6,
  brandName: 'Example App',
  idempotencyKey: 'login_ada_2026_05_10',
})

const result = await stackshift.mail.otp.verify({
  challengeId: challenge.id,
  code: '123456',
})

console.log(result.verified, result.status)
```

## OTP challenge states

* Challenge status is pending, verified, expired, failed, or canceled.
* Responses include attempts, maxAttempts, attemptsRemaining, expiresAt, resendAvailableAt, and optional messageId.
* Admin-style challenge APIs can list, fetch, and cancel challenges.

## Expected result

<Check>
  Template sends are rendered server-side, missing variables fail before queueing, and OTP challenges record status and attempt counts.
</Check>

## Common failures

<Warning>
  * Missing template variables in data.
  * Sending with a deleted or archived template.
  * Verifying an OTP after expiration, cancellation, too many attempts, or wrong challenge context.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Send email" href="/stackshift-mail/send-email">
    Send a single outbound email with the official SDK or REST API, then inspect the message, attempts, logs, and timeline.
  </Card>

  <Card title="Events, webhooks, and timelines" href="/stackshift-mail/events-webhooks-and-timelines">
    List mail events, inspect per-message timelines, subscribe webhooks, rotate secrets, retry deliveries, and verify webhook signatures.
  </Card>
</CardGroup>
