MailSlurp logo

Use inbound TXT/SMS

Documentation navigation
Search documentation

Create MailSlurp phone numbers, send and receive SMS, wait for OTP codes, and test mobile messaging workflows from the dashboard or API.

MailSlurp supports creation and management of real mobile phone numbers in multiple regions. Phone numbers can receive inbound SMS text messages that can be read and received in code, tests, or online.

sms-basic

A common use for SMS is testing OTP codes. See the tutorials for examples:

Set up the SDK

The phone examples on this page use the MailSlurp SDK for JavaScript, Python, Java or C#. Choose a language once and every example on the page switches to it. Install the SDK for your language, set MAILSLURP_API_KEY in your server or test environment, then create a client:

JavaScript

import { MailSlurp } from 'mailslurp-client';

export function createClient() {
  const apiKey = process.env.MAILSLURP_API_KEY;
  if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
  return new MailSlurp({ apiKey });
}

Python

import os

import mailslurp_client


def create_client():
    api_key = os.environ.get("MAILSLURP_API_KEY")
    if not api_key:
        raise RuntimeError("Set MAILSLURP_API_KEY")
    configuration = mailslurp_client.Configuration()
    configuration.api_key["x-api-key"] = api_key
    return mailslurp_client.ApiClient(configuration)

Java

import com.mailslurp.apis.*;
import com.mailslurp.clients.ApiClient;
import com.mailslurp.clients.ApiException;
import com.mailslurp.models.*;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

final class PhoneSetup {
    static ApiClient createClient() {
        String apiKey = System.getenv("MAILSLURP_API_KEY");
        if (apiKey == null || apiKey.trim().isEmpty()) {
            throw new IllegalStateException("Set MAILSLURP_API_KEY");
        }
        ApiClient client = new ApiClient();
        client.setApiKey(apiKey);
        client.setConnectTimeout(10_000);
        client.setReadTimeout(75_000);
        return client;
    }
}

C#

using System;
using System.Collections.Generic;
using System.Linq;
using mailslurp.Api;
using mailslurp.Client;
using mailslurp.Model;

public static class PhoneSetup
{
    public static Configuration CreateConfiguration()
    {
        var apiKey = Environment.GetEnvironmentVariable("MAILSLURP_API_KEY");
        if (string.IsNullOrWhiteSpace(apiKey))
            throw new InvalidOperationException("Set MAILSLURP_API_KEY");
        var configuration = new Configuration { Timeout = TimeSpan.FromSeconds(75) };
        configuration.ApiKey.Add("x-api-key", apiKey);
        return configuration;
    }
}

The complete phone examples include dependency files for each language.

Creating phone numbers

First add a phone plan to your account subscription. Then provision a phone number for the plan in the MailSlurp dashboard or using the API.

Variants

Some regions support phone number variants such as Mobile and Toll Free. These variants affect the type of number allocated on our networks. Choosing the right variant can improve receipt of SMS messages from short-code numbers.

Create in dashboard

To assign a new phone number, use the dashboard phone number page. You can select from a range of countries and pricing plans.

Create phone

Create phone number programmatically

To enable API phone creation you must first have a paid plan and enable API creation on the phone number overview page in the dashboard.

enable sms

Once enabled you can call the create phone number endpoints:

const phone = await mailslurp.phoneController.createPhoneNumber({
  createPhoneNumberOptions: {
    name: 'Sales inbound',
    phoneCountry: CreatePhoneNumberOptionsPhoneCountryEnum.US,
  }
})
expect(phone.phoneNumber).toContain('+1')

Info. Each create phone number call bills your account for a phone number rental of one month. This is because telecom providers only rent phone numbers for a minimum of one month.

The phone creation endpoint is the API surface for provisioning numbers after API creation is enabled for your account.

POST /phone

Add phone number to your account. Only works if you have already added a plan and an initial phone number in your account and acknowledged the pricing and terms of service by enabling API phone creation.

Create new phone number

Request, parameters, and responses

Request body (required)

