MailSlurp logo

Wait for email and SMS messages

Documentation navigation
Search documentation

Choose MailSlurp wait methods, match the right email or SMS, use AI prompts and structured extraction, and handle stale messages, ambiguity, timeouts, and retries in automated tests.

View MarkdownAgent setup

Email and SMS delivery is asynchronous. MailSlurp wait methods keep the request open until the requested message or count is available, or the timeout is reached. They return immediately when existing messages already meet the conditions.

The important part is defining which messages belong to your test. A wait without a suitable time boundary or filter can return an older message.

Info: Start with the full testing flow. The integration testing guide explains inboxes, emails, phone numbers, application actions, extraction, and assertions. The Playwright guide puts the steps into one browser test.

Choose a wait method

You need JavaScript SDK method on waitController Result
One email from an isolated inbox waitForLatestEmail Full Email
One email matching sender, subject, or recipient waitForMatchingFirstEmail Full Email
Several matching emails waitForMatchingEmails EmailPreview[]; fetch full bodies by ID
A number of emails without field matching waitForEmailCount EmailPreview[]
A message at a zero-based index waitForNthEmail Full Email
Combined count, time, sort, and field conditions waitFor EmailPreview[]
One SMS on a phone number waitForLatestSms Full SmsDto
SMS messages matching sender or body waitForSms SmsPreview[]; fetch full messages by ID

For semantic selection, use POST /ai/messages/wait to return the selected message together with assertion results, extracted data, and diagnostics. Use /ai/messages/wait-all when several named deliveries must all arrive. These operations belong to mailslurp.aiController; see AI matching below for HTTP examples and wait-controller aliases.

The deterministic examples below use the TypeScript SDK. HTTP clients and other SDKs expose the same operations in the API reference.

Create the client and inbox

Install mailslurp-client, set MAILSLURP_API_KEY in your test environment, and create the client in the test process:

import assert from 'node:assert/strict';
import { MailSlurp, MatchOptionFieldEnum, MatchOptionShouldEnum } from 'mailslurp-client';

const apiKey = process.env.MAILSLURP_API_KEY;
if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
const mailslurp = new MailSlurp({ apiKey });
const inbox = await mailslurp.createInboxWithOptions({ expiresIn: 600_000 });
const since = new Date();
// Trigger your application's email action with inbox.emailAddress now.

Each recipe below is an alternative using that setup. Do not run all the unread-only examples consecutively against one message: a full-message read changes its read state.

Wait for latest email

const email = await mailslurp.waitController.waitForLatestEmail({
  inboxId: inbox.id,
  since,
  timeout: 60_000,
  unreadOnly: true,
});
assert.ok(email.to.includes(inbox.emailAddress));
assert.ok(email.body);

This selects a qualifying latest email; it does not inherently mean "the next email sent by my application". Use it when only one message is expected in a fresh inbox. If several kinds of email can arrive, choose a matching method.

Wait for matching emails

Return one full email

const verificationEmail = await mailslurp.waitController.waitForMatchingFirstEmail({
  inboxId: inbox.id,
  since,
  timeout: 60_000,
  unreadOnly: true,
  matchOptions: {
    matches: [
      {
        field: MatchOptionFieldEnum.SUBJECT,
        should: MatchOptionShouldEnum.CONTAIN,
        value: 'Please confirm your email address',
      },
      {
        field: MatchOptionFieldEnum.TO,
        should: MatchOptionShouldEnum.CONTAIN,
        value: inbox.emailAddress,
      },
    ],
  },
});
assert.ok(verificationEmail.body);

The filters are applied together. A message must pass both of these conditions. Other email fields include FROM, CC, BCC, and HEADERS; EQUAL checks an exact value and CONTAIN checks contained text. Email body extraction uses a different operation, such as getEmailContentMatch.

To require an attachment, add conditions: [{ condition: 'HAS_ATTACHMENTS', value: 'TRUE' }] to matchOptions in an HTTP/JavaScript request. TypeScript exposes the corresponding ConditionOptionConditionEnum and ConditionOptionValueEnum values.

Info: Extract the value after matching. Continue with OTP, link, and HTML extraction. Matching selects the message; extraction retrieves a value from its content.

