MailSlurp logo

Receive SMS in Code with a Real Phone Number

Create a MailSlurp phone number, read inbound text messages, wait for an OTP in a test, and forward new SMS to your server with a webhook.

Use with your tools
Receive SMS in Code with a Real Phone Number article preview

Your code can receive a real text message through a MailSlurp phone number. Use the assigned number's ID to read a message, wait for an OTP during a test, or set up a webhook for incoming replies.

Use this approach when a signup form sends an SMS OTP, a customer replies to a product number, or an application needs to turn an incoming text into an event. The same messages remain visible in the MailSlurp dashboard, which makes a failed automation easier to inspect.

Before you start

Add a phone plan to your MailSlurp account and provision a number from the dashboard. If you want to create numbers through the API, enable API phone creation from the phone-number overview first.

Phone numbers are rented from telecom providers by the month. Reuse a controlled number for a stable test lane when that suits your workflow, or isolate numbers by environment and parallel test worker when messages could be consumed by the wrong run.

Fetch your assigned phone number

MailSlurp phone numbers have a displayed number and an internal ID. Set MAILSLURP_PHONE_NUMBER_ID to the ID assigned to your test and keep MAILSLURP_API_KEY in the test environment. Submit the displayed number to the application that sends the SMS; use the ID in MailSlurp calls.

Fetching the phone does not reserve it. Keep the number exclusive to this flow, or use a phone-pool lease for parallel tests and release the lease afterward.

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 an API key and the ID of your assigned test number');
}
const mailslurp = new MailSlurp({ apiKey });
const phoneNumber = await mailslurp.phoneController.getPhoneNumber({ phoneNumberId });
// Give phoneNumber.phoneNumber to your app. Use phoneNumber.id in API calls.

If the sender uses a short code, check whether a mobile or toll-free number variant is available and appropriate in your chosen region.

Read a received SMS

Use the SMS API to fetch message content after it has arrived.

import { MailSlurp } from 'mailslurp-client';

export async function readSms(mailslurp: MailSlurp, smsId: string) {
  return mailslurp.smsController.getSmsMessage({ smsId });
}
// Inspect the returned message in your test; avoid logging its body or OTP.
// Reading a message does not release or cancel the phone number.

Inspect the sender, recipient, body, and arrival time. For an OTP workflow, extract the code and then submit it through the real product flow. Finding a code-shaped value is not enough if the user still cannot complete verification.

Wait for a new SMS in a test

An SMS can arrive after the action that triggered it. The helper captures since before requesting the code, then waits on the assigned number with a 60-second timeout and unreadOnly: false.

Supply requestCode, submitCode and assertSignedIn from your application test. Adapt the exact six-digit template to your message, and make the final function reject unless the intended account is signed in.

import { MailSlurp } from 'mailslurp-client';

export async function checkSmsLogin(
  mailslurp: MailSlurp,
  phoneNumber: { id: string; phoneNumber: string },
  actions: {
    requestCode: (number: string) => Promise<void>;
    submitCode: (code: string) => Promise<void>;
    assertSignedIn: () => Promise<void>;
  },
) {
  // Reserve this number exclusively for this attempt before calling the helper.
  const since = new Date();
  await actions.requestCode(phoneNumber.phoneNumber);
  const sms = await mailslurp.waitController.waitForLatestSms({
    waitForSingleSmsOptions: {
      phoneNumberId: phoneNumber.id,
      since,
      timeout: 60_000,
      unreadOnly: false,
    },
  });
  // Adapt this exact template to the SMS your application sends.
  const match = /^Your code: ([0-9]{6})$/.exec(sms.body ?? '');
  if (!match) throw new Error('SMS did not match the expected OTP template');
  await actions.submitCode(match[1]);
  await actions.assertSignedIn();
}

The complete test should:

  1. give the application a controlled MailSlurp number,
  2. trigger the signup, login, recovery, or reply action,
  3. wait for the new SMS,
  4. verify that it belongs to the current test,
  5. extract and submit the OTP or process the reply,
  6. assert the resulting user or application state.

Keep the phone number ID, trigger time, and received message ID with failed CI output. Use them to investigate delivery, message selection and application failures without writing the OTP or full message body into shared logs.

Forward inbound SMS to a webhook

Use a phone-number webhook when every new text should be sent to your application as it arrives.

// create a webhook for a phone number to have new SMS sent to your server
const webhook = await mailslurp.webhookController.createWebhookForPhoneNumber({
  phoneNumberId: phoneNumber.id,
  createWebhookOptions: {
    eventName: CreateWebhookOptionsEventNameEnum.NEW_SMS,
    url: "https://myserver.com",
    name: "Process SMS",
  },
});

Your endpoint should acknowledge receipt quickly, record the message identity, and move slow work to a queue or background process. Keep API retrieval available for debugging and reconciliation if downstream processing fails.

See SMS webhooks for a receiver design and troubleshooting checklist.

Send a reply from the phone number

MailSlurp supports outbound SMS from provisioned phone numbers for consent-based workflows. By default, you can send to numbers that have initiated contact with your MailSlurp number. Contact support if an approved use case needs a different outbound policy.

await mailslurp.phoneController.sendSmsFromPhoneNumber({
  phoneNumberId: sendingNumber,
  sendPhoneNumberOptions: {
    to: '+14155551212',
    message: 'Hello from Mailslurp'
  }
});

This is useful for a controlled two-way test: receive the first message, reply from MailSlurp, send another response back, and assert that the application keeps the conversation attached to the correct customer.

Avoid flaky SMS tests

  • Use an exclusive number or phone-pool lease for each active worker; a timestamp alone cannot separate concurrent requests to the same number.
  • Record the trigger time to exclude messages already received. Do not reuse a number while an earlier request is outstanding: its delayed SMS could still arrive after that boundary.
  • Use the number type and region expected by the real sender, especially for short-code OTP routes.
  • Assert the final signup, login, recovery, or conversation state instead of stopping after message receipt.
  • Preserve MailSlurp message IDs in test artifacts so a human can inspect the source when CI fails.

Continue with MailSlurp