Recipe
One-time passcodes
iMessage OTPs land instantly, in the blue bubble experience users already trust. The recipe below: generate a 6-digit code, store it server-side, send via iMessage with an idempotency key, verify on submit.
Why iMessage OTPs?
- Sub-second delivery to iPhones (faster than SMS in most networks)
- Native autofill on iOS picks up "Your code is 123456" pattern
- Blue bubble = your brand, not "Short Code 22000"
- No carrier filtering, no A2P registration
Implementation
otp.js
import { BlueReplies } from 'blueapi';import crypto from 'node:crypto';const client = new BlueReplies({ apiKey: process.env.BLUEREPLIES_KEY });// In-memory for the example — use Redis with TTL in production.const codes = new Map();export async function sendOtp(phone) {// 1. Generate codeconst code = crypto.randomInt(100000, 999999).toString();// 2. Store with 5-minute expirycodes.set(phone, { code, expiresAt: Date.now() + 5 * 60_000 });// 3. Send via iMessage. Idempotency key prevents double-send on retry.await client.messages.send({to: phone,body: `Your BlueReplies code is ${code}. It expires in 5 minutes.`,idempotencyKey: `otp-${phone}-${Math.floor(Date.now() / 60_000)}`,});}export function verifyOtp(phone, submittedCode) {const entry = codes.get(phone);if (!entry) return false;if (entry.expiresAt < Date.now()) {codes.delete(phone);return false;}if (entry.code !== submittedCode) return false;codes.delete(phone);return true;}
Best practices
- Use idempotency keys bucketed by minute. Stops accidental double-sends if the user mashes "Resend code".
- Rate-limit per phone server-side. e.g. 5 OTPs per hour per phone.
- Use the iOS autofill pattern:
Your code is 123456— iOS auto-suggests it in the keypad. - 5-minute expiry max. Force a fresh code rather than long-lived ones.
- SMS fallback for non-iPhone users: set
allowSmsFallback: trueon the send. - Subscribe to
message.failedvia webhook so you can surface delivery problems to the user quickly.
For full account-takeover protection, also check whether the recipient is iMessage-capable before sending — see Phone numbers.