Return several matches and read their bodies

For a two-message order flow, make both subjects include a unique order reference, then wait for the two matches:

const previews = await mailslurp.waitController.waitForMatchingEmails({
  inboxId: inbox.id,
  since,
  timeout: 60_000,
  count: 2,
  unreadOnly: true,
  matchOptions: {
    matches: [{
      field: MatchOptionFieldEnum.SUBJECT,
      should: MatchOptionShouldEnum.CONTAIN,
      value: 'ORDER-1042',
    }],
  },
});
assert.equal(previews.length, 2);
const messages = await Promise.all(previews.map((preview) =>
  mailslurp.emailController.getEmail({ emailId: preview.id })
));
assert.ok(messages.every((message) => message.body));

An EmailPreview is not the full message body. Retain the preview IDs and call getEmail when you need content or attachments. Assert the expected message types or references as well as the count; two copies of the same notification should not satisfy a receipt-and-dispatch test.

Wait for plus-addressed emails

When multiple test recipients share an inbox, match the full plus address in the TO field. Set MAILSLURP_INBOX_ID to the parent inbox ID, MAILSLURP_PLUS_ADDRESS to the complete recipient used by this test, and TEST_STARTED_AT to an ISO timestamp captured before triggering the send.

curl --fail-with-body --silent --show-error \
  --max-time 75 \
  --request POST 'https://api.mailslurp.com/waitFor' \
  --header "x-api-key: $MAILSLURP_API_KEY" \
  --header 'Content-Type: application/json' \
  --data "{
    \"inboxId\": \"$MAILSLURP_INBOX_ID\",
    \"since\": \"$TEST_STARTED_AT\",
    \"count\": 1,
    \"countType\": \"ATLEAST\",
    \"timeout\": 60000,
    \"unreadOnly\": true,
    \"matches\": [{
      \"field\": \"TO\",
      \"should\": \"CONTAIN\",
      \"value\": \"$MAILSLURP_PLUS_ADDRESS\"
    }]
  }"

This returns previews. Fetch the selected message by its ID before extracting its body. Plus aliases separate recipient identities, while the stored messages and read state still belong to the parent inbox.

Info: Set up the alias lifecycle. The plus-addressing guide explains creating or retrieving a plus address, matching it, and listing messages by alias ID.

Wait for a count or an indexed email

waitForEmailCount waits until at least the requested count is available and returns that many previews:

const batch = await mailslurp.waitController.waitForEmailCount({
  inboxId: inbox.id,
  since,
  count: 3,
  timeout: 60_000,
  unreadOnly: false,
});
assert.equal(batch.length, 3);

Use waitFor when you also need countType: ATLEAST or EXACTLY, field matching, and a sort direction. EXACTLY limits the returned result count; do not treat it as proof that only that many messages were delivered. A wait can complete before duplicates arrive. Use a defined observation window and count checks for duplicate-delivery tests.

Wait for email number

Indexes are zero-based. With ascending order, index: 0 selects the oldest qualifying message and index: 2 selects the third:

import { WaitForNthEmailSortEnum } from 'mailslurp-client';

const thirdEmail = await mailslurp.waitController.waitForNthEmail({
  inboxId: inbox.id,
  since,
  index: 2,
  sort: WaitForNthEmailSortEnum.ASC,
  unreadOnly: false,
  timeout: 60_000,
});
assert.ok(thirdEmail.id);

The index applies to the filtered, sorted set. Adding unreadOnly: true changes that set as messages are read. For business sequences, matching a specific subject or reference is usually clearer than relying on arrival order.

For stricter matching, use the generic wait endpoint when you need one request body that combines count, timeout, unread state, sort order, and field-level match rules.

POST /waitFor

Wait for an email to match the provided filter conditions such as subject contains keyword.

Generic waitFor method that will wait until an inbox meets given conditions or return immediately if already met

Request, parameters, and responses

Request body (required)

