Travelling? Get a Roamsim travel eSIM →
receive‑sms.app

For developers: testing SMS verification flows properly

How to test OTP and SMS verification in your own application: provider test credentials and magic numbers, staging setups, why public inbox numbers are wrong for production QA, and OTP design best practices.

By receive-sms.app · Based on how this directory collects and displays public SMS.

Phone verification looks easy to build and is easy to get wrong. A flow that sends a code, accepts it and moves on needs to cope with delays, retries, expiry, abuse and different number formats. Testing it well takes more than sending yourself a text. This guide covers the testing tools that messaging providers give you, how to set up staging, where public receive-SMS numbers help and where they do not, and the design choices around code length, expiry, attempt limits and rate limiting that make verification both usable and hard to abuse.

Start with your provider's test tools

Most SMS platforms offer a way to exercise your integration without sending real messages. Twilio, for example, provides test credentials, a separate Account SID and Auth Token, together with magic phone numbers that produce specific results: certain from numbers are treated as valid, while particular to numbers trigger errors such as an invalid number or a number that cannot receive messages. Test credentials do not deliver real messages, so they validate your request and error handling but not the end-to-end path. Check your provider's current documentation for the exact numbers and which API calls they cover.

From our team · Roamsim

Travelling? Skip the roaming bill.

Roamsim sells prepaid data eSIMs for 200+ destinations. Install before you fly and you're online when you land, without swapping SIM cards.

Browse eSIMs →

What test credentials cannot tell you

Because nothing is actually delivered, test credentials do not prove that a real phone would receive a message, that your sender ID is accepted in a given country, or that a carrier will not filter your template. They also do not exercise delivery receipts in the same way a live route does. Use them in automated tests and continuous integration, where repeatability and zero cost matter, and complement them with a small number of live tests on numbers you own before each release, in each country that matters to you.

Separate environments and configuration

Keep development, staging and production apart, each with its own credentials. In development and automated tests, put the SMS sender behind an interface and swap in a fake implementation that records the code instead of sending it. Expose that recorded code to your test suite, never to production users. In staging, use the real provider with a small allow list of numbers that belong to your team, so a bug cannot message customers. Never let a test configuration, such as a fixed code like 123456, reach production.

Use numbers you control for live tests

For real end-to-end checks, use SIMs, eSIMs or private virtual numbers that your team owns. They let you confirm real delivery, timing, sender display and message wording on actual handsets and carriers. Keep a few in different countries if you serve several. Because only your team can read the messages, a leaked or intercepted code costs nothing. Treat these numbers like any shared test resource: document who has them, rotate them if someone leaves, and do not register them to personal accounts.

Why public inbox numbers are wrong for production QA

Public receive-SMS numbers can be useful for a quick look at how your message appears or whether your flow sends anything at all on a throwaway staging account. They are wrong for testing production accounts or any flow with real data, since anyone can read the messages, including your codes, and anyone can reset what you register. They are also shared with other users, may already be registered, and are often blocked by services that screen VoIP ranges. Do not rely on them in automated tests, because availability is not guaranteed.

Code design: length and randomness

Generate codes with a cryptographically secure random number generator, not a general-purpose one, and never derive them from a user ID or a timestamp. Six digits is a common choice, giving a million possibilities; four digits is usable only with very strict attempt limits and short expiry, and eight digits gives extra margin at the expense of usability. Store only a hash of the code with the phone number, an expiry time and an attempt counter, and compare in constant time. Invalidate the code after one successful use.

Expiry and attempt limits

Short expiry limits the window in which a code can be guessed or intercepted. Several minutes is typical, with five to ten a common range; choose the lowest that suits your users' real delivery times. Limit attempts per code to a small number, such as three to five, and invalidate it afterwards, requiring a new request. Apply a lockout or growing delay after repeated failures for the same number and the same account. Return the same generic error whether a number is registered or not, so verification cannot be used to discover accounts.

Rate limiting and abuse protection

SMS costs money, and attackers abuse verification endpoints to run up your bill or flood someone else's phone. Rate limit requests per phone number, per account, per IP address and per device, and add a global ceiling with an alert. Use a short cooldown between sends, a daily cap per number and a CAPTCHA or other challenge when behaviour looks automated. Watch for traffic to premium-rate or unusual destinations and restrict countries you do not serve. Providers also offer fraud protection features worth enabling.

Handling formats, delays and failure

Normalise every number to E.164 before storing or sending, validate it with a maintained library, and test with numbers from several countries, including ones with leading zeros in the national format. Plan for delayed messages: do not invalidate a code on resend until the new one has been sent, and say clearly how long to wait. Offer a fallback such as a voice call, email or an authenticator app. Log delivery status and errors without recording the codes themselves, and test the failure paths as carefully as the happy path.

A test checklist

Cover these cases in automation: valid code, wrong code, expired code, reused code, too many attempts, too many requests, malformed number, unsupported country, resend after cooldown, simultaneous codes for one number, and a provider error. Add a manual live pass on real devices for message wording, sender name, delivery speed and links. Keep personal data out of test fixtures. Finally, consider whether SMS is the right channel at all, since an authenticator app or passkey offers stronger security for sensitive accounts.

Monitoring in production

After launch, watch the metrics that show whether verification works: the share of requests that lead to a successful verification, median time from request to entry, delivery failures by country and by route, and resend rates. A drop in conversion in one country often means a carrier started filtering your sender. Alert on sudden spikes in volume, which can signal abuse. Keep alerts free of codes and full phone numbers, and set retention limits on logs that contain numbers.