CreatePhoneNumberOptions application/json
FieldTypeRequiredDescription
phoneCountryenum: US | GB | AU | CA | EE | HK | PL | CH | PT | NL | IL | FI | SEYesValues: US, GB, AU, CA, EE, HK, PL, CH, PT, NL, IL, FI, SE
namestringNo
descriptionstringNo
tagsstring[]No
scheduleenum: MONTHLY | YEARLYNoValues: MONTHLY, YEARLY
phoneNumberEndpointOverridestringNo
phoneNumberVariantenum: LOCAL | MOBILE | TOLL_FREENoValues: LOCAL, MOBILE, TOLL_FREE
phoneProviderenum: T1 | T2NoValues: T1, T2
phoneLineFilterenum: ANY | OTP_FRIENDLY | NON_VOIPNoLine-quality preference for simple phone number provisioningValues: ANY, OTP_FRIENDLY, NON_VOIP
environmentAccountIdstring:uuidNo
Request example
{
  "phoneCountry": "US",
  "name": "Example name",
  "description": "value",
  "tags": [
    "example"
  ],
  "schedule": "MONTHLY",
  "phoneNumberEndpointOverride": "value"
}

Responses

StatusSchemaDescription
200PhoneNumberDtoOK
HTTP examples

HTTP

HTTP
POST /phone HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "phoneCountry": "US"
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "phoneCountry": "US"\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/phone' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"phoneCountry\": \"US\"\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/phone", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"phoneCountry\": \"US\"\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Listing phone numbers

Phone numbers have an ID and a number. When fetching the phone number use the ID. Use the PhoneControllerApi to manage phone numbers. List your numbers to find the ID of the one a test should use:

JavaScript

export async function listNumbers(mailslurp) {
  // Phone numbers are created in the dashboard or API; list them to find the one for your test.
  const page = await mailslurp.phoneController.getPhoneNumbers({ size: 20 });
  return page.content.map(phone => ({ id: phone.id, phoneNumber: phone.phoneNumber, country: phone.phoneCountry }));
}

Python

def list_numbers(api_client):
    # Phone numbers are created in the dashboard or API; list them to find the one for your test.
    page = mailslurp_client.PhoneControllerApi(api_client).get_phone_numbers(size=20)
    return [(phone.id, phone.phone_number, phone.phone_country) for phone in page.content]

Java

public static List<String> listNumbers(ApiClient client) throws ApiException {
    // Phone numbers are created in the dashboard or API; list them to find the one for your test.
    return new PhoneControllerApi(client).getPhoneNumbers().size(20).execute().getContent().stream()
        .map(phone -> phone.getId() + " " + phone.getPhoneNumber())
        .collect(Collectors.toList());
}

C#

public static List<string> ListNumbers(Configuration configuration)
{
    // Phone numbers are created in the dashboard or API; list them to find the one for your test.
    return new PhoneControllerApi(configuration).GetPhoneNumbers(size: 20).Content
        .Select(phone => $"{phone.Id} {phone.PhoneNumber}")
        .ToList();
}

You can fetch numbers directly using the getBy methods:

const phoneBy = await mailslurp.phoneController.getPhoneNumberByName({
  name: 'Sales inbound'
})

View SMS messages

In the dashboard you can view TXT message threads in the phones section:

View SMS

Use the SmsControllerApi to view and download SMS messages. This lists the latest messages received by one number:

JavaScript

export async function listSms(mailslurp, phoneNumberId) {
  const page = await mailslurp.smsController.getAllSmsMessages({ phoneNumber: phoneNumberId, size: 20 });
  return page.content.map(sms => ({ id: sms.id, from: sms.fromNumber, body: sms.body, createdAt: sms.createdAt }));
}

Python

def list_sms(api_client, phone_number_id):
    page = mailslurp_client.SmsControllerApi(api_client).get_all_sms_messages(
        phone_number=phone_number_id, size=20
    )
    return [(sms.id, sms.from_number, sms.body, sms.created_at) for sms in page.content]

Java