WaitForConditions application/json
FieldTypeRequiredDescription
inboxIdstring:uuidYesID of inbox to search within and apply conditions to. Essentially filtering the emails found to give a count.
countinteger:int32NoNumber 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** emails. All emails begin with `read=false`. An email is marked `read=true` when an `EmailDto` representation of it has been returned to the user at least once. For example you have called `getEmail` or `waitForLatestEmail` etc., or you have viewed the email in the dashboard.
countTypeenum: EXACTLY | ATLEASTNoHow result size should be compared with the expected size. Exactly or at-least matching result?
matchesMatchOption[]NoConditions that should be matched for an email to qualify for results. Each condition will be applied in order to each email within an inbox to filter a result list of matching emails you are waiting for.
sortDirectionenum: ASC | DESCNoDirection to sort matching emails by created time
sincestring:date-timeNoISO Date Time earliest time of email to consider. Filter for matching emails that were received after this date
beforestring:date-timeNoISO Date Time latest time of email to consider. Filter for matching emails that were received before this date
Request example
{
  "inboxId": "00000000-0000-4000-8000-000000000000",
  "timeout": 1,
  "count": 1,
  "delayTimeout": 1,
  "unreadOnly": true,
  "countType": "EXACTLY"
}

Responses

StatusSchemaDescription
200EmailPreview[]OK
HTTP and SDK snippets

HTTP

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

{
  "inboxId": "00000000-0000-4000-8000-000000000000",
  "timeout": 1,
  "count": 1,
  "delayTimeout": 1,
  "unreadOnly": true,
  "countType": "EXACTLY"
}

cURL

cURL
curl -X POST "https://api.mailslurp.com/waitFor" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data '{"inboxId":"00000000-0000-4000-8000-000000000000","timeout":1,"count":1,"delayTimeout":1,"unreadOnly":true,"countType":"EXACTLY"}'

JavaScript SDK

JavaScript SDK
import { Configuration, WaitForControllerApi } from "mailslurp-client";

const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const waitForController = new WaitForControllerApi(config);
const request = {
  "waitForConditions": {
    "inboxId": "00000000-0000-4000-8000-000000000000",
    "timeout": 1,
    "count": 1,
    "delayTimeout": 1,
    "unreadOnly": true,
    "countType": "EXACTLY"
  }
};

const result = await waitForController.waitFor(request);

Python SDK

Python SDK
import mailslurp_client
from mailslurp_client.api.wait_for_controller_api import WaitForControllerApi

configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"

with mailslurp_client.ApiClient(configuration) as api_client:
    waitForController = WaitForControllerApi(api_client)
    wait_for_conditions = {
      "inboxId": "00000000-0000-4000-8000-000000000000",
      "timeout": 1,
      "count": 1,
      "delayTimeout": 1,
      "unreadOnly": True,
      "countType": "EXACTLY"
    }
    result = waitForController.wait_for(wait_for_conditions)
POST /waitForMatchingEmails

Wait or return list of emails that match simple matching patterns

Perform a search of emails in an inbox with the given patterns. If results match expected count then return or else retry the search until results are found or timeout is reached. Match options allow simple CONTAINS or EQUALS filtering on SUBJECT, TO, BCC, CC, and FROM. See the `MatchOptions` object for options. An example payload is `{ matches: [{field: 'SUBJECT',should:'CONTAIN',value:'needle'}] }`. You can use an array of matches and they will be applied sequentially to filter out emails. If you want to perform matches and extractions of content using Regex patterns see the EmailController `getEmailContentMatch` method.

Request, parameters, and responses

Query parameters

NameTypeRequiredDescription
inboxIdstring:uuidYesId of the inbox we are fetching emails from
countinteger:int32YesNumber of emails to wait for. Must be greater or equal to 1
beforestring:date-timeNoFilter for emails that were received before the given timestamp
sincestring:date-timeNoFilter for emails that were received after the given timestamp
sortenum: ASC | DESCNoSort directionValues: ASC, DESC
delayinteger:int64NoMax milliseconds delay between calls
timeoutinteger:int64NoMax milliseconds to wait
unreadOnlybooleanNoOptional filter for unread only

Request body (required)

