Review AI extraction results with MailSlurp
Documentation navigation
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
- Human review: validate extraction quality, then tune transformer prompts/schemas.
- Automation: map extracted values into internal workflows (tickets, CRM updates, data syncs).
- 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.
Related pages
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.
/ai/transformer/results
Get transformer results
Get AI transformer results
Request, parameters, and responses
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
emailId | string:uuid | No | |
smsId | string:uuid | No | |
attachmentId | string | No | |
aiTransformId | string:uuid | No | |
aiTransformMappingId | string:uuid | No | |
entityId | string:uuid | No | |
entityType | enum: INBOX | PHONE | No | Values: INBOX, PHONE |
page | integer:int32 | No | |
size | integer:int32 | No | Optional page size. Maximum size is 100. Use page index and sort to page through larger results |
sort | enum: ASC | DESC | No | Optional createdAt sort direction ASC or DESCValues: ASC, DESC |
Responses
| Status | Schema | Description |
|---|---|---|
200 | PageAITransformResultProjection | OK |
HTTP examples
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.
(
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.
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.
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()
/ai/transformer/results/{id}
Get transformer result
Get AI transformer result
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string:uuid | Yes | ID of transform result |
Responses
| Status | Schema | Description |
|---|---|---|
200 | AITransformResultDto | OK |
HTTP examples
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.
(
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.
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.
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.
/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)
| Field | Type | Required | Description |
|---|---|---|---|
aiTransformId | string:uuid | No | |
aiMappingId | string:uuid | No | |
flattenArrays | boolean | Yes | |
since | string:date-time | No | |
before | string:date-time | No | |
format | enum: CSV | EXCEL | XML | JSON | Yes | Values: CSV, EXCEL, XML, JSON |
inboxId | string:uuid | No | |
phoneId | string:uuid | No | |
emailId | string:uuid | No | |
smsId | string:uuid | No | |
attachmentId | string | No | |
includeMetaData | boolean | Yes | |
delimiter | string | No | |
arraySeparator | string | No | |
includeMetaDataColumns | string[] | No |
{
"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
| Status | Schema | Description |
|---|---|---|
200 | ExportTransformerResponse | OK |
HTTP examples
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.
(
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.
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.
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()
/ai/transformer/results/export/{id}
Get export transformer results job
Get the job status for an export
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string:uuid | Yes |
Responses
| Status | Schema | Description |
|---|---|---|
200 | ExportTransformerResultJobDto | OK |
HTTP examples
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.
(
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.
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.
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.
/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
| Name | Type | Required | Description |
|---|---|---|---|
transformerId | string:uuid | Yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Public 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. |
basicAuth | BasicAuthOptions | No | |
name | string | No | Optional name for the webhook |
eventName | enum: 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 | No | Optional 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 |
includeHeaders | WebhookHeaders | No | |
requestBodyTemplate | string | No | Template 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. |
aiTransformId | string:uuid | No | AI Transform ID to apply to the webhook event and send a payload matching transform output schema |
useStaticIpRange | boolean | No | Use static IP range when calling webhook endpoint |
ignoreInsecureSslCertificates | boolean | No | Ignore insecure SSL certificates when sending request. Useful for self-signed certs. |
tags | string[] | No | Optional list of tags |
{
"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
| Status | Schema | Description |
|---|---|---|
201 | WebhookDto | Created |
HTTP examples
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.
(
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.
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.
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.
Running this code needs an API key. Get one free