# Phone pools for parallel tests

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](/docs/txt-sms/#creating-phone-numbers) 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.

[API endpoint: `getOrCreatePhonePool`](/docs/api/#getOrCreatePhonePool)

[API endpoint: `addPhoneNumbersToPhonePool`](/docs/api/#addPhoneNumbersToPhonePool)

## 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.

```javascript
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();
  };
}
```

```javascript
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](/docs/wait-for/#wait-for-sms) 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.

[API endpoint: `acquirePhonePoolLease`](/docs/api/#acquirePhonePoolLease)

[API endpoint: `releasePhonePoolLease`](/docs/api/#releasePhonePoolLease)

## 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.

- [Playwright fixtures and retries](/docs/playwright/#fixtures-retries-and-ci)
- [Selenium parallel tests](/docs/selenium/#parallel-tests-retries-and-ci-troubleshooting)
- [Page Object Model for email and SMS MFA](/guides/page-object-model-e2e-mfa-testing/)
- [Load testing across phone cohorts](/docs/deliverability-test/)
