MailSlurp logo

mobile

Phone Verification API for Signup, Recovery, and MFA Testing

Test phone verification with an API-controlled number. Receive the SMS code, verify the account, and check number changes, retries, expiry, and recovery.

A phone verification test should prove that the right account becomes verified after entering a code delivered to its phone number. A successful SMS send request is only one part of that journey: the number may be wrong, the message may arrive late, or a code from an earlier attempt may still be accepted.

Copying codes from a shared handset makes those cases hard to repeat in CI. MailSlurp provides real phone numbers with APIs for receiving SMS, so your test can enter a controlled number in the application, retrieve the delivered code, and check the resulting account state. Your application or authentication provider still creates and validates the challenge; MailSlurp gives the test access to the receiving phone.

Use the same signup, recovery, or MFA action that customers use. Keep a number assigned exclusively to that test, wait for the expected message with a timeout, then submit its code and assert the application result. The SMS verification example implements the receive-and-submit part in TypeScript. The steps below cover the phone ownership and account lifecycle around it.

Phone verification API workflow

1. Create a test user and controlled number

Provision a number in the MailSlurp dashboard and check its country, number type, availability, and account access. Assign it to a test user in your own application. The displayed number goes into the signup form; the MailSlurp phone ID selects the messages your test will read.

For a single worker, store that assigned ID as MAILSLURP_PHONE_NUMBER_ID and the API key as MAILSLURP_API_KEY in your test environment. Install the client with npm install mailslurp-client, then retrieve the number before starting the application flow:

import { MailSlurp } from 'mailslurp-client';

const apiKey = process.env.MAILSLURP_API_KEY;
const phoneNumberId = process.env.MAILSLURP_PHONE_NUMBER_ID;
if (!apiKey || !phoneNumberId) {
  throw new Error('Set the API key and assigned phone number ID');
}

const mailslurp = new MailSlurp({ apiKey });
const phone = await mailslurp.phoneController.getPhoneNumber({ phoneNumberId });

// Enter phone.phoneNumber in your application.
// Use phone.id when waiting for its SMS.

Fetching a number does not reserve it. For parallel tests, acquire a phone-pool lease per active flow and release it in teardown. Keep the lease valid until all requests for that flow have finished; starting a retry on a number with an outstanding SMS request can mix the two attempts.

2. Collect and normalize numbers

Your application should store a consistent international representation, usually E.164, while allowing the input formats it promises to customers. Test the same controlled number as an international value and, where supported, as a local value with a country selector. Both should identify the same phone rather than create separate ownership records.

Include invalid characters, missing country codes, pasted spaces, a duplicate number on another test account, and a number from an unsupported market. Format validation can reject malformed input before sending; completing an SMS challenge checks access to the number. Neither a formatting regex nor an accepted send request proves ownership.

3. Send the verification challenge

Trigger the challenge through your application's normal UI or documented test interface. That exercises its authentication provider, SMS template, routing, and challenge storage together. A separate text sent directly from a test utility can check message transport, but it does not test your application's verification code.

Record the start time immediately before that trigger. Capture the application's challenge or request identifier when it exposes one, and keep it with the test user and assigned phone ID. These identifiers help distinguish a failed send, a late delivery, and a code rejected by the application.

4. Receive the SMS and validate the OTP

Wait within the assigned phone ID using the start time and a bounded timeout. Match the expected sender and message template, preserve leading zeros when extracting the code, and submit that exact string to the application. An unread flag alone is insufficient isolation: opening a message in the dashboard can change it.

After submission, check that the intended user's phone is verified and that the page or API exposes the expected authenticated state. For a phone change, also verify that the new number replaced the previous one. Finding six digits in an SMS is not a substitute for those assertions.

Use the SMS OTP API example for the wait and extraction code, or the Playwright SMS guide to connect it to browser actions. A timestamp excludes older messages, but a delayed earlier request can arrive after it; exclusive number use and request correlation still matter.

