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
Engineering
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' > .envBun.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 });
},
});Session via Cookie
Bun has no built-in session helper, but signing a cookie withBun.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.
Related Articles
Hono OTP tutorial using StartMessaging. Targets Cloudflare Workers, Bun and Node. Uses zValidator, JSON schema, signed-cookie session and middleware-based rate limiting.
Deno OTP tutorial using StartMessaging. Uses Deno.serve, native fetch, signed-cookie helpers and runs on Deno Deploy with zero infrastructure.
Step-by-step Node.js tutorial to send and verify OTP via SMS using the StartMessaging API. Includes fetch examples, error handling, and verification flow.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.