public static List<String> listSms(ApiClient client, UUID phoneNumberId) throws ApiException {
    return new SmsControllerApi(client).getAllSmsMessages().phoneNumber(phoneNumberId).size(20).execute()
        .getContent().stream()
        .map(sms -> sms.getFromNumber() + ": " + sms.getBody())
        .collect(Collectors.toList());
}

C#

public static List<string> ListSms(Configuration configuration, Guid phoneNumberId)
{
    return new SmsControllerApi(configuration).GetAllSmsMessages(phoneNumber: phoneNumberId, size: 20).Content
        .Select(sms => $"{sms.FromNumber}: {sms.Body}")
        .ToList();
}

You can fetch individual messages using the message's ID.

// fetch a message
const txtMessage = await mailslurp.smsController.getSmsMessage({
  smsId: sms.id // UUID of the SMS message
});
expect(txtMessage.read).toEqual(true);
expect(txtMessage.fromNumber).toEqual('+13252527014');

For tests that need to list, inspect, or parse SMS content after receipt, pair phone-number message listing with direct SMS reads and code extraction.

GET /phone/numbers/{phoneNumberId}/sms

List SMS messages for a phone number

Get SMS messages for a phone number

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
phoneNumberIdstring:uuidYes

Query parameters

NameTypeRequiredDescription
pageinteger:int32NoOptional page index in SMS list pagination
sizeinteger:int32NoOptional page size in SMS list pagination. Maximum size is 100. Use page index and sort to page through larger results
sortenum: ASC | DESCNoOptional createdAt sort direction ASC or DESCValues: ASC, DESC
unreadOnlybooleanNoOptional filter for unread SMS only. All SMS are considered unread until they are viewed in the dashboard or requested directly
sincestring:date-timeNoOptional filter SMSs received after given date time
beforestring:date-timeNoOptional filter SMSs received before given date time
searchstringNoOptional search filter
favouritebooleanNoOptionally filter results for favourites only

Responses

StatusSchemaDescription
200PageSmsProjectionOK
HTTP examples

HTTP

HTTP
GET /phone/numbers/00000000-0000-4000-8000-000000000000/sms HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/phone/numbers/00000000-0000-4000-8000-000000000000/sms' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/phone/numbers/00000000-0000-4000-8000-000000000000/sms", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone/numbers/00000000-0000-4000-8000-000000000000/sms")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
GET /sms/{smsId}

Get SMS content including body. Expects SMS to exist by ID. For SMS that may not have arrived yet use the WaitForController.

Returns a SMS summary object with content.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
smsIdstring:uuidYes

Responses

StatusSchemaDescription
200SmsDtoOK
HTTP examples

HTTP

HTTP
GET /sms/00000000-0000-4000-8000-000000000000 HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
POST /sms/{smsId}/codes

Extract verification codes from an SMS

Extract one-time passcodes and verification tokens from SMS body content. Deterministic `PATTERN` extraction is available now. Use method flags to control fallback behavior for QA.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
smsIdstring:uuidYesID of SMS to extract codes from

Request body

ExtractCodesOptions application/json
FieldTypeRequiredDescription
methodenum: AUTO | PATTERN | LLM | OCR | OCR_THEN_LLMNoExtraction strategy for verification codes. Unsupported strategies may fall back when allowFallback is true.Values: AUTO, PATTERN, LLM, OCR, OCR_THEN_LLM
allowFallbackbooleanNoAllow fallback to deterministic pattern extraction when the selected method is unavailable.
minLengthinteger:int32NoMinimum code length to consider. Typical OTP values are between 4 and 8 characters.
maxLengthinteger:int32NoMaximum code length to consider.
maxCandidatesinteger:int32NoMaximum number of code candidates to return. Best candidate is also returned separately.
customPatternsstring[]NoOptional custom regex patterns for code extraction. Each pattern should have either one capture group for the code or match the full code directly.
Request example
{
  "method": "AUTO",
  "allowFallback": true,
  "minLength": 4,
  "maxLength": 10,
  "maxCandidates": 5,
  "customPatterns": [
    "value"
  ]
}