MatchOptions application/json
FieldTypeRequiredDescription
matchesMatchOption[]NoZero or more match options such as `{ field: 'SUBJECT', should: 'CONTAIN', value: 'Welcome' }`. Options are additive so if one does not match the email is excluded from results
conditionsConditionOption[]NoZero or more conditions such as `{ condition: 'HAS_ATTACHMENTS', value: 'TRUE' }`. Note the values are the strings `TRUE|FALSE` not booleans.
Request example
{
  "matches": [
    {
      "field": "SUBJECT",
      "should": "MATCH",
      "value": "value"
    }
  ],
  "conditions": [
    {
      "condition": "HAS_ATTACHMENTS",
      "value": "TRUE"
    }
  ]
}

Responses

StatusSchemaDescription
200EmailPreview[]OK
HTTP and SDK snippets

HTTP

HTTP
POST /waitForMatchingEmails?inboxId=00000000-0000-4000-8000-000000000000&count=value&before=value HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
Content-Type: application/json

{
  "matches": [
    {
      "field": "SUBJECT",
      "should": "MATCH",
      "value": "value"
    }
  ],
  "conditions": [
    {
      "condition": "HAS_ATTACHMENTS",
      "value": "TRUE"
    }
  ]
}

cURL

cURL
curl -X POST "https://api.mailslurp.com/waitForMatchingEmails?inboxId=00000000-0000-4000-8000-000000000000&count=value&before=value" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data '{"matches":[{"field":"SUBJECT","should":"MATCH","value":"value"}],"conditions":[{"condition":"HAS_ATTACHMENTS","value":"TRUE"}]}'

JavaScript SDK

JavaScript SDK
import { Configuration, WaitForControllerApi } from "mailslurp-client";

const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const waitForController = new WaitForControllerApi(config);
const request = {
  "inboxId": "00000000-0000-4000-8000-000000000000",
  "count": null,
  "before": "value",
  "matchOptions": {
    "matches": [
      {
        "field": "SUBJECT",
        "should": "MATCH",
        "value": "value"
      }
    ],
    "conditions": [
      {
        "condition": "HAS_ATTACHMENTS",
        "value": "TRUE"
      }
    ]
  }
};

const result = await waitForController.waitForMatchingEmails(request);

Python SDK

Python SDK
import mailslurp_client
from mailslurp_client.api.wait_for_controller_api import WaitForControllerApi

configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"

with mailslurp_client.ApiClient(configuration) as api_client:
    waitForController = WaitForControllerApi(api_client)
    match_options = {
      "matches": [
        {
          "field": "SUBJECT",
          "should": "MATCH",
          "value": "value"
        }
      ],
      "conditions": [
        {
          "condition": "HAS_ATTACHMENTS",
          "value": "TRUE"
        }
      ]
    }
    result = waitForController.wait_for_matching_emails(inbox_id="00000000-0000-4000-8000-000000000000", count=NaN, before="value", match_options)

Wait for SMS

Use a provisioned phone number reserved for your test. Capture the time before the SMS action and pass the phone's ID, not its dialable number:

const phoneNumberId = process.env.MAILSLURP_PHONE_NUMBER_ID;
if (!phoneNumberId) throw new Error('Set MAILSLURP_PHONE_NUMBER_ID');
const smsSince = new Date();
// Trigger your application's SMS action here.
const sms = await mailslurp.waitController.waitForLatestSms({
  waitForSingleSmsOptions: {
    phoneNumberId,
    since: smsSince,
    timeout: 60_000,
    unreadOnly: true,
  },
});
assert.ok(sms.body);

For a shared notification stream, filter the body with waitForSms. This alternative assumes phoneNumberId and smsSince were set before sending:

import { SmsMatchOptionFieldEnum, SmsMatchOptionShouldEnum } from 'mailslurp-client';

const smsPreviews = await mailslurp.waitController.waitForSms({
  waitForSmsConditions: {
    phoneNumberId,
    since: smsSince,
    count: 1,
    timeout: 60_000,
    unreadOnly: true,
    matches: [{
      field: SmsMatchOptionFieldEnum.BODY,
      should: SmsMatchOptionShouldEnum.CONTAIN,
      value: 'Your verification code',
    }],
  },
});
assert.ok(smsPreviews.length > 0);
const matchedSms = await mailslurp.smsController.getSmsMessage({ smsId: smsPreviews[0].id });
assert.ok(matchedSms.body);

Add a FROM match when the sender is known. Keep concurrent tests on separate numbers when their messages cannot be reliably distinguished.

