Skip to main content
Live. This area is documented as current, user-reliable behavior.

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

1
Create a template with name, slug, subject, html, and/or text.
2
Preview with data before sending so missing variables are visible.
3
Send by template slug or id and optionally pin versionId.
4
Use OTP send and verify for challenge-based authentication flows.
5
Inspect OTP challenges by to, purpose, status, cursor, and limit.

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

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

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

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

Common failures

  • 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.

Send email

Send a single outbound email with the official SDK or REST API, then inspect the message, attempts, logs, and timeline.

Events, webhooks, and timelines

List mail events, inspect per-message timelines, subscribe webhooks, rotate secrets, retry deliveries, and verify webhook signatures.