Responses

StatusSchemaDescription
200ExtractCodesResultOK
HTTP examples

HTTP

HTTP
POST /sms/00000000-0000-4000-8000-000000000000/codes HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "allowFallback": true,
  "minLength": 4,
  "maxLength": 10,
  "maxCandidates": 5
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "allowFallback": true,\0012  "minLength": 4,\0012  "maxLength": 10,\0012  "maxCandidates": 5\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/codes' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"allowFallback\": true,\n  \"minLength\": 4,\n  \"maxLength\": 10,\n  \"maxCandidates\": 5\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/codes", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/codes")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"allowFallback\": true,\n  \"minLength\": 4,\n  \"maxLength\": 10,\n  \"maxCandidates\": 5\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Send SMS

You can send SMS programmatically or using the dashboard. Send from one of your numbers to an E.164 destination:

JavaScript

export async function sendSms(mailslurp, fromPhoneId, to) {
  // `to` is an E.164 number, for example +15555550100.
  return mailslurp.smsController.sendSms({
    fromPhoneId,
    smsSendOptions: { to, body: 'Your appointment is confirmed for 3pm.' },
  });
}

Python

def send_sms(api_client, from_phone_id, to):
    # `to` is an E.164 number, for example +15555550100.
    return mailslurp_client.SmsControllerApi(api_client).send_sms(
        mailslurp_client.SmsSendOptions(to=to, body="Your appointment is confirmed for 3pm."),
        from_phone_id=from_phone_id,
    )

Java

public static SentSmsDto sendSms(ApiClient client, UUID fromPhoneId, String to) throws ApiException {
    // `to` is an E.164 number, for example +15555550100.
    return new SmsControllerApi(client).sendSms(new SmsSendOptions()
        .to(to)
        .body("Your appointment is confirmed for 3pm.")).fromPhoneId(fromPhoneId).execute();
}

C#

public static SentSmsDto SendSms(Configuration configuration, Guid fromPhoneId, string to)
{
    // `to` is an E.164 number, for example +15555550100.
    return new SmsControllerApi(configuration).SendSms(
        new SmsSendOptions(to: to, body: "Your appointment is confirmed for 3pm."), fromPhoneId: fromPhoneId);
}

Info. Telecom providers impose some regulations on sending. Only send to numbers that have explicitly consented to being contacted. By default MailSlurp only permits sending to numbers that have initiated contact with you. To remove this restriction contact support.

Send a test message

Use the normal send endpoint with a controlled destination and content in test environments:

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

Use phone-scoped sending when the sender number matters. For a received inbound message, replies can be sent from the number that received it.

POST /phone/numbers/{phoneNumberId}/sms

Send TXT message from a phone number

Send SMS from a phone number

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
phoneNumberIdstring:uuidYes

Request body (required)

SmsSendOptions application/json
FieldTypeRequiredDescription
tostringYes
bodystringYes
Request example
{
  "to": "+15551234567",
  "body": "Hello from MailSlurp"
}

Responses

StatusSchemaDescription
200SentSmsDtoOK
HTTP examples

HTTP

HTTP
POST /phone/numbers/00000000-0000-4000-8000-000000000000/sms HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "to": "user@example.com",
  "body": "value"
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "to": "user@example.com",\0012  "body": "value"\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/phone/numbers/00000000-0000-4000-8000-000000000000/sms' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"to\": \"user@example.com\",\n  \"body\": \"value\"\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/phone/numbers/00000000-0000-4000-8000-000000000000/sms", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone/numbers/00000000-0000-4000-8000-000000000000/sms")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"to\": \"user@example.com\",\n  \"body\": \"value\"\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
POST /sms/{smsId}/reply

Send a reply to a received SMS message. Replies are sent from the receiving number.

Reply to an SMS message.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
smsIdstring:uuidYes

Request body (required)

SmsReplyOptions application/json
FieldTypeRequiredDescription
bodystringYes
Request example
{
  "body": "Hello from MailSlurp"
}

Responses

StatusSchemaDescription
200SentSmsDtoOK
HTTP examples