SMS waits use the same deterministic pattern as email waits. After a message arrives you can also extract verification codes with the SMS controller.

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?
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 time
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
Request example
{
  "phoneNumberId": "00000000-0000-4000-8000-000000000000",
  "count": 1,
  "timeout": 1,
  "limit": 1,
  "delayTimeout": 1,
  "unreadOnly": true
}

Responses

StatusSchemaDescription
200SmsPreview[]OK
HTTP and SDK snippets

HTTP

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

{
  "phoneNumberId": "00000000-0000-4000-8000-000000000000",
  "count": 1,
  "timeout": 1,
  "limit": 1,
  "delayTimeout": 1,
  "unreadOnly": true
}

cURL

cURL
curl -X POST "https://api.mailslurp.com/waitForSms" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data '{"phoneNumberId":"00000000-0000-4000-8000-000000000000","count":1,"timeout":1,"limit":1,"delayTimeout":1,"unreadOnly":true}'

JavaScript SDK

JavaScript SDK
import { Configuration, WaitForControllerApi } from "mailslurp-client";

const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const waitForController = new WaitForControllerApi(config);
const request = {
  "waitForSmsConditions": {
    "phoneNumberId": "00000000-0000-4000-8000-000000000000",
    "count": 1,
    "timeout": 1,
    "limit": 1,
    "delayTimeout": 1,
    "unreadOnly": true
  }
};

const result = await waitForController.waitForSms(request);

Python SDK

Python SDK
import mailslurp_client
from mailslurp_client.api.wait_for_controller_api import WaitForControllerApi

configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"

with mailslurp_client.ApiClient(configuration) as api_client:
    waitForController = WaitForControllerApi(api_client)
    wait_for_sms_conditions = {
      "phoneNumberId": "00000000-0000-4000-8000-000000000000",
      "count": 1,
      "timeout": 1,
      "limit": 1,
      "delayTimeout": 1,
      "unreadOnly": True
    }
    result = waitForController.wait_for_sms(wait_for_sms_conditions)
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 | DESCNo
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 and SDK snippets

HTTP

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

{
  "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"
}

cURL

cURL
curl -X POST "https://api.mailslurp.com/waitForLatestSms" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data '{"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"}'

JavaScript SDK

JavaScript SDK
import { Configuration, WaitForControllerApi } from "mailslurp-client";

const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const waitForController = new WaitForControllerApi(config);
const request = {
  "waitForSingleSmsOptions": {
    "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"
  }
};

const result = await waitForController.waitForLatestSms(request);

Python SDK

Python SDK
import mailslurp_client
from mailslurp_client.api.wait_for_controller_api import WaitForControllerApi

configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"

with mailslurp_client.ApiClient(configuration) as api_client:
    waitForController = WaitForControllerApi(api_client)
    wait_for_single_sms_options = {
      "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"
    }
    result = waitForController.wait_for_latest_sms(wait_for_single_sms_options)
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.
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 and SDK snippets

HTTP

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

{
  "method": "AUTO",
  "allowFallback": true,
  "minLength": 4,
  "maxLength": 10,
  "maxCandidates": 5,
  "customPatterns": [
    "value"
  ]
}

cURL

cURL
curl -X POST "https://api.mailslurp.com/sms/00000000-0000-4000-8000-000000000000/codes" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data '{"method":"AUTO","allowFallback":true,"minLength":4,"maxLength":10,"maxCandidates":5,"customPatterns":["value"]}'

JavaScript SDK

JavaScript SDK
import { Configuration, SmsControllerApi } from "mailslurp-client";

const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const smsController = new SmsControllerApi(config);
const request = {
  "smsId": "00000000-0000-4000-8000-000000000000",
  "extractCodesOptions": {
    "method": "AUTO",
    "allowFallback": true,
    "minLength": 4,
    "maxLength": 10,
    "maxCandidates": 5,
    "customPatterns": [
      "value"
    ]
  }
};

const result = await smsController.getSmsCodes(request);

Python SDK

Python SDK
import mailslurp_client
from mailslurp_client.api.sms_controller_api import SmsControllerApi

configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"

