Phone pools for parallel tests
Documentation navigation
Reserve a phone number for each test worker, wait for its SMS, and release the lease without deleting the number.
A phone pool lets parallel workers borrow different numbers from an existing inventory. Each worker acquires a lease, uses the returned number for its application flow, then releases the lease. The number remains in your account for the next test.
Prepare the pool
Add phone numbers in the dashboard first. Provision enough numbers for the concurrency you intend to run. Creating a lease does not buy a number, and releasing it does not cancel the number's rental.
Create a named pool using createPhonePool or getOrCreatePhonePool. Add only the numbers intended for these tests with addPhoneNumbersToPhonePool. Keep production conversations and unrelated tests outside this pool.
/phone/pools/get-or-create
Get or create phone pool
Get a phone pool by name or create it if it does not exist
Request, parameters, and responses
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | |
description | string | No |
{
"name": "Example name",
"description": "value"
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | PhonePoolDetailDto | OK |
HTTP and SDK snippets
HTTP
POST /phone/pools/get-or-create HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
Content-Type: application/json
{
"name": "Example name",
"description": "value"
}
cURL
curl -X POST "https://api.mailslurp.com/phone/pools/get-or-create" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"name":"Example name","description":"value"}'
JavaScript SDK
import { Configuration, PhoneControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const phoneController = new PhoneControllerApi(config);
const request = {
"getOrCreatePhonePoolOptions": {
"name": "Example name",
"description": "value"
}
};
const result = await phoneController.getOrCreatePhonePool(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.phone_controller_api import PhoneControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
phoneController = PhoneControllerApi(api_client)
get_or_create_phone_pool_options = {
"name": "Example name",
"description": "value"
}
result = phoneController.get_or_create_phone_pool(get_or_create_phone_pool_options)
/phone/pools/{poolId}/numbers
Add phone numbers to phone pool
Add one or more owned phone numbers to a pool
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
poolId | string:uuid | Yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumberIds | string:uuid[] | Yes |
{
"phoneNumberIds": [
"00000000-0000-4000-8000-000000000000"
]
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | PhonePoolDetailDto | OK |
HTTP and SDK snippets
HTTP
POST /phone/pools/00000000-0000-4000-8000-000000000000/numbers HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
Content-Type: application/json
{
"phoneNumberIds": [
"00000000-0000-4000-8000-000000000000"
]
}
cURL
curl -X POST "https://api.mailslurp.com/phone/pools/00000000-0000-4000-8000-000000000000/numbers" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"phoneNumberIds":["00000000-0000-4000-8000-000000000000"]}'
JavaScript SDK
import { Configuration, PhoneControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const phoneController = new PhoneControllerApi(config);
const request = {
"poolId": "00000000-0000-4000-8000-000000000000",
"addPhonePoolNumbersOptions": {
"phoneNumberIds": [
"00000000-0000-4000-8000-000000000000"
]
}
};
const result = await phoneController.addPhoneNumbersToPhonePool(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.phone_controller_api import PhoneControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
phoneController = PhoneControllerApi(api_client)
add_phone_pool_numbers_options = {
"phoneNumberIds": [
"00000000-0000-4000-8000-000000000000"
]
}
result = phoneController.add_phone_numbers_to_phone_pool("00000000-0000-4000-8000-000000000000", add_phone_pool_numbers_options)
Reserve, test, and release
Use Node.js 22 or later for this REST example. Set MAILSLURP_API_KEY on the test runner and provide the pool ID. exercisePhone is your application-specific browser flow: it receives the number and the timestamp from before the flow started.
const API_BASE_URL = 'https://api.mailslurp.com';
function createApi(apiKey) {
if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
return async function api(path, { method = 'GET', body } = {}) {
const response = await fetch(`${API_BASE_URL}${path}`, {
method,
headers: {
'x-api-key': apiKey,
...(body ? { 'content-type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const detail = await response.text();
throw new Error(`MailSlurp ${method} ${path} failed (${response.status}): ${detail}`);
}
return response.status === 204 ? null : response.json();
};
}
async function withPhoneLease(api, poolId, workerId, exercisePhone) {
const lease = await api(`/phone/pools/${encodeURIComponent(poolId)}/leases`, {
method: 'POST',
body: {
leaseName: `SMS test for ${workerId}`,
leaseOwner: workerId,
leaseDurationMillis: 10 * 60 * 1000,
acquireTimeoutMillis: 30 * 1000,
},
});
const since = new Date();
try {
return await exercisePhone(lease, since);
} finally {
await api(
`/phone/pools/${encodeURIComponent(poolId)}/leases/${encodeURIComponent(lease.id)}`,
{ method: 'DELETE' },
);
}
}
Inside exercisePhone, enter lease.phoneNumber in the application, trigger the SMS, and call waitForSms or waitForLatestSms with lease.phoneNumberId. Use the supplied since, a bounded timeout, and sender/body conditions where appropriate. Extract the OTP, enter it in the application and assert the final authenticated state.
The lease duration must cover the whole browser flow, message wait and cleanup. An expired lease can make the number available to another worker, so abort work before its expiry rather than continuing with a number that may have been reassigned.
/phone/pools/{poolId}/leases
Acquire phone pool lease
Acquire an available phone number from the pool and mark it leased
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
poolId | string:uuid | Yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
leaseName | string | No | |
leaseOwner | string | No | |
leaseDurationMillis | integer:int64 | No | |
acquireTimeoutMillis | integer:int64 | No |
{
"leaseName": "Example name",
"leaseOwner": "value",
"leaseDurationMillis": 1,
"acquireTimeoutMillis": 1
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | PhonePoolLeaseDto | OK |
HTTP and SDK snippets
HTTP
POST /phone/pools/00000000-0000-4000-8000-000000000000/leases HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
Content-Type: application/json
{
"leaseName": "Example name",
"leaseOwner": "value",
"leaseDurationMillis": 1,
"acquireTimeoutMillis": 1
}
cURL
curl -X POST "https://api.mailslurp.com/phone/pools/00000000-0000-4000-8000-000000000000/leases" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"leaseName":"Example name","leaseOwner":"value","leaseDurationMillis":1,"acquireTimeoutMillis":1}'
JavaScript SDK
import { Configuration, PhoneControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const phoneController = new PhoneControllerApi(config);
const request = {
"poolId": "00000000-0000-4000-8000-000000000000",
"acquirePhonePoolLeaseOptions": {
"leaseName": "Example name",
"leaseOwner": "value",
"leaseDurationMillis": 1,
"acquireTimeoutMillis": 1
}
};
const result = await phoneController.acquirePhonePoolLease(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.phone_controller_api import PhoneControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
phoneController = PhoneControllerApi(api_client)
acquire_phone_pool_lease_options = {
"leaseName": "Example name",
"leaseOwner": "value",
"leaseDurationMillis": 1,
"acquireTimeoutMillis": 1
}
result = phoneController.acquire_phone_pool_lease("00000000-0000-4000-8000-000000000000", acquire_phone_pool_lease_options)
/phone/pools/{poolId}/leases/{leaseId}
Release phone pool lease
Release an active phone pool lease
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
poolId | string:uuid | Yes | |
leaseId | string:uuid | Yes |
Responses
| Status | Schema | Description |
|---|---|---|
204 | Response | No Content |
HTTP and SDK snippets
HTTP
DELETE /phone/pools/00000000-0000-4000-8000-000000000000/leases/00000000-0000-4000-8000-000000000000 HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
cURL
curl -X DELETE "https://api.mailslurp.com/phone/pools/00000000-0000-4000-8000-000000000000/leases/00000000-0000-4000-8000-000000000000" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json"
JavaScript SDK
import { Configuration, PhoneControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const phoneController = new PhoneControllerApi(config);
const request = {
"poolId": "00000000-0000-4000-8000-000000000000",
"leaseId": "00000000-0000-4000-8000-000000000000"
};
const result = await phoneController.releasePhonePoolLease(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.phone_controller_api import PhoneControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
phoneController = PhoneControllerApi(api_client)
result = phoneController.release_phone_pool_lease("00000000-0000-4000-8000-000000000000", "00000000-0000-4000-8000-000000000000")
Pool exhaustion and test retries
acquireTimeoutMillis bounds how long acquisition can wait for a free number; it is separate from the SMS wait timeout. If acquisition fails, report a resource/setup failure and do not pick an arbitrary shared number. Reduce parallelism or add numbers to the pool.
Give leaseOwner a worker/run identifier so a failed job is traceable. A retried test needs a new timestamp and a newly acquired lease. Do not reuse an earlier OTP or assume unread state alone isolates the test. If the process crashes before cleanup, the lease expires at expiresAt.
Use the pool from a browser framework
Put lease acquisition in a fixture or setup method and release in teardown or finally. Keep the lease in the message-service object rather than the page object, which should only operate the application UI.