5. Enforce policy and retry controls

Build separate tests for wrong codes, expiry, resend, and cooldown behavior. Define the expected policy first: some applications invalidate an earlier code on resend, while others keep one challenge active until it expires. Assert your documented behavior rather than assuming every provider uses the same rule.

For expiry, receive a valid code, wait beyond the application's configured lifetime, then submit it and assert rejection. For a wrong-code case, submit a known different value and check that the account remains unverified. Exercise the allowed number of attempts and confirm that the next attempt produces the expected cooldown or lockout state.

Test a number change with two controlled phones. Request verification for the old number, change the account to the new number, then try the old challenge. Verify that it cannot authorize the new number. Complete the new challenge and inspect the stored phone association as well as the success message.

6. Record evidence and outcomes

When a test fails, retain the stage, test-user ID, phone ID, SMS ID if received, challenge identifier if available, request time, elapsed wait, and final account state. Keep OTPs, complete message bodies, API keys, and unredacted phone numbers out of shared CI logs. Store any content needed for debugging only in access-controlled artifacts with your normal retention policy.

Report failure where it occurred. A setup failure means no suitable number was available. A wait timeout means no matching message was found within the chosen conditions. A rejected code means delivery and extraction completed but the application refused it. Those outcomes need different investigations.

Verification reliability checklist

Before running the suite in parallel, confirm that each worker has exclusive number access, its test timeout covers the SMS wait and cleanup, and a failed setup cannot fall back to another worker's number. The sender should use the same configuration and template as the environment being tested.

After each test, release its lease and clean up the test user's verification state. Releasing a lease returns the number to the pool; it does not cancel the number rental. Avoid deleting shared inventory as part of routine teardown.

Phone verification API vs SMS verification API

Phone verification is the account-level task: establish access to a number and attach that verified number to the correct user. An SMS OTP is one way to complete it. Testing that flow involves both the received message and the application's challenge, expiry, retry, and ownership rules.

SMS receiving APIs supply the message for your test. Authentication APIs issue and validate challenges. Keep those roles explicit when designing a test so it exercises the real verification provider instead of bypassing it. The SMS API guide covers number access, waits, webhooks, and sending separately.

Common failure modes to test

Symptom Check
A test verifies the wrong account Confirm the number-to-user mapping and exclusive worker assignment.
No matching SMS arrives Inspect the application's send result, receiving number, sender filter, and timeout separately.
A valid-looking code is rejected Check expiry, leading zeros, challenge identity, and resend policy.
A changed number becomes verified with an old code Ensure the challenge is bound to the intended user and number.
A retry passes with a previous message Start a new attempt boundary and avoid reusing a number while an earlier send is outstanding.

For flows that recover through email, add the email challenge to the same user journey. MailSlurp's inbox APIs support email OTP, magic-link, and password-reset checks alongside the SMS step, so you can verify that the fallback restores the correct account without leaving its phone state inconsistent.

FAQ

Is phone verification the same as SMS verification?

SMS verification is a method for proving access to a phone number. A complete phone verification flow also controls which account owns the number and how challenges behave during retries, recovery, and number changes.

What is the difference between phone verification and phone validation?

Phone validation checks properties such as number format. Phone verification asks the user to complete a challenge delivered to that number. A valid format alone does not prove that the user can receive the challenge.

Should phone verification run only during signup?

Include the places your application uses it: signup, risky login, MFA enrollment, recovery, and number changes. Each can have different rules for the existing session and verified phone state.

Can I run phone verification tests in CI?

Yes. Give the test a controlled number, trigger the application challenge, receive its SMS through the API, and assert the final account state. Use separate assignments or phone-pool leases for parallel workers.

Can MailSlurp test phone verification and email fallback together?

Yes. Use a MailSlurp phone number and inbox for the same test account, then complete and assert each branch through the application's normal verification or recovery flow.