StackShift Mail
Templates and OTP
LiveCreate versioned templates, preview and test them, send from a template, and use the built-in one-time-code challenge flow.
Goal
Use reusable email templates and OTP challenges without storing plaintext OTP codes in your application.
Current status
Live
This area is documented as current, user-reliable behavior.
Workflow
- 1Create a template with name, slug, subject, html, and/or text.
- 2Preview with data before sending so missing variables are visible.
- 3Send by template slug or id and optionally pin versionId.
- 4Use OTP send and verify for challenge-based authentication flows.
- 5Inspect 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
Example
tsawait 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
Example
tsconst 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.