MailSlurp logo

Review AI extraction results with MailSlurp

Documentation navigation
Search documentation

How to manage AI extracted data and connect your systems

AI Transformers can convert emails, files, and TXT messages into tables of structured data. You can view this data in the MailSlurp app or connect it to your systems using webhooks or 3rd-party integrations with tools like Notion, Excel, and Google Sheets.

Where to review results

  • Dashboard: inspect and validate extracted fields in the AI area.
  • API-driven flows: fetch and process records in your own services.
  • Event-driven flows: forward processed output to downstream systems using webhooks.

Result consumption patterns

  1. Human review: validate extraction quality, then tune transformer prompts/schemas.
  2. Automation: map extracted values into internal workflows (tickets, CRM updates, data syncs).
  3. Testing assertions: validate that expected values were extracted from realistic email/SMS fixtures.

Data quality checklist

  • Validate critical fields before triggering side effects.
  • Track extraction misses and update schema/prompt definitions.
  • Keep destination integrations idempotent to avoid duplicate writes.

Fetch stored results from the API

Invoke a transform with saveResults: true, or use an automatic mapping, then filter results by aiTransformId, mapping ID or source message. Use the REST helper from the extraction example:

async function getAllTransformerResults(api, transformId) {
  const results = [];

  for (let page = 0; ; page += 1) {
    const query = new URLSearchParams({
      aiTransformId: transformId,
      page: String(page),
      size: '100',
      sort: 'DESC',
    });
    const batch = await api(`/ai/transformer/results?${query}`);
    results.push(...(batch.content ?? []));

    if (batch.last || page + 1 >= batch.totalPages) return results;
  }
}

The function reads every page for a transform. For a large or continuously changing dataset, consume pages incrementally rather than collecting them all in memory. Fetch an individual result when you need its full value and source IDs.

GET /ai/transformer/results

Get transformer results

Get AI transformer results

Request, parameters, and responses

Query parameters

NameTypeRequiredDescription
emailIdstring:uuidNo
smsIdstring:uuidNo
attachmentIdstringNo
aiTransformIdstring:uuidNo
aiTransformMappingIdstring:uuidNo
entityIdstring:uuidNo
entityTypeenum: INBOX | PHONENoValues: INBOX, PHONE
pageinteger:int32No
sizeinteger:int32NoOptional page size. 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

Responses

StatusSchemaDescription
200PageAITransformResultProjectionOK
HTTP examples

HTTP

HTTP
GET /ai/transformer/results 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/ai/transformer/results' \
  --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/ai/transformer/results", {
  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/ai/transformer/results")
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 /ai/transformer/results/{id}

Get transformer result

Get AI transformer result

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
idstring:uuidYesID of transform result

Responses

StatusSchemaDescription
200AITransformResultDtoOK
HTTP examples

HTTP

HTTP
GET /ai/transformer/results/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/ai/transformer/results/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/ai/transformer/results/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/ai/transformer/results/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()

Export a snapshot

Create an export with a format, transform or mapping filter and optional time bounds. Record the returned jobId, poll the export job with a bounded deadline, and retrieve its output after success. Treat ERROR as a failed export and inspect the returned diagnostic fields.

POST /ai/transformer/results/export

Export transformer results

Export AI transformer results in formats such as Excel, CSV, XML etc.

Request, parameters, and responses

Request body (required)

ExportTransformerOptions application/json
FieldTypeRequiredDescription
aiTransformIdstring:uuidNo
aiMappingIdstring:uuidNo
flattenArraysbooleanYes
sincestring:date-timeNo
beforestring:date-timeNo
formatenum: CSV | EXCEL | XML | JSONYesValues: CSV, EXCEL, XML, JSON
inboxIdstring:uuidNo
phoneIdstring:uuidNo
emailIdstring:uuidNo
smsIdstring:uuidNo
attachmentIdstringNo
includeMetaDatabooleanYes
delimiterstringNo
arraySeparatorstringNo
includeMetaDataColumnsstring[]No
Request example
{
  "flattenArrays": true,
  "format": "CSV",
  "includeMetaData": true,
  "aiTransformId": "00000000-0000-4000-8000-000000000000",
  "aiMappingId": "00000000-0000-4000-8000-000000000000",
  "since": "2026-06-21T00:00:00.000Z"
}

Responses

StatusSchemaDescription
200ExportTransformerResponseOK
HTTP examples

HTTP

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

