Skip to main content

Builder guide

Free OTP for your SaaS:
login codes by WhatsApp, SMS or email, no per-code fee

Twilio Verify bills $0.05 for every successful code, plus the SMS. For a side project or an early SaaS, you can send the same codes from your own WhatsApp number, Android phone or mailbox through one free API call. Here is the code, the prompt for your AI coding tool, and the point where a paid verification service becomes worth it.

In a hurry? Summarize this article with an AI

The short answer

Can you send OTP codes for free?

Yes. Your backend generates the code, stores a hash, and sends it with one POST /message call to Unlimited Messaging, which delivers it from your own WhatsApp number, your Android phone's SIM or your mailbox. The free plan has no per-code fee and no message cap. The catch: a short footer on each message, and a human sending pace that does not fit huge bursts.

The usual route is a verification API. Twilio Verify charges $0.05 per successful verification plus the channel fee: $0.0083 per SMS in the US, so about $58 for 1,000 logins. Building it yourself on a raw SMS API is cheaper per message ($0.0083 plus a carrier fee in the US) but adds A2P 10DLC registration before the first text.

For an indie SaaS, the code logic is the easy part: about 30 lines for send and verify, shown below. What costs money is the delivery. Sending from accounts you already own removes that line: WhatsApp from your number (no Meta authentication template), SMS from your phone's plan, email from Gmail or your domain's mailbox.

This page is the builder how-to. If you want the case for WhatsApp over SMS (delivery rates, SIM swap, SMS pumping), read the WhatsApp OTP article linked below. Prices were read on the vendors' pages on November 9, 2026.

Related: why WhatsApp OTP beats SMS codes, a free SMS API from your own phone and sending from Gmail with an app password.

Illustration: a six-digit verification code under a padlock, with the headline “Login codes, no per-code fee”.

The bill

How much does an OTP cost with the usual APIs?

Per code and for 1,000 successful logins a month, US numbers. Verify bills only successful checks; raw SMS bills every send.

How much does an OTP cost with the usual APIs?
Cost per code1,000 codes a monthWhat you set up
Twilio Verify, SMS$0.05 + $0.0083 per SMSAbout $58Twilio account, Verify service; fraud protection included
Twilio Verify, WhatsApp$0.05 + Meta's authentication feeFrom about $53WhatsApp sender approved by Meta
Twilio SMS API, your own code$0.0083 + carrier fee ($0.0035 to $0.005)About $12 to $13A2P 10DLC brand and campaign registration
Resend, email codes$0 on the free tier$0 while under 100 emails a dayDNS records on your domain
Unlimited Messaging Free€0 on WhatsApp, SMS and email€0Link your number, phone or mailbox; codes carry a short footer
Unlimited Messaging MediumFlat €19 a month, no per-code fee€19Same setup, footer removed

Twilio Verify, SMS and WhatsApp prices from Twilio's pricing pages, Resend from resend.com/pricing, read on November 9, 2026. Meta's authentication fee depends on the recipient's country (Twilio lists utility and authentication from $0.0034). SMS sent through Unlimited Messaging count against your own carrier plan.

The recipe

How do you build OTP verification in four steps?

The same logic works for SMS, WhatsApp and email; only the channel in the request changes.

  1. 01

    Generate and store

    Create a 6-digit code with a cryptographic random generator, store only its hash with an expiry of about 5 minutes and an attempt counter.

  2. 02

    Send in one call

    POST /message with to: { phone } or { email } and the channel the user picked: WHATSAPP, SMS or EMAIL.

  3. 03

    Verify

    Hash what the user typed, compare in constant time, reject expired codes and lock after 5 wrong attempts. Delete the code once it is used.

  4. 04

    Rate limit

    One new code per minute per user and a few per hour per IP. It blocks SMS pumping and keeps your number from looking like a spammer.

digits, enough with a 5-attempt lock
6digits, enough with a 5-attempt lock
typical code lifetime
5 mintypical code lifetime
API call to send on any channel
1API call to send on any channel

The code

What does the send and verify code look like?

Two route handlers in TypeScript (Next.js App Router style; the same logic fits Express, Hono or a Supabase edge function). Replace db with your own storage.

POST /api/otp/sendTypeScript
// app/api/otp/send/route.ts
import { createHash, randomInt } from 'node:crypto';
import { db } from '@/lib/db'; // your own storage