HTTP

HTTP
POST /sms/00000000-0000-4000-8000-000000000000/reply HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "body": "value"
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "body": "value"\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/reply' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"body\": \"value\"\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/reply", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/reply")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"body\": \"value\"\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Wait for new SMS messages

Use the WaitForControllerApi waitForSms methods to hold a connection open until an expected count of SMS messages is present in a phone number

// wait for the latest unread sms
const [sms] = await mailslurp.waitController.waitForSms({
  waitForSmsConditions: {
    count: 1,
    unreadOnly: true,
    phoneNumberId: phoneNumber.id,
    timeout: 30_000
  }
});
// extract a code from body with regex
expect(sms.body).toContain('Your code: 123');
const [, code] = /.+:\s([0-9]{3})/.exec(sms.body);
expect(code).toEqual('123');

This is an important technique for SMS based testing. See the testing guide for more information.

Wait for the latest SMS and read its code

For a one-time passcode test, record the time before your application sends the code, wait for the next unread SMS after that time, then ask MailSlurp for the code in the message:

JavaScript

export async function waitForSms(mailslurp, phoneNumberId, since) {
  // Waits until an unread SMS arrives after `since`, or throws after the timeout.
  return mailslurp.waitController.waitForLatestSms({
    waitForSingleSmsOptions: { phoneNumberId, timeout: 60_000, unreadOnly: true, since },
  });
}

Python

def wait_for_sms(api_client, phone_number_id, since):
    # Waits until an unread SMS arrives after `since`, or raises after the timeout.
    return mailslurp_client.WaitForControllerApi(api_client).wait_for_latest_sms(
        mailslurp_client.WaitForSingleSmsOptions(
            phone_number_id=phone_number_id, timeout=60_000, unread_only=True, since=since
        )
    )

Java

public static SmsDto waitForSms(ApiClient client, UUID phoneNumberId, OffsetDateTime since) throws ApiException {
    // Waits until an unread SMS arrives after `since`, or throws after the timeout.
    return new WaitForControllerApi(client).waitForLatestSms(new WaitForSingleSmsOptions()
        .phoneNumberId(phoneNumberId)
        .timeout(60_000L)
        .unreadOnly(true)
        .since(since)).execute();
}

C#

public static SmsDto WaitForSms(Configuration configuration, Guid phoneNumberId, DateTime since)
{
    // Waits until an unread SMS arrives after `since`, or throws after the timeout.
    return new WaitForControllerApi(configuration).WaitForLatestSms(new WaitForSingleSmsOptions(
        phoneNumberId: phoneNumberId, timeout: 60_000, unreadOnly: true, since: since));
}

JavaScript

export async function extractCode(mailslurp, smsId) {
  // MailSlurp finds the one-time code in the message body.
  const result = await mailslurp.smsController.getSmsCodes({ smsId });
  if (!result.found) throw new Error('No code found in the SMS');
  return result.code;
}

Python

def extract_code(api_client, sms_id):
    # MailSlurp finds the one-time code in the message body.
    result = mailslurp_client.SmsControllerApi(api_client).get_sms_codes(sms_id)
    if not result.found:
        raise ValueError("No code found in the SMS")
    return result.code

Java

public static String extractCode(ApiClient client, UUID smsId) throws ApiException {
    // MailSlurp finds the one-time code in the message body.
    ExtractCodesResult result = new SmsControllerApi(client).getSmsCodes(smsId).execute();
    if (!Boolean.TRUE.equals(result.getFound())) throw new IllegalStateException("No code found in the SMS");
    return result.getCode();
}

C#

public static string ExtractCode(Configuration configuration, Guid smsId)
{
    // MailSlurp finds the one-time code in the message body.
    var result = new SmsControllerApi(configuration).GetSmsCodes(smsId);
    if (!result.Found) throw new InvalidOperationException("No code found in the SMS");
    return result.Code;
}

Define BLOCK and ALLOW rules

