Tutorials

How to Send OTP with Bun (2026)

Bun OTP tutorial using StartMessaging. Uses Bun.serve, Bun.password.hash for credential hygiene, native fetch and zero npm install required.

StartMessaging Team Updated

Bun’s built-in HTTP server, native fetch and zero-config TS make OTP integrations a few-file affair. This tutorial uses StartMessaging.

Setup

bun init -y otp-bun
cd otp-bun
echo 'SM_API_KEY=sm_live_xxx' > .env

Bun.serve OTP App

// src/index.ts
const apiKey = Bun.env.SM_API_KEY!;

async function smSend(phoneNumber: string) {
  const r = await fetch('https://api.startmessaging.com/otp/send', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
    body: JSON.stringify({ phoneNumber, idempotencyKey: crypto.randomUUID() }),
  });
  if (!r.ok) throw new Error('send failed');
  return (await r.json()).data as { requestId: string; expiresAt: string };
}

async function smVerify(requestId: string, otpCode: string) {
  const r = await fetch('https://api.startmessaging.com/otp/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
    body: JSON.stringify({ requestId, otpCode }),
  });
  return r.ok;
}

Bun.serve({
  port: 3001,
  async fetch(req) {
    const url = new URL(req.url);
    if (req.method === 'POST' && url.pathname === '/auth/send-otp') {
      const { phoneNumber } = await req.json();
      const data = await smSend(phoneNumber);
      return new Response(JSON.stringify({ requestId: data.requestId, expiresAt: data.expiresAt }));
    }
    if (req.method === 'POST' && url.pathname === '/auth/verify-otp') {
      const { requestId, otpCode } = await req.json();
      const ok = await smVerify(requestId, otpCode);
      return new Response(JSON.stringify({ verified: ok }), { status: ok ? 200 : 401 });
    }
    return new Response('Not Found', { status: 404 });
  },
});

Bun has no built-in session helper, but signing a cookie with Bun.password.hash + a secret works. For production, prefer the iron-session npm module.

Tests

// src/index.test.ts
import { describe, it, expect, mock } from 'bun:test';

it('sends OTP', async () => {
  globalThis.fetch = mock(() => new Response(JSON.stringify({
    data: { requestId: 'req_x', expiresAt: 't', attemptsLeft: 3 }
  }), { status: 200 })) as any;
  const r = await fetch('http://localhost:3001/auth/send-otp', {
    method: 'POST',
    body: JSON.stringify({ phoneNumber: '+919876543210' }),
  });
  expect(r.ok).toBe(true);
});

FAQ

Pair with Hono for a richer framework on top of Bun.

S

StartMessaging Team

StartMessaging Team

Related posts

Tutorials

How to Send OTP with Hono (2026)

Hono OTP tutorial using StartMessaging. Targets Cloudflare Workers, Bun and Node. Uses zValidator, JSON schema, signed-cookie session and middleware-based rate limiting.

Read more →