blog
SMS Verification API for OTP and MFA Testing
Test an SMS OTP API from request to verified login. Receive the real message, check its sender, extract the code, and test resend, expiry, and MFA recovery.
An SMS verification test starts when a user requests a code and ends when the application accepts it for the right account. Checking that a sending API returned success leaves the receiving half untested: the SMS may contain the wrong code, arrive after expiry, or reach a phone assigned to another user.
That gap is awkward to automate with a handset and manual copying. MailSlurp provides real phone numbers and message APIs, letting a test receive the text, extract its OTP, and submit it through the same signup or login form a customer uses. Your application or authentication provider generates and validates the code; MailSlurp supplies the receiving number and access to its messages.
Connect the sending and receiving sides
Use an account in your test environment and a controlled receiving phone. Trigger the application's normal SMS verification action so its provider integration, template, and challenge storage are exercised together. Sending an unrelated text directly to the number checks transport, but it bypasses the application behavior this test is meant to cover.
Provision a MailSlurp number and check current regional availability and account access in the dashboard. Enter the displayed number in your application; use its MailSlurp ID in API calls. Install the Node.js client with npm install mailslurp-client and provide MAILSLURP_API_KEY and MAILSLURP_PHONE_NUMBER_ID through your test environment.
Keep that number exclusive to the active test. For several workers, use phone-pool leases and acquire a separate number per flow. Reading a number from a list or fetching it by ID does not reserve it.
Receive an OTP and complete the login
Suppose your test application sends Your code: 004271. The test must preserve 004271 as a string, check the expected sender and template, enter it in the application, and verify the signed-in user. Replace the sample template with the exact text your application sends.
This helper takes three functions from your test suite. requestCode enters the phone number and awaits the SMS request, submitCode enters the delivered code, and assertSignedIn checks the intended account. They must reject when their respective action fails; they are application-specific functions, not MailSlurp SDK methods.
import { MailSlurp } from 'mailslurp-client';
export async function verifySmsLogin(options: {
mailslurp: MailSlurp;
phoneNumberId: string;
expectedSender: string;
requestCode: (number: string) => Promise<void>;
submitCode: (code: string) => Promise<void>;
assertSignedIn: () => Promise<void>;
}) {
const phone = await options.mailslurp.phoneController.getPhoneNumber({
phoneNumberId: options.phoneNumberId,
});
// This phone must be assigned exclusively to the active test.
const since = new Date();
await options.requestCode(phone.phoneNumber);
const sms = await options.mailslurp.waitController.waitForLatestSms({
waitForSingleSmsOptions: {
phoneNumberId: phone.id,
since,
timeout: 60_000,
unreadOnly: false,
},
});
if (sms.fromNumber !== options.expectedSender) {
throw new Error('SMS came from an unexpected sender');
}
const match = /^Your code: ([0-9]{6})$/.exec(sms.body);
if (!match) throw new Error('SMS did not match the OTP template');
await options.submitCode(match[1]);
await options.assertSignedIn();
return { phoneNumberId: phone.id, smsId: sms.id };
}
Use the sender representation your receiving route actually returns. A provider may use different approved senders by market; in that case, apply an explicit allowlist for that environment rather than accepting any sender. The helper deliberately fails on an unexpected message. It uses a single latest-message wait, so keep other traffic off the assigned number.
The since boundary excludes messages timestamped before the request. It cannot distinguish a delayed earlier request that arrives afterward. Finish outstanding sends before reusing a number and correlate the application's challenge identifier when its message or test interface exposes one. unreadOnly: false avoids changing test behavior simply because someone inspected the SMS in the dashboard.
Give the enclosing test enough time for setup, the 60-second message wait, submission, assertions, and cleanup. Keep the returned IDs for diagnostics, without logging the OTP or whole SMS. The Playwright SMS OTP guide shows how the receive step fits into browser automation.
What OTP and MFA teams actually need to test
Once one delivered code completes the journey, add independent cases for the challenge rules. Use a fresh test user or reset its verification state according to your application's supported test lifecycle so a previously verified account cannot skip the check.
| Case | Action | Assert in the application |
|---|---|---|
| Correct code | Receive and submit the current OTP. | The intended account becomes verified or signed in. |
| Wrong code | Submit a different value. | The account stays unverified and the error is shown. |
| Expired code | Receive a code, then submit after its configured lifetime. | The expired challenge is rejected. |
| Resend | Request another code and inspect both attempts. | Earlier and current codes follow the documented resend policy. |
| Attempt limit | Submit wrong codes until the configured limit. | The expected cooldown or lockout applies. |
| Number change | Request a code, then change the account's phone. | An old challenge cannot verify the replacement number. |
For MFA, begin with the first factor completed and verify that access remains blocked until the SMS challenge succeeds. If the product offers email or another recovery method, test that branch separately and check which factors remain enrolled afterward. Authentication testing covers combining email and SMS in one account journey.
Common reasons SMS auth tests become flaky
A timeout can mean the application never requested a send, the delivery route failed, or the wait conditions excluded the message. Inspect those stages separately. Start with the application's request outcome and the intended receiving number, then inspect the SMS record and filters. Repeatedly requesting new codes can make a delayed first message harder to distinguish from the current one.
When the message arrives but the code is rejected, check its challenge, expiry, leading zeros, and resend policy. When failures happen only in parallel runs, inspect number allocation and outstanding requests. Increasing a timeout will not repair two workers consuming the same message stream.
Record the failing stage, phone ID, SMS ID when available, start time, and elapsed wait. Keep message content and codes out of shared logs. The wait troubleshooting guide explains how to separate missing delivery from matching and timing problems.
SMS verification API vs phone verification API
SMS verification describes a challenge delivered by text, commonly an OTP. Phone verification is the broader account operation: proving access to a number and attaching it to the right user. It also includes the application's behavior when a number is changed, reused, or already linked to another account.
For a receive-and-submit test, start with the helper above. For account ownership and number lifecycle assertions, use the phone verification guide. The general SMS API guide covers number access, inbound processing, webhooks, and approved outbound messages.
FAQ
What is an SMS verification API?
It supports a challenge sent by SMS, usually a one-time code. For an end-to-end test, connect the application's challenge-issuing and validating service to an API-controlled receiving number so the delivered message is part of the assertion.
Can I use an SMS verification API for OTP testing?
Yes. Trigger the application's challenge, retrieve the SMS from the assigned MailSlurp number, extract the code without converting it to a number, then submit it and verify the application result.
What is the difference between SMS verification and MFA testing?
SMS verification tests one challenge channel. MFA testing checks the complete authentication journey, including the first factor, required second factor, failed challenges, and recovery behavior.
Why do SMS auth tests fail so often in CI?
Shared numbers, outstanding sends from earlier attempts, fixed sleeps, and parser assumptions can all cause intermittent failures. Isolate the receiving number, use a bounded wait, and retain enough stage information to identify which part failed.
What should I test besides the happy path?
Test wrong and expired codes, resend policy, attempt limits, number changes, and recovery. Release the number's lease in teardown, and reset or remove the application's test account using its supported cleanup process.