{
  "flattenArrays": false,
  "format": "CSV",
  "includeMetaData": false
}

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  "flattenArrays": false,\0012  "format": "CSV",\0012  "includeMetaData": false\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/ai/transformer/results/export' \
  --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  \"flattenArrays\": false,\n  \"format\": \"CSV\",\n  \"includeMetaData\": false\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/ai/transformer/results/export", {
  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/ai/transformer/results/export")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"flattenArrays\": false,\n  \"format\": \"CSV\",\n  \"includeMetaData\": false\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 /ai/transformer/results/export/{id}

Get export transformer results job

Get the job status for an export

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
idstring:uuidYes

Responses

StatusSchemaDescription
200ExportTransformerResultJobDtoOK
HTTP examples

HTTP

HTTP
POST /ai/transformer/results/export/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 POST \
  --url 'https://api.mailslurp.com/ai/transformer/results/export/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/ai/transformer/results/export/00000000-0000-4000-8000-000000000000", {
  method: "POST",
  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/ai/transformer/results/export/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("POST", 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()

Choose whether to include metadata and flatten arrays according to the destination. Keep source references when reviewers need to trace a row back to an email, SMS or attachment.

Receive a result webhook

Attach a webhook to the transformer for NEW_AI_TRANSFORM_RESULT. Verify the webhook signature, durably queue the event and deduplicate its messageId. Fetch the stored result and validate its fields before updating your destination.

POST /ai-transformers/{transformerId}/webhooks

Attach a WebHook URL to an AI transformer

Get notified whenever AI transformation pipeline converts and email or SMS into structured data via a WebHook URL.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
transformerIdstring:uuidYes

Request body (required)

CreateWebhookOptions application/json
FieldTypeRequiredDescription
urlstringYesPublic URL on your server that MailSlurp can post WebhookNotification payload to when an email is received or an event is trigger. The payload of the submitted JSON is dependent on the webhook event type. See /docs/webhooks for event payload documentation.
basicAuthBasicAuthOptionsNo
namestringNoOptional name for the webhook
eventNameenum: EMAIL_RECEIVED | NEW_AI_TRANSFORM_RESULT | NEW_EMAIL | NEW_CONTACT | NEW_ATTACHMENT | EMAIL_OPENED | EMAIL_READ | DELIVERY_STATUS | BOUNCE | BOUNCE_RECIPIENT | NEW_SMS | NEW_GUEST_USERNoOptional webhook event name. Default is `EMAIL_RECEIVED` and is triggered when an email is received by the inbox associated with the webhook. Payload differ according to the webhook event name.Values: EMAIL_RECEIVED, NEW_AI_TRANSFORM_RESULT, NEW_EMAIL, NEW_CONTACT, NEW_ATTACHMENT, EMAIL_OPENED, EMAIL_READ, DELIVERY_STATUS, BOUNCE, BOUNCE_RECIPIENT, NEW_SMS, NEW_GUEST_USER
includeHeadersWebhookHeadersNo
requestBodyTemplatestringNoTemplate for the JSON body of the webhook request that will be sent to your server. Use Moustache style `{{variableName}}` templating to use parts of the standard webhook payload for the given event.
aiTransformIdstring:uuidNoAI Transform ID to apply to the webhook event and send a payload matching transform output schema
useStaticIpRangebooleanNoUse static IP range when calling webhook endpoint
ignoreInsecureSslCertificatesbooleanNoIgnore insecure SSL certificates when sending request. Useful for self-signed certs.
tagsstring[]NoOptional list of tags
Request example
{
  "url": "https://example.com/webhook",
  "basicAuth": {
    "username": "Example name",
    "password": "value"
  },
  "name": "Example name",
  "eventName": "EMAIL_RECEIVED",
  "includeHeaders": {
    "headers": [
      {
        "name": "Example name",
        "value": "value"
      }
    ]
  },
  "requestBodyTemplate": "Hello from MailSlurp"
}

Responses

StatusSchemaDescription
201WebhookDtoCreated
HTTP examples

HTTP

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

{
  "url": "https://example.com/",
  "useStaticIpRange": false
}

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  "url": "https://example.com/",\0012  "useStaticIpRange": false\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/ai-transformers/00000000-0000-4000-8000-000000000000/webhooks' \
  --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  \"url\": \"https://example.com/\",\n  \"useStaticIpRange\": false\n}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/ai-transformers/00000000-0000-4000-8000-000000000000/webhooks", {
  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/ai-transformers/00000000-0000-4000-8000-000000000000/webhooks")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{\n  \"url\": \"https://example.com/\",\n  \"useStaticIpRange\": false\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 the result ID for repeated delivery of the same result. If replaying the same source must update an existing business record, also define a destination key such as invoice number plus supplier; a new extraction result ID does not mean a new invoice. See webhook recovery for retry and redrive behavior.