You can define rules for which phone numbers can send SMS to your phone numbers. This is done using rulesets. You can create rulesets that block or allow SMS from specific phone numbers or patterns to prevent any unwanted spam. An example looks like this:

// block SMS spam except for known numbers
const blockAll = await rulesetController.createNewRuleset({
  phoneId: undefined, // account wide block
  createRulesetOptions: {
    action: 'BLOCK',
    scope: 'RECEIVING_SMS',
    target: '*'
  }
})
const allowSpecific = await rulesetController.createNewRuleset({
  phoneId: undefined, // account wide permit
  createRulesetOptions: {
    action: 'ALLOW',
    scope: 'RECEIVING_SMS',
    target: '+1234567890' // replace with a known number
  }
})

In the app dashboard the interface can be found in the automations tab on the phone number overview:

block-spam-sms.jpeg

When a test triggers an OTP or MFA flow, wait for the expected SMS instead of sleeping for a fixed delay.

POST /waitForSms

Wait for an SMS message to match the provided filter conditions such as body contains keyword.

Generic waitFor method that will wait until a phone number meets given conditions or return immediately if already met

Request, parameters, and responses

Request body (required)

WaitForSmsConditions application/json
FieldTypeRequiredDescription
phoneNumberIdstring:uuidYesID of phone number to search within and apply conditions to. Essentially filtering the SMS found to give a count.
limitinteger:int32NoLimit results
countinteger:int64YesNumber of results that should match conditions. Either exactly or at least this amount based on the `countType`. If count condition is not met and the timeout has not been reached the `waitFor` method will retry the operation.
delayTimeoutinteger:int64NoMax time in milliseconds to wait between retries if a `timeout` is specified.
timeoutinteger:int64YesMax time in milliseconds to retry the `waitFor` operation until conditions are met.
unreadOnlybooleanNoApply conditions only to **unread** SMS. All SMS messages begin with `read=false`. An SMS is marked `read=true` when an `SMS` has been returned to the user at least once. For example you have called `getSms`, or you have viewed the SMS in the dashboard.
countTypeenum: EXACTLY | ATLEASTNoHow result size should be compared with the expected size. Exactly or at-least matching result?Values: EXACTLY, ATLEAST
matchesSmsMatchOption[]NoConditions that should be matched for an SMS to qualify for results. Each condition will be applied in order to each SMS within a phone number to filter a result list of matching SMSs you are waiting for.
sortDirectionenum: ASC | DESCNoDirection to sort matching SMSs by created timeValues: ASC, DESC
sincestring:date-timeNoISO Date Time earliest time of SMS to consider. Filter for matching SMSs that were received after this date
beforestring:date-timeNoISO Date Time latest time of SMS to consider. Filter for matching SMSs that were received before this date
aiAIMessageMatchOptionsNo
Request example
{
  "phoneNumberId": "00000000-0000-4000-8000-000000000000",
  "count": 1,
  "timeout": 1,
  "limit": 1,
  "delayTimeout": 1,
  "unreadOnly": true
}

Responses

StatusSchemaDescription
200SmsPreview[]OK
HTTP examples

HTTP

HTTP
POST /waitForSms HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "phoneNumberId": "00000000-0000-4000-8000-000000000000",
  "count": 1,
  "timeout": 1
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "phoneNumberId": "00000000-0000-4000-8000-000000000000",\0012  "count": 1,\0012  "timeout": 1\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/waitForSms' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"phoneNumberId\": \"00000000-0000-4000-8000-000000000000\",\n  \"count\": 1,\n  \"timeout\": 1\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/waitForSms", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/waitForSms")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"phoneNumberId\": \"00000000-0000-4000-8000-000000000000\",\n  \"count\": 1,\n  \"timeout\": 1\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
POST /waitForLatestSms

Wait for the latest SMS message to match the provided filter conditions such as body contains keyword.

Wait until a phone number meets given conditions or return immediately if already met

Request, parameters, and responses

Request body (required)

