OTP Guide

Use the built-in Verify API to launch OTP, login, and fraud-check flows without building your own code-store layer first.

1. Create a Verify service

A Verify service defines the code length, expiry, retry budget, and SMS template once so every app flow stays consistent.

typescript
import MailSetu from 'mailsetu'

const client = new MailSetu(process.env.MAILSETU_API_KEY!)

const service = await client.verify.services.create({
  name: 'Login OTP',
  codeLength: 6,
  ttlSeconds: 300,
  maxAttempts: 5,
  template: 'Your {{serviceName}} code is {{code}}. It expires in {{ttlMinutes}} minutes.',
})

console.log(service.id)

2. Start a verification

Starting a verification creates a code, stores only a hash at rest, sends the SMS, and returns a verification ID that your app can track.

typescript
const verification = await client.verify.start({
  serviceId: 'vs_123',
  to: '+919876543210',
  idempotencyKey: 'otp:user_123:login:2026-05-09T14:30',
  metadata: {
    flow: 'login',
    userId: 'user_123',
  },
})

console.log(verification.id)

3. Check the code

Your frontend should collect the code from the user and send it back to your backend. Your backend then calls Verify check.

typescript
const result = await client.verify.check('verify_123', '482913')

if (result.approved) {
  // create session / mark phone verified / continue checkout
}

Operational safeguards

For production OTP systems: • Keep traffic transactional and use live sender configuration for real users • Pass an idempotency key from your app so retries do not create duplicate sends • Use sandbox keys in staging so QA can test flows without hitting carriers • Track verification status from the dashboard when debugging onboarding or checkout friction