MailSlurp logo

AI matching, assertions, and extraction for email and SMS

Documentation navigation
Search documentation

Find email and SMS by meaning, assert message content, extract OTP codes and structured data, and build tests with source evidence and explicit failure handling.

View MarkdownAgent setup

Find the verification email even when its wording changes. Extract its one-time code as a string, enter it in your application, and check that signup succeeds. MailSlurp's AI endpoints combine message selection, named assertions, and structured extraction for email and SMS tests.

You can also apply reusable AI transformers to messages and attachments, or connect inboxes and phone numbers to pipelines that process incoming messages automatically.

Choose the operation your test needs

Task HTTP operation Main result
Wait for a message, then check or extract from it POST /ai/messages/wait Selected message, assertions, data, and diagnostics
Require several named deliveries across inboxes and phones POST /ai/messages/wait-all One result per expectation and an overall success flag
Check the meaning of a message you already have POST /ai/messages/assert Named assertion verdicts with reasons and evidence
Extract fields from a message you already have POST /ai/messages/extract Schema-validated data with source evidence
Discover available models GET /ai/models Model IDs, evaluator version, and token multipliers

These operations belong to mailslurp.aiController in SDKs that expose them. The examples here call the HTTP endpoints directly with Playwright's built-in request fixture, so they do not depend on a particular SDK release. Requests run in the test process; keep the API key out of browser-page JavaScript.

Use exact sender, recipient, or reference filters when you know them. Add a semantic prompt when the message's purpose is stable but its wording varies. For selection details, legacy prompt-enabled waits, and wait-controller aliases, read the wait-for guide.

Complete a signup with the delivered code

Install Playwright in your test project and its Chromium browser:

npm install --save-dev @playwright/test
npx playwright install chromium

Set MAILSLURP_API_KEY and APP_URL. The example uses the MailSlurp playground's signup selectors; replace the selectors when testing your own application. MAILSLURP_BASE_PATH defaults to https://api.mailslurp.com and lets you choose a different API environment.

Save this as tests/email-otp.spec.ts and run npx playwright test tests/email-otp.spec.ts:

import { test, expect } from '@playwright/test';
import { randomUUID } from 'node:crypto';

test('verify signup using the delivered email code', async ({ page, request }) => {
  test.setTimeout(180_000);
  const apiKey = process.env.MAILSLURP_API_KEY;
  const appUrl = process.env.APP_URL;
  if (!apiKey || !appUrl) throw new Error('Set MAILSLURP_API_KEY and APP_URL');
  const baseUrl = (process.env.MAILSLURP_BASE_PATH ?? 'https://api.mailslurp.com').replace(/\/$/, '');
  const headers = { 'x-api-key': apiKey };
  const password = 'Test-password-42!';

  const created = await request.post(`${baseUrl}/inboxes`, {
    headers, params: { expiresIn: 600_000 },
  });
  await expect(created).toBeOK();
  const inbox = await created.json();
  try {
    await page.goto(appUrl);
    await page.locator('[data-test="sign-in-create-account-link"]').click();
    await page.locator('input[name=email]').fill(inbox.emailAddress);
    await page.locator('input[name=password]').fill(password);
    const since = new Date().toISOString();
    await page.locator('[data-test="sign-up-create-account-button"]').click();

    const response = await request.post(`${baseUrl}/ai/messages/wait`, {
      headers: { ...headers, 'Idempotency-Key': randomUUID() },
      timeout: 130_000,
      data: {
        scope: { inboxIds: [inbox.id] }, since, timeout: 120_000,
        match: { prompt: 'The account signup verification email containing a confirmation code' },
        extractionPreset: 'OTP_CODE',
      },
    });
    await expect(response).toBeOK();
    const result = await response.json();
    expect(result.successful, `${result.summary} (evaluation ${result.evaluationId})`).toBe(true);
    expect(result.data?.code).toMatch(/^\d{6}$/);

    await page.locator('[data-test="confirm-sign-up-confirmation-code-input"]').fill(result.data.code);
    await page.locator('[data-test="confirm-sign-up-confirm-button"]').click();
    await page.locator('[data-test="username-input"]').fill(inbox.emailAddress);
    await page.locator('[data-test="sign-in-password-input"]').fill(password);
    await page.locator('[data-test="sign-in-sign-in-button"]').click();
    await expect(page.locator('[data-test="greetings-nav"]')).toBeVisible();
  } finally {
    const deleted = await request.delete(`${baseUrl}/inboxes/${inbox.id}`, { headers });
    await expect(deleted).toBeOK();
  }
});