WaitForSingleSmsOptions application/json
FieldTypeRequiredDescription
phoneNumberIdstring:uuidYes
timeoutinteger:int64Yes
unreadOnlybooleanNo
beforestring:date-timeNo
sincestring:date-timeNo
sortDirectionenum: ASC | DESCNoValues: ASC, DESC
delayinteger:int64No
Request example
{
  "phoneNumberId": "00000000-0000-4000-8000-000000000000",
  "timeout": 1,
  "unreadOnly": true,
  "before": "2026-06-21T00:00:00.000Z",
  "since": "2026-06-21T00:00:00.000Z",
  "sortDirection": "ASC"
}

Responses

StatusSchemaDescription
200SmsDtoOK
HTTP examples

HTTP

HTTP
POST /waitForLatestSms HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "phoneNumberId": "00000000-0000-4000-8000-000000000000",
  "timeout": 1
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "phoneNumberId": "00000000-0000-4000-8000-000000000000",\0012  "timeout": 1\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/waitForLatestSms' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"phoneNumberId\": \"00000000-0000-4000-8000-000000000000\",\n  \"timeout\": 1\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/waitForLatestSms", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/waitForLatestSms")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"phoneNumberId\": \"00000000-0000-4000-8000-000000000000\",\n  \"timeout\": 1\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Use SMS webhooks

Have SMS messages sent via HTTP POST to your server with TXT Webhooks

// 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'
  }
});

Manage number ownership and reuse

Name and tag numbers by environment or purpose so tests can find their assigned inventory. Use get-by-name or get-by-number when selecting a known resource and paginate inventory reads rather than assuming the first page contains every number.

For parallel tests, use phone pools and leases. Reserve a number for the entire application flow, capture a time boundary before requesting the OTP and release the lease after verification. Deleting a rented phone number is a separate lifecycle action; it is not test-message cleanup or a lease release.

GET /phone/numbers

Get phone numbers

List phone numbers for account

Request, parameters, and responses

Query parameters

NameTypeRequiredDescription
phoneCountryenum: US | GB | AU | CA | EE | HK | PL | CH | PT | NL | IL | FI | SENoOptional phone countryValues: US, GB, AU, CA, EE, HK, PL, CH, PT, NL, IL, FI, SE
lineTypestringNoOptional line type filter
carrierNamestringNoOptional carrier name filter
mobileCountryCodestringNoOptional mobile country code filter
mobileNetworkCodestringNoOptional mobile network code filter
providerLabelstringNoOptional provider label filter such as T1 or T2
pageinteger:int32NoOptional page index for list pagination
sizeinteger:int32NoOptional page size for list pagination
sortenum: ASC | DESCNoOptional createdAt sort direction ASC or DESCValues: ASC, DESC
sincestring:date-timeNoFilter by created at after the given timestamp
beforestring:date-timeNoFilter by created at before the given timestamp
searchstringNoOptional search filter
tagstring[]NoOptional tags to filter by. A phone must include all provided tags
includestring:uuid[]NoOptional phoneIds to include in result
favouritebooleanNoOptionally filter results for favourites only

Responses

StatusSchemaDescription
200PagePhoneNumberProjectionOK
HTTP examples

HTTP

HTTP
GET /phone/numbers HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/phone/numbers' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/phone/numbers", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone/numbers")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
GET /phone/provisioning/capabilities

Get phone provisioning capabilities

Get supported provider-country variant capabilities for advanced provisioning

Request, parameters, and responses

Query parameters

NameTypeRequiredDescription
phoneCountryenum: US | GB | AU | CA | EE | HK | PL | CH | PT | NL | IL | FI | SEYesValues: US, GB, AU, CA, EE, HK, PL, CH, PT, NL, IL, FI, SE
providerLabelstringNo

Responses

StatusSchemaDescription
200PhoneProviderCapabilitiesResultOK
HTTP examples

HTTP

HTTP
GET /phone/provisioning/capabilities?phoneCountry=US HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/phone/provisioning/capabilities?phoneCountry=US' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/phone/provisioning/capabilities?phoneCountry=US", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone/provisioning/capabilities?phoneCountry=US")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
POST /phone/provisioning/search

