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 code
const code = crypto.randomInt(100000, 999999).toString();
// 2. Store with 5-minute expiry
codes.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: true on the send.
  • Subscribe to message.failed via 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.