Define AI transformer pipelines
Documentation navigation
Configure inbox and phone number inputs, define prompts and schemas, export results
Use MailSlurp's AI Transformers to turn emails, attachments, and SMS into structured data.
- Define the data you want to extract using prompts or JSON schemas.
- Connect inboxes and phone numbers
- View results in tables or connect webhooks, databases, or external apps directly
Transformer design workflow
- Define target fields in output schemas.
- Write a transformer prompt that reflects source message structure.
- Connect input sources (inboxes, attachments, SMS feeds).
- Run controlled test samples and inspect extraction output.
- Promote to production integrations once quality is stable.
Prompt and schema tips
- Use explicit field descriptions (for example, expected format and units).
- Keep required fields minimal at first to reduce false negatives.
- Add examples for ambiguous values (invoice numbers, order IDs, phone formats).
- Prefer stable vocabulary for enum-like fields.
Operating guidance
- Maintain a versioned change log for transformer updates.
- Re-test with representative historical messages after each prompt/schema change.
- Link each transformer to an owner and downstream consumer for faster incident triage.
Continue with
Create a reusable invoice transformer
Use the REST helper and invoiceSchema from PDF invoice extraction. The example creates a named definition you can invoke repeatedly:
async function createInvoiceTransformer(api) {
return api('/ai/transformer', {
method: 'POST',
body: {
name: 'Invoice extraction v1',
instructions: [
'Extract only values present in the invoice.',
'Preserve invoice and purchase order identifiers as text.',
'Do not infer missing financial values.',
],
outputSchema: invoiceSchema,
},
});
}
Call createInvoiceTransformer(api) and retain its id. Test the definition against representative messages before attaching it to automatic intake. Use a new named version when you need to compare changed instructions with the old definition.
/ai/transformer
Create a transformer for reuse in automations
Save an AI transform instructions and schema for use with webhooks and automations
Request, parameters, and responses
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | |
conditions | string[] | No | |
instructions | string[] | No | |
outputSchema | StructuredOutputSchema | No | |
outputSchemaId | string:uuid | No | |
extractPrompt | string | No |
{
"name": "Example name",
"conditions": [
"value"
],
"instructions": [
"value"
],
"outputSchema": {
"anyOf": [
{
"anyOf": [
"value"
],
"default": {},
"description": "value",
"enumValues": [
"value"
],
"example": {},
"format": "2026-06-21T00:00:00.000Z"
}
],
"default": {},
"description": "value",
"enumValues": [
"value"
],
"example": {},
"format": "2026-06-21T00:00:00.000Z"
},
"outputSchemaId": "00000000-0000-4000-8000-000000000000",
"extractPrompt": "value"
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | AITransformDto | OK |
HTTP examples
HTTP
POST /ai/transformer HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
{}
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' '{}'
} > "$request_body"
status=$(curl --silent --show-error \
--request POST \
--url 'https://api.mailslurp.com/ai/transformer' \
--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("{}", "utf8")
]);
const response = await fetch("https://api.mailslurp.com/ai/transformer", {
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")
headers = {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
"Accept": "*/*"
}
body = b"".join([
"{}".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()
Attach the transformer to incoming messages
A mapping connects a transform to an inbox or phone. Always provide the intended entityId for a narrowly scoped workflow. This example processes each message in a dedicated invoice inbox, including its body and attachments:
async function mapInvoiceInbox(api, transformId, inboxId) {
return api('/ai/transformer/mappings', {
method: 'POST',
body: {
name: 'Invoice inbox mapping',
aiTransformId: transformId,
entityType: 'INBOX',
entityId: inboxId,
contentSelector: 'BODY_ATTACHMENTS',
triggerSelector: 'PER_MESSAGE',
spreadRootArray: false,
},
});
}
Call mapInvoiceInbox(api, transform.id, inbox.id) after validating the transform. Choose PER_ATTACHMENT when each attachment should produce its own invocation; use PER_MESSAGE when a message and its attachments form one input. BODY_ATTACHMENTS includes file content, while BODY limits the source to the message body and RAW selects the raw message representation.
/ai/transformer/mappings
Create transformer mapping
Create AI transformer mappings to other entities
Request, parameters, and responses
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | |
aiTransformId | string:uuid | Yes | |
entityId | string:uuid | No | |
entityType | enum: INBOX | PHONE | Yes | Values: INBOX, PHONE |
contentSelector | enum: RAW | BODY | BODY_ATTACHMENTS | No | Values: RAW, BODY, BODY_ATTACHMENTS |
triggerSelector | enum: PER_MESSAGE | PER_ATTACHMENT | No | Values: PER_MESSAGE, PER_ATTACHMENT |
spreadRootArray | boolean | No | |
matchOptions | AIMappingMatchOptions | No |
{
"aiTransformId": "00000000-0000-4000-8000-000000000000",
"entityType": "INBOX",
"name": "Example name",
"entityId": "00000000-0000-4000-8000-000000000000",
"contentSelector": "RAW",
"triggerSelector": "PER_MESSAGE"
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | AITransformMappingDto | OK |
HTTP examples
HTTP
POST /ai/transformer/mappings HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
{
"aiTransformId": "00000000-0000-4000-8000-000000000000",
"entityType": "INBOX"
}
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 "aiTransformId": "00000000-0000-4000-8000-000000000000",\0012 "entityType": "INBOX"\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
--request POST \
--url 'https://api.mailslurp.com/ai/transformer/mappings' \
--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 \"aiTransformId\": \"00000000-0000-4000-8000-000000000000\",\n \"entityType\": \"INBOX\"\n}", "utf8")
]);
const response = await fetch("https://api.mailslurp.com/ai/transformer/mappings", {
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/mappings")
headers = {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
"Accept": "*/*"
}
body = b"".join([
"{\n \"aiTransformId\": \"00000000-0000-4000-8000-000000000000\",\n \"entityType\": \"INBOX\"\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/mappings/{id}/match
Test transformer mapping match result
Evaluate transform mapping match conditions for given email, sms, or attachment
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string:uuid | Yes | ID of transform mapping |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
emailId | string:uuid | No | |
smsId | string:uuid | No | |
attachmentId | string | No |
Responses
| Status | Schema | Description |
|---|---|---|
200 | AITransformMappingMatchResult | OK |
HTTP examples
HTTP
POST /ai/transformer/mappings/00000000-0000-4000-8000-000000000000/match 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/mappings/00000000-0000-4000-8000-000000000000/match' \
--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/mappings/00000000-0000-4000-8000-000000000000/match", {
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/mappings/00000000-0000-4000-8000-000000000000/match")
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()
Add matchOptions when only particular incoming messages should qualify. Test the mapping against examples that should match and examples that should be excluded. A dedicated intake inbox is a simple starting point when every message is relevant.
To stop automatic processing, delete the mapping while retaining the transform and existing results. Deleting or changing a mapping does not undo writes already made to your own systems.
/ai/transformer/mappings/{id}
Delete transformer mapping
Delete an AI transformer mapping
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string:uuid | Yes | ID of transform mapping |
Responses
| Status | Schema | Description |
|---|---|---|
204 | Response | No Content |
HTTP examples
HTTP
DELETE /ai/transformer/mappings/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 DELETE \
--url 'https://api.mailslurp.com/ai/transformer/mappings/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/mappings/00000000-0000-4000-8000-000000000000", {
method: "DELETE",
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/mappings/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("DELETE", 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()
Continue with invocation and replay and stored results, exports and webhooks.
Running this code needs an API key. Get one free