export async function POST(req: Request) {
  const { phone } = await req.json();
  const code = String(randomInt(0, 1_000_000)).padStart(6, '0');

  await db.otp.save(phone, {
    hash: createHash('sha256').update(code).digest('hex'),
    expiresAt: Date.now() + 5 * 60_000,
    attempts: 0,
  });

  await fetch('https://api.unlimitedmessaging.app/message', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.UM_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to: { phone },
      channels: [{ channel: 'WHATSAPP' }],
      text: `${code} is your Acme verification code. It expires in 5 minutes.`,
    }),
  });

  return Response.json({ sent: true });
}
Generates the code, stores its hash for 5 minutes and sends it by WhatsApp. Use channel 'SMS' or to: { email } with 'EMAIL' for the other channels.
POST /api/otp/verifyTypeScript
// app/api/otp/verify/route.ts
import { createHash, timingSafeEqual } from 'node:crypto';
import { db } from '@/lib/db';

export async function POST(req: Request) {
  const { phone, code } = await req.json();
  const row = await db.otp.get(phone);

  if (!row || row.expiresAt < Date.now() || row.attempts >= 5) {
    return Response.json({ ok: false }, { status: 400 });
  }

  const hash = createHash('sha256').update(String(code)).digest('hex');
  if (!timingSafeEqual(Buffer.from(hash), Buffer.from(row.hash))) {
    await db.otp.incrementAttempts(phone);
    return Response.json({ ok: false }, { status: 401 });
  }

  await db.otp.delete(phone);
  return Response.json({ ok: true });
}
Checks expiry and attempts, compares hashes in constant time and deletes the code after success.
Prompt for Lovable, Bolt, v0, Cursor or Claude CodePrompt
Add phone and email verification codes (OTP) to this app with Unlimited Messaging. Read UM_API_KEY from the server environment only. Create two server routes. 1) POST /api/otp/send: takes { phone } or { email } and channel ('WHATSAPP', 'SMS' or 'EMAIL'); generate a 6-digit code with crypto.randomInt, store a SHA-256 hash with a 5-minute expiry and an attempts counter, allow one new code per minute per user; send it with POST https://api.unlimitedmessaging.app/message, header Authorization: Bearer UM_API_KEY, body { to: { phone } or { email }, channels: [{ channel }], subject (email only), text: '<code> is your verification code. It expires in 5 minutes.' }. 2) POST /api/otp/verify: compare hashes with crypto.timingSafeEqual, reject expired codes, lock after 5 wrong attempts, delete the code on success. Add a simple UI: choose WhatsApp, SMS or email, enter the destination, then a 6-digit input. Reference: https://unlimitedmessaging.app/llms-full.txt
Paste it into your AI coding tool. It needs an Unlimited Messaging API key stored as a server-side secret, and a linked WhatsApp number, Android phone or mailbox.

The channel

WhatsApp, SMS or email: which channel should carry the code?

Let the user choose when you can. Each channel has a different reach and a different limit.

WhatsApp, SMS or email: which channel should carry the code?
WhatsAppSMSEmail
User needsA WhatsApp account on that numberAny mobile numberAn inbox they can open
Sent fromYour linked WhatsApp numberYour Android phone's SIMYour Gmail or domain mailbox
Cost with Unlimited MessagingNo per-message feeYour carrier planNo per-message fee
Pace limitHuman pace, spaced sends30 SMS per 30 minutes (Android)500 a day on Gmail, 2,000 on Workspace
Best forInternational users, phone loginUsers without WhatsApp, local numbersSignup verification, magic-code login

Fair play

When is a free OTP setup not enough?

Free delivery from your own accounts suits a side project or an early SaaS. These are the cases where a paid verification API earns its fee.

  1. Launch-day bursts

    Android asks for confirmation beyond 30 SMS in 30 minutes, and WhatsApp messages go out at a human pace. Hundreds of codes a minute belong on Twilio Verify or a similar service.

  2. A footer on the free plan

    On the free plan, every code ends with "Sent with UnlimitedMessaging.app". The Medium plan at €19 a month removes it.

  3. WhatsApp is unofficial

    Your number is linked as a device, so there is no Meta authentication template and no copy-code button. Abuse can get the number banned: rate limit every send.

  4. No built-in fraud shield

    Verify services detect SMS pumping and bot signups for you. With your own accounts, rate limits and a CAPTCHA on the send route are your job.

  5. High-security 2FA

    NIST SP 800-63B classes OTP over the phone network as a restricted authenticator. For admin accounts, offer an authenticator app (TOTP) or passkeys, which need no message at all.

FAQ

Questions about free OTP APIs

Ilian, senior software engineer

Written by

· Senior Software Engineer

A senior software engineer, he founds and runs several tech companies. He builds products around messaging APIs, automation and AI integration, and shares here what he learns while building.

View LinkedIn profileUpdated

Ship login codes today, pay nothing per code.

Link your WhatsApp number, your Android phone or your mailbox, create an API key and send your first verification code for free.