GetVerifyCode

OTP / SMS verification testing checklist

Vendor-neutral QA for engineers who ship SMS one-time codes. Use it in staging with real inbound SMS, when mocks are not enough.

Open checklist View source on GitHub
0 / 0 checks complete

Why this exists

SMS verification breaks in small ways: codes arrive late, expire early, get rate-limited, or fail on certain country formats. Most of these bugs only show up with real inbound SMS, not mocks.

Failure modeWhat users see
Slow deliveryCode never appears before they abandon
Early expiry“Invalid or expired code” after a resend
Rate limitsTemporary blocks after spam-clicking resend
Format mismatchCountry picker and dialing code disagree
Log leaksOTP strings land in analytics or error trackers

How to use it

  1. Work through the checklist on a staging build that can send real SMS.
  2. Record p50 / p95 delivery time for at least one country.
  3. Fail the release if any security or privacy item is unchecked.
  4. Prefer passkeys or authenticator apps for high-risk actions once the SMS path is proven.

Checklist

Input and formatting

Delivery

Validation

Security and privacy

Suggested test matrix

Run at least one happy path and one failure path per row before release.

ScenarioAssert
Domestic long numberCode arrives within SLO; E.164 paste works
International numberCountry picker and dial code stay aligned
Immediate resendCooldown shows; previous code is rejected
Expired codeClear expired/invalid message; new code works
Provider rejectReadable error; no silent hang
Log / analytics spot checkOTP string never appears in events or traces

Testing with real inbound SMS

Temporary inbound numbers help you exercise live carrier routes across countries without tying QA to personal SIMs.

See the QA checklist write-up and the SMS verification API guide. Only test apps you own or are authorized to test.

Pass criteria

Treat the release as not ready if any security or privacy box is unchecked, if p95 delivery exceeds your product SLO, or if resend does not invalidate the previous code.