How to Send OTP with Astro (2026)
Astro OTP login tutorial using StartMessaging. Uses Astro Actions / API routes, server-only env, and a clean two-step phone verification flow.
StartMessaging Team
Engineering
Astro is increasingly used for marketing-led SaaS sites that need a thin auth layer. This tutorial adds OTP login with StartMessaging on top of Astro’s server output.
Project Setup
pnpm create astro@latest otp-astro
cd otp-astro && pnpm install
pnpm astro add node
# astro.config.mjs: output: 'server'Environment Variables
# .env
SM_API_KEY=sm_live_xxxxxxxxxxxxxxxxAPI Routes
// src/pages/api/send-otp.ts
import type { APIRoute } from 'astro';
import { randomUUID } from 'node:crypto';
export const POST: APIRoute = async ({ request, cookies }) => {
const { phoneNumber } = await request.json();
const res = await fetch('https://api.startmessaging.com/otp/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': import.meta.env.SM_API_KEY },
body: JSON.stringify({ phoneNumber, idempotencyKey: randomUUID() }),
});
if (!res.ok) return new Response(JSON.stringify({ error: 'send failed' }), { status: res.status });
const { data } = await res.json();
cookies.set('otp_req', data.requestId, { httpOnly: true, sameSite: 'lax', secure: true, maxAge: 900 });
return new Response(JSON.stringify({ expiresAt: data.expiresAt }));
};// src/pages/api/verify-otp.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request, cookies }) => {
const { otpCode } = await request.json();
const requestId = cookies.get('otp_req')?.value;
if (!requestId) return new Response(JSON.stringify({ error: 'no active OTP' }), { status: 400 });
const res = await fetch('https://api.startmessaging.com/otp/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': import.meta.env.SM_API_KEY },
body: JSON.stringify({ requestId, otpCode }),
});
if (!res.ok) return new Response(JSON.stringify({ error: 'verify failed' }), { status: res.status });
cookies.delete('otp_req');
cookies.set('session', 'verified', { httpOnly: true, sameSite: 'lax', secure: true, maxAge: 1800 });
return new Response(JSON.stringify({ verified: true }));
};Frontend Form
---
// src/pages/login.astro
---
<form id="phone-form">
<input name="phoneNumber" placeholder="+919876543210" />
<button>Send OTP</button>
</form>
<form id="otp-form" hidden>
<input name="otpCode" placeholder="482910" />
<button>Verify</button>
</form>
<script>
document.getElementById('phone-form')!.addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(e.target as HTMLFormElement);
await fetch('/api/send-otp', { method: 'POST', body: JSON.stringify(Object.fromEntries(fd)) });
document.getElementById('otp-form')!.hidden = false;
});
</script>Session Cookie
Astro’s cookies API on APIContext handles signed cookies natively. Configure secrets through your adapter settings.
FAQ
Need the same flow in Next.js or SvelteKit? Browse our tutorial library.
Related Articles
SvelteKit OTP tutorial using StartMessaging — uses form actions, server-only modules, hooks for session, and Zod for validation. Production-ready end-to-end flow.
Send and verify SMS OTPs from a Next.js 14/15 App Router app using server actions and the StartMessaging API. Includes a full login form, server actions, and middleware.
Remix OTP login tutorial using StartMessaging. Uses action functions, server-only utilities, signed cookies for session, and Zod for validation.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.