Search available phone numbers

Search available numbers for advanced provisioning

Request, parameters, and responses

Request body (required)

FieldTypeRequiredDescription
phoneCountryenum: US | GB | AU | CA | EE | HK | PL | CH | PT | NL | IL | FI | SEYesValues: US, GB, AU, CA, EE, HK, PL, CH, PT, NL, IL, FI, SE
phoneVariantenum: LOCAL | MOBILE | TOLL_FREENoValues: LOCAL, MOBILE, TOLL_FREE
qualityFilterenum: OTP | NON_VOIP | MOBILE_ONLY | ANYNoQuality filter for advanced phone provisioning searchValues: OTP, NON_VOIP, MOBILE_ONLY, ANY
lineTypestringNo
carrierNamestringNo
mobileCountryCodestringNo
mobileNetworkCodestringNo
providerLabelsstring[]No
limitinteger:int32No
Request example
{
  "phoneCountry": "US",
  "phoneVariant": "LOCAL",
  "qualityFilter": "OTP",
  "lineType": "value",
  "carrierName": "Example name",
  "mobileCountryCode": "value"
}

Responses

StatusSchemaDescription
200AvailablePhoneNumbersResultOK
HTTP examples

HTTP

HTTP
POST /phone/provisioning/search HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "phoneCountry": "US"
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "phoneCountry": "US"\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/phone/provisioning/search' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"phoneCountry\": \"US\"\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/phone/provisioning/search", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone/provisioning/search")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"phoneCountry\": \"US\"\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
POST /phone/provisioning/jobs

Create a phone provisioning job

Create an advanced phone provisioning job from shortlisted numbers

Request, parameters, and responses

Request body (required)

FieldTypeRequiredDescription
phoneCountryenum: US | GB | AU | CA | EE | HK | PL | CH | PT | NL | IL | FI | SEYesValues: US, GB, AU, CA, EE, HK, PL, CH, PT, NL, IL, FI, SE
phoneVariantenum: LOCAL | MOBILE | TOLL_FREENoValues: LOCAL, MOBILE, TOLL_FREE
itemsCreatePhoneProvisioningJobItemOptions[]Yes
Request example
{
  "phoneCountry": "US",
  "items": [
    {
      "phoneNumber": "value",
      "providerLabel": "value",
      "lineType": "value",
      "carrierName": "Example name",
      "mobileCountryCode": "value",
      "mobileNetworkCode": "value"
    }
  ],
  "phoneVariant": "LOCAL"
}

Responses

StatusSchemaDescription
200PhoneProvisioningJobDtoOK
HTTP examples

HTTP

HTTP
POST /phone/provisioning/jobs HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{
  "phoneCountry": "US",
  "items": [
    {
      "phoneNumber": "value"
    }
  ]
}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{\0012  "phoneCountry": "US",\0012  "items": [\0012    {\0012      "phoneNumber": "value"\0012    }\0012  ]\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/phone/provisioning/jobs' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{\n  \"phoneCountry\": \"US\",\n  \"items\": [\n    {\n      \"phoneNumber\": \"value\"\n    }\n  ]\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/phone/provisioning/jobs", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/phone/provisioning/jobs")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"phoneCountry\": \"US\",\n  \"items\": [\n    {\n      \"phoneNumber\": \"value\"\n    }\n  ]\n}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Advanced provisioning exposes available country/variant capabilities and a job workflow. Check job status before using the number. Confirm pricing and API-creation prerequisites in the dashboard before automating purchases.

Troubleshoot a missing verification text

Check the selected phone ID, application destination and country format first. Confirm the application actually requested a new code and that another test does not own the number. Keep the SMS wait bounded and filter by sender or content when the workflow has a known signature.

Use since captured before the application action to exclude old messages. unreadOnly is useful but shared dashboard reads and other workers can change read state. A resend may make a previous code invalid; assert that the application accepts the extracted code rather than only checking that an SMS exists.

Use SMS webhook recovery for asynchronous consumers, or scoped SMS access when an AI agent should see only assigned numbers.