The fresh inbox isolates the test, and since is captured before signup can send an email. OTP_CODE returns the code in data.code; the six-digit assertion checks this application's format. The final browser assertion verifies that the application accepted the delivered code.

Use the Playwright guide for runner setup and the result checks below for source evidence and token usage. Retain a browser trace on failure. Keep test-runner retries disabled while diagnosing AI outcomes; do not automatically repeat an inconclusive evaluation until it passes.

Extract a code from an email or SMS you already have

If another wait has already returned the right message, call /ai/messages/extract directly. Send this JSON body after replacing the message ID:

{
  "message": { "type": "SMS", "id": "YOUR_SMS_ID" },
  "extractionPreset": "OTP_CODE",
  "timeout": 60000
}

Use type: "EMAIL" with an email ID. For an SMS wait, use scope: { phoneNumberIds: [phone.id] } on /ai/messages/wait; the response and extraction preset work the same way. The message reference identifies the originating type and ID, and the result includes the corresponding email or sms payload.

OTP_CODE supports numeric and alphanumeric codes of 1 to 128 characters and preserves leading zeros, letter case, and separators. It asks for the current verification or authentication code, excluding support references, order numbers, and explicitly expired or superseded codes. If two codes are equally eligible, it abstains rather than choosing one.

A missing or ambiguous code returns status: "INCONCLUSIVE", successful: false, and data: null. extractionReason explains the problem and is included in summary. For example, an explanation may identify two eligible codes with no indication of which is current. Model explanations are labeled model-reported; use them for diagnosis, not as exact strings to assert against.

Check named requirements on the selected message

Assertions let a test ask whether the message communicates a business requirement even when phrasing varies. For a known message, send a body like this to /ai/messages/assert:

{
  "message": { "type": "EMAIL", "id": "YOUR_EMAIL_ID" },
  "assertions": [
    { "id": "order", "prompt": "This is a receipt for order ORDER-1042" },
    { "id": "total", "prompt": "The order total is USD 49.00" },
    { "id": "delivery", "prompt": "Delivery is confirmed for Friday, not merely estimated" }
  ],
  "timeout": 60000
}

Replace the reference and requirements with your test's expected values. Each assertion returns PASS, FAIL, or INCONCLUSIVE, a reason, source excerpts, and model-reported confidence. An absent required fact can fail an assertion; ambiguous or insufficient content can be inconclusive. Overall successful must be true before the test proceeds. failedAssertionIds identifies the named failures.

The same assertions array can be added to a wait alongside extraction. Select "the order receipt" first, then check its amount and delivery statement. If an assertion fails, the wait returns that selected message and does not search for another receipt with a more convenient answer.

Choose exactly one extraction selector: extractionPreset, outputSchema, or transformId. A custom schema can require field names, types, patterns, enums, and nested objects or arrays. This body for /ai/messages/extract requests a reset URL and expiry from an email:

{
  "message": { "type": "EMAIL", "id": "YOUR_EMAIL_ID" },
  "outputSchema": {
    "type": "OBJECT",
    "required": ["resetUrl", "expiryText"],
    "properties": {
      "resetUrl": {
        "type": "STRING",
        "description": "The exact password reset URL intended for the recipient"
      },
      "expiryText": {
        "type": "STRING",
        "description": "The exact phrase stating when that reset link expires"
      }
    }
  },
  "timeout": 60000
}

Required missing or ambiguous values make extraction inconclusive. Strings need to appear verbatim in the source, so extract expiryText before converting it into a duration in your own code. After a successful result, validate a URL's origin and path against your test application's expected destination before opening it. For invoices, define an invoice ID string, currency, numeric total, and line-item array, then compare those values to the order your test created.

Use transformId to reuse an owned transform's output schema and extraction instructions. On message-testing endpoints, express selection explicitly in match and checks in assertions; a transform with selection conditions is rejected. See output schemas and AI transformers for reusable definitions.

Read the result before accepting its data