with mailslurp_client.ApiClient(configuration) as api_client:
    smsController = SmsControllerApi(api_client)
    extract_codes_options = {
      "method": "AUTO",
      "allowFallback": True,
      "minLength": 4,
      "maxLength": 10,
      "maxCandidates": 5,
      "customPatterns": [
        "value"
      ]
    }
    result = smsController.get_sms_codes("00000000-0000-4000-8000-000000000000", extract_codes_options)

Match email and SMS by meaning

An exact sender or order reference is a useful filter even when the template varies. Combine those known fields with a prompt such as "The account verification email for this signup" to select messages by purpose. Keep the prompt about identity; make business requirements separate assertions after selection.

Requirement Put it in
Only this test's inbox or reserved phone scope on an AI wait, or the legacy wait's inbox/phone ID
Only messages sent during this test since, captured before the application action
Known sender, recipient, or order reference Deterministic match options
The type or purpose of message match.prompt on AI waits, or ai.prompt on existing waits
A statement the selected message must make A named entry in assertions
The value the application needs next extractionPreset, outputSchema, or transformId

Add a prompt to an existing email wait

The HTTP examples in this section run inside a Playwright test with the request fixture, with test and expect imported from @playwright/test.

Existing matching endpoints accept optional AI matching without changing their response type. This Playwright request uses apiKey, inbox, and since from the setup above. It returns a full email, just like the original matching endpoint:

const baseUrl = (process.env.MAILSLURP_BASE_PATH ?? 'https://api.mailslurp.com').replace(/\/$/, '');
const response = await request.post(`${baseUrl}/waitForMatchingFirstEmail`, {
  headers: { 'x-api-key': apiKey },
  params: { inboxId: inbox.id, since: since.toISOString(), timeout: 60_000 },
  timeout: 75_000,
  data: {
    matches: [{ field: 'FROM', should: 'EQUAL', value: 'accounts@example.com' }],
    ai: {
      prompt: 'The account signup verification email containing a confirmation code',
      aiOptions: { model: 'BALANCED_V1', maxCandidates: 4, maxTokens: 8000 },
    },
  },
});
await expect(response).toBeOK();
const email = await response.json();
expect(email.id).toBeTruthy();
expect(email.body).toBeTruthy();

Replace the sender with the address your application uses. Both the sender filter and semantic match must pass. The same ai field is available in the MatchOptions body for /waitForMatchingEmails, and directly in the conditions body for /waitFor. Omit ai to retain deterministic matching with no AI usage.

For /waitForSms, put ai directly in its conditions body alongside phoneNumberId, since, count, and matches:

{
  "phoneNumberId": "YOUR_PHONE_NUMBER_ID",
  "since": "TEST_START_ISO_TIMESTAMP",
  "timeout": 60000,
  "count": 1,
  "countType": "ATLEAST",
  "matches": [{ "field": "FROM", "should": "EQUAL", "value": "+15551234567" }],
  "ai": { "prompt": "The one-time sign-in code for this login attempt" }
}

Replace the IDs, timestamp, and sender before sending. Legacy multi-message waits return preview arrays. They do not add the rich evaluation envelope shown below. AI count waits use ATLEAST; EXACTLY is rejected for AI requests because a matching decision cannot prove the absence of another delivery.

Wait, assert, and extract from the same message

Use the rich AI wait when a test needs a pass/fail result, source evidence, or structured data. This is an alternative to the previous request, using the same inbox and start time:

import { randomUUID } from 'node:crypto';

const response = await request.post(`${baseUrl}/ai/messages/wait`, {
  headers: { 'x-api-key': apiKey, 'Idempotency-Key': randomUUID() },
  timeout: 130_000,
  data: {
    scope: { inboxIds: [inbox.id] },
    since: since.toISOString(),
    timeout: 120_000,
    emailMatchOptions: {
      matches: [{ field: 'FROM', should: 'EQUAL', value: 'accounts@example.com' }],
    },
    match: { prompt: 'The account signup verification email' },
    assertions: [{ id: 'expiry', prompt: 'The message states that the code expires in ten minutes' }],
    extractionPreset: 'OTP_CODE',
    aiOptions: { model: 'BALANCED_V1', maxCandidates: 4, maxTokens: 8000 },
  },
});
await expect(response).toBeOK();
const result = await response.json();
expect(result.successful, result.summary).toBe(true);
expect(result.data?.code).toMatch(/^\d{6}$/);
expect(result.extractionEvidence['/code']?.length).toBeGreaterThan(0);

