Short answer: Split the code flow into sending, delivery, validation, security, and UX, then write cases per segment. Most production incidents land in validation and security, not in delivery.

“Can the user receive the code?” is the shallowest possible test plan. Here is the full chain.
1. Sending
- Phone formats: country codes, leading zeros, spaces and dashes, too long, too short.
- Number types: landlines, virtual ranges, disconnected lines — does the error say which?
- Rate limits per number, per IP, per device. See verification rate limits.
- Double-click on “Send code”: is the endpoint idempotent, or do two messages go out?
- Channel fallback to voice when SMS fails. See voice OTP.
2. Delivery
- Latency, and whether the timeout copy is honest about what happened.
- Sender ID and template filtering. See why carriers filter SMS.
- Cross-border routes, and whether low-traffic countries have a fallback.
- Autofill compatibility — the message format has to match the platform spec. See SMS OTP autofill.
3. Validation — where incidents come from
| Case | Expected |
|---|---|
| Expired code | Rejected with a clear reason |
| Already-used code | Single use only |
| Several codes requested | Only the newest works; older ones die immediately |
| Old code after number change | Rejected |
| Case, spaces, full-width digits | Normalised before compare |
| Too many wrong attempts | Locked, must re-request |
| Same code submitted concurrently | Succeeds exactly once |
“The old code still works” and “the same code works twice” are the two classic production holes.
4. Security
- Brute force: six digits is a million combinations. Without an attempt cap there is effectively no verification. See why OTP codes are six digits.
- Response oracles: body, status code, and timing must match for existing and non-existing numbers, otherwise accounts are enumerable.
- Authorisation: can a code sent to A’s phone reset B’s password?
- Replay: can the send endpoint be replayed to burn SMS budget? See OTP bombing.
- Log hygiene: codes must never reach logs or monitoring in plain text.
5. UX
- Resend before the countdown ends; countdown survival across refresh.
- Actionable error copy — “try again later” tells the user nothing.
- Pasting a whole SMS should extract the digits.
- Screen reader support and keyboard navigation on the code input.
Answering this as an interview question
Name the five segments, then give one bug you personally shipped or caught. For example: stale codes staying valid after a user tapped resend three times, fixed by keeping only the newest code per request.
Wrap-up
Cover expiry, reuse, concurrency, and attempt caps first. Those four words catch most real verification-code defects.