Field How the test uses it
successful The primary assertion: true only when all requested work succeeded.
summary Human-readable context for a failed assertion.
status Distinguishes business failure, ambiguity, timeout, limits, and provider errors.
evaluationId Correlates the evaluation with your test report.
message, email, sms Identify and inspect the selected source message.
assertions, failedAssertionIds Find a failed named requirement and its evidence.
data The extracted JSON value; null when extraction is inconclusive or unavailable.
extractionEvidence Exact source excerpts indexed by the extracted field's JSON pointer.
extractionReason Diagnostic explanation for inconclusive extraction.
model, usage, elapsedMs Record model choice, allowance use, and duration.

Evidence paths are relative to data: the code is /code, not /data/code. A nested amount might be /items/0/total. MailSlurp validates the output schema and checks the cited excerpts against the message. Unsupported extracted fields are withheld; fabricated excerpts or invalid output can return INVALID_OUTPUT. Evidence helps explain and validate extraction, while interpretation of the message's meaning still relies on the model.

Add these checks when your test needs source and allowance diagnostics:

expect(result.extractionEvidence['/code']?.length).toBeGreaterThan(0);
expect(result.usage.complete).toBe(true);
expect(result.usage.reservedTokens).toBe(0);

A 2xx response can contain FAIL, INCONCLUSIVE, TIMED_OUT, or an operational failure. Assert successful before using data, even when an individual assertion passed. The wait-for outcome table covers each status and the older matching endpoints' HTTP error behavior.

Choose a model and bound AI usage

The first example uses the default model and limits. Add aiOptions to a request when you need explicit controls:

{
  "aiOptions": {
    "model": "BALANCED_V1",
    "maxTokens": 8000,
    "maxCandidates": 4,
    "maxOutputTokens": 2048
  }
}

This is a fragment to add to an extraction, assertion, or wait request. maxTokens is the total budget of weighted MailSlurp AI tokens, shared across candidate selection and checks. maxOutputTokens bounds an individual generated response. maxCandidates limits the messages considered for AI evaluation in a wait. A narrower scope and exact filters reduce unnecessary classification calls.

Call GET /ai/models in your API environment to inspect enabled model IDs, provider model names, evaluator versions, moving-alias flags, and token multipliers. BALANCED_V1 is the default. FLASH_LATEST selects a moving alias; use a versioned model option when you want a more stable choice. Model enum values can exist without being enabled in your environment. A provider failure does not silently switch the request to another model.

usage.tokens reports weighted MailSlurp AI tokens, not raw provider tokens. Rejected candidates and generated responses that fail validation can still consume tokens. When provider usage remains unresolved after a timeout or transport problem, usage.complete can be false and reservedTokens can remain held pending settlement or reconciliation. An unsuccessful evaluation does not imply zero usage.

Treat confidence as a reason to abstain

Assertion and match confidence is MODEL_REPORTED with calibrated: false. It expresses confidence in the chosen verdict, including a failing verdict. A score of 0.95 does not mean 95% verified accuracy.

The optional aiOptions.minimumConfidence causes match or assertion decisions below the threshold to become INCONCLUSIVE; their reasons identify the threshold. It does not trigger another evaluation. Extraction instead relies on its structured result, ambiguity decision, and source-evidence checks. Use the application's acceptance of the code or link, expected values, and explicit failure handling as the test's final evidence.

Keep tests scoped and failures visible

  • Capture since before the action and isolate inboxes or reserve phone numbers for each test worker.
  • Describe message identity separately from the requirements being asserted.
  • Preserve codes as strings and validate the application's required format.
  • Use an Idempotency-Key for interrupted-request retries, reusing the original request body and start time. See retry behavior.
  • Record evaluation IDs and relevant diagnostics. Keep message bodies and authentication codes out of shared logs unless your test-data policy allows them.
  • Complete the action in your application. Extraction alone does not prove that a reset link or OTP works.

Process attachments and recurring message streams

For a test working with the body of an email or SMS, use the message-testing endpoints above. For fields inside a PDF, image, or other attachment, use the dedicated AI invocation workflow with the attachment ID and an output schema. Keep file-content checks separate from assertions about the email's text.

AI transformers provide reusable extraction definitions. Invoke a transformer on a particular email, SMS, or attachment, or connect a pipeline to an inbox or phone number to process incoming messages automatically. Review the structured results and use webhooks or integrations to pass them to your application.

AI transformation from message input to structured data

Start with defining transformers, schema definitions, invoking AI, and viewing results.

AI transformer pipeline for incoming messages