Set the expiry assertion to your application's requirement. A verification email that says "five minutes" is still the verification email: it should be selected and fail expiry. Putting "expires in ten minutes" into the match prompt could hide that defect by rejecting the message before the assertion runs.

For a dedicated inbox expecting exactly one message type, omit match and select using scope and deterministic filters. This avoids the semantic classification call; extraction or assertions still use AI. On rich requests, execution settings belong in the top-level aiOptions. Do not put another AI prompt inside emailMatchOptions or smsMatchOptions.

The AI guide explains OTP_CODE, custom schemas, direct extraction from a known message ID, result fields, model selection, and token usage. A complete browser example submits the extracted code and checks the resulting login.

Know when selection stops

AI waits use ascending chronological order by default. sortDirection: 'DESC' reverses the search order. Scope can contain up to 20 inbox IDs and 20 phone IDs; at least one source is required. A qualifying email or SMS from any listed source can satisfy a single wait.

  1. Find candidate messages in the scope and time window. since is required; before can close the window and must be later than since.
  2. Apply deterministic email or SMS filters before AI matching.
  3. Evaluate the identity prompt once per candidate. NO_MATCH continues the search; an uncertain identity returns INCONCLUSIVE instead of skipping ahead.
  4. Select the first qualifying message and run its assertions and extraction. A failure or ambiguity ends that expectation. The wait does not look for a later message that happens to pass.

A candidate rejected by the identity prompt is not repeatedly sent to the model on each polling cycle. maxCandidates bounds AI candidate evaluation, not the number of database polls. Narrow your scope or time window if a busy inbox exhausts that limit. The overall timeout includes waiting and AI work; the maximum request timeout is 120,000 milliseconds, subject to the API environment's configured limit.

Require both email and SMS delivery

Use named expectations when a purchase must produce an email receipt and an SMS confirmation. Sending both IDs in a single scope only requires one qualifying message. This request body for /ai/messages/wait-all requires both:

{
  "since": "TEST_START_ISO_TIMESTAMP",
  "timeout": 120000,
  "expectations": [
    {
      "id": "receipt",
      "scope": { "inboxIds": ["YOUR_INBOX_ID"] },
      "match": { "prompt": "The email receipt for order ORDER-1042" },
      "assertions": [{ "id": "amount", "prompt": "The order total is USD 49.00" }]
    },
    {
      "id": "confirmation",
      "scope": { "phoneNumberIds": ["YOUR_PHONE_NUMBER_ID"] },
      "match": { "prompt": "The SMS purchase confirmation for order ORDER-1042" }
    }
  ],
  "aiOptions": { "maxCandidates": 8, "maxTokens": 16000 }
}

Substitute your IDs, start time, order reference, and expected amount. Each expectation can include its own assertions and extraction preset or schema. Messages must be distinct by default; use allowSharedMessages: true only when one message is intentionally allowed to satisfy multiple expectations. The request supports up to ten expectations.

Assert the top-level successful flag with summary. The results array pairs each expectation id with its evaluation; unsatisfiedExpectationIds identifies incomplete or failed requirements. One shared timeout and token budget covers the entire request. Each child's usage reports the shared totals, so do not add them together.

Choose the controller and handle errors

/ai/messages/wait and /ai/messages/wait-all belong to the AI controller. /waitForAIMessage and /waitForAIMessages are aliases with the same request and rich response contracts. SDKs exposing the new operations use mailslurp.aiController; the aliases have separate wait-controller method names. Direct HTTP requests let you use these contracts independently of your installed SDK version.

Rich AI endpoints return completed evaluation outcomes in the response body. Check both HTTP success and result.successful:

Outcome What to do in the test
MATCHED, PASS, EXTRACTED All requested work succeeded. Validate the field format and continue the application flow.
FAIL Inspect failedAssertionIds and each assertion's reason; fix the application or the test's stated requirement.
INCONCLUSIVE Read summary, match.reason, or extractionReason. Identify missing or ambiguous content; do not accept null data.
TIMED_OUT No complete result before the deadline. Check scope, time filters, delivery, and any partial wait-all results.
CANDIDATE_LIMIT, TOKEN_LIMIT Narrow the search or deliberately increase the relevant limit.
PROVIDER_ERROR, PROVIDER_TIMEOUT Record the evaluation ID and usage state. Apply your suite's infrastructure-failure policy.
INVALID_OUTPUT The provider response failed schema or evidence checks. Treat it as a failed evaluation.

Authentication, ownership, invalid-input, and allowance errors can still be non-2xx HTTP responses. Legacy prompt-enabled waits retain their older contracts: a no-delivery timeout is HTTP 408, AI ambiguity/budget/invalid-output failures are HTTP 409 with evaluation details, and provider failures are HTTP 500. Do not parse a legacy full-email response as a rich AI result.

Retry an interrupted request without another evaluation

Send an Idempotency-Key for a logical AI evaluation. If transport delivery is interrupted, reuse the same key, request body, scope, and original since. For 15 minutes, an identical completed request can return its cached result; an in-progress or unresolved attempt returns HTTP 409 rather than starting duplicate work. A changed body with the same key also returns HTTP 409. The AI routes and their wait-controller aliases share this protection.

An idempotency key does not make the model deterministic, extend the deadline, or refresh a cached verdict. A new key starts new work and can consume more AI tokens. Do not change the key or loosen the prompt repeatedly until a test passes. Record evaluationId, status, and usage for diagnosis, and use a new key for a genuinely new test attempt with isolated messages.

Understand unread state and repeated reads

The rich AI wait, assertion, and extraction endpoints preserve message read state. Legacy prompt-enabled waits retain their original read behavior.

Messages start unread. Retrieving a full email through getEmail or a full-message wait marks it read; opening it in the dashboard can also change read state. Preview/list results are summaries, so do not use a returned preview as evidence that the full message has been consumed.

If a test needs the same message twice, retain the returned object or fetch it by ID. An unread-only wait is not an atomic work queue and should not coordinate multiple workers competing for one inbox.

For reused resources, combine a timestamp boundary and content filters. Capture since before the action; capturing it afterward can exclude a fast delivery. Synchronize the test runner's clock, or use resource isolation and unique message identifiers where clock differences make time filtering unreliable.

Timeouts, retries, and troubleshooting

All API wait durations shown here are in milliseconds. Budget for three separate limits:

API message wait:       60 seconds
HTTP request timeout:  greater than 60 seconds, allowing network overhead
Whole test timeout:    setup + application actions + waits + assertions + teardown

A longer whole-test timeout does not automatically extend the SDK's HTTP timeout. Configure the transport separately for your language; Java examples set the API client's read timeout, while HTTP tools have their own request timeout setting.

Symptom Check
Wait returns an old message Confirm inboxId or phoneNumberId, since, filters, and unread state.
Email exists but the wait times out Check whether it was read, whether every match condition is satisfied, and whether the time filter excludes it.
Request fails before the API timeout Check the HTTP client's timeout, proxy limits, runner timeout, and network connection.
Correct message arrives but extraction fails Inspect the full message and adapt the label, regex, selector, or schema to its actual contents.
Parallel tests are intermittent Check for shared inboxes/numbers, identical match values, and competing unread-only readers.
No message arrives Verify the submitted address, that the application sent it, and its sender logs; then check account/environment and resource expiry.

Wait methods already wait for delivery. Avoid wrapping every exception in a retry loop. Authentication errors, invalid filters, and failed assertions need a fix, while rate limits or transient network failures may justify a bounded retry with backoff. Preserve the same resource, start time, and match conditions across retries, honor Retry-After when supplied, and keep one overall deadline.

A negative assertion such as "no reset email is sent for this action" must distinguish an expected no-match timeout from a request failure. Record the observation window; a network error does not prove that no message arrived.

Info: Compare working examples. The Vitest wait-method project demonstrates latest, matching, and indexed email waits. The Java OTP match project demonstrates subject matching followed by API extraction and confirmation. Return to integration testing for cleanup and scenario-specific assertions.