MailSlurp logo

Define AI transformer pipelines

Documentation navigation
Search documentation

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

ai pipeline flow

Transformer design workflow

  1. Define target fields in output schemas.
  2. Write a transformer prompt that reflects source message structure.
  3. Connect input sources (inboxes, attachments, SMS feeds).
  4. Run controlled test samples and inspect extraction output.
  5. 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.

POST /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)

AITransformCreateOptions application/json
FieldTypeRequiredDescription
namestringNo
conditionsstring[]No
instructionsstring[]No
outputSchemaStructuredOutputSchemaNo
outputSchemaIdstring:uuidNo
extractPromptstringNo
Request example
{
  "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

StatusSchemaDescription
200AITransformDtoOK
HTTP examples

HTTP

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.

cURL
(
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.

JavaScript (HTTP)
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.

Python (HTTP)
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.

POST /ai/transformer/mappings

Create transformer mapping

Create AI transformer mappings to other entities

Request, parameters, and responses

Request body (required)

FieldTypeRequiredDescription
namestringNo
aiTransformIdstring:uuidYes
entityIdstring:uuidNo
entityTypeenum: INBOX | PHONEYesValues: INBOX, PHONE
contentSelectorenum: RAW | BODY | BODY_ATTACHMENTSNoValues: RAW, BODY, BODY_ATTACHMENTS
triggerSelectorenum: PER_MESSAGE | PER_ATTACHMENTNoValues: PER_MESSAGE, PER_ATTACHMENT
spreadRootArraybooleanNo
matchOptionsAIMappingMatchOptionsNo
Request example
{
  "aiTransformId": "00000000-0000-4000-8000-000000000000",
  "entityType": "INBOX",
  "name": "Example name",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "contentSelector": "RAW",
  "triggerSelector": "PER_MESSAGE"
}

Responses

StatusSchemaDescription
200AITransformMappingDtoOK
HTTP examples

HTTP

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.

cURL
(
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.

JavaScript (HTTP)
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.

Python (HTTP)
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()
POST /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

NameTypeRequiredDescription
idstring:uuidYesID of transform mapping

Query parameters

NameTypeRequiredDescription
emailIdstring:uuidNo
smsIdstring:uuidNo
attachmentIdstringNo

Responses

StatusSchemaDescription
200AITransformMappingMatchResultOK
HTTP examples

HTTP

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.

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/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.

JavaScript (HTTP)
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.

Python (HTTP)
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.

DELETE /ai/transformer/mappings/{id}

Delete transformer mapping

Delete an AI transformer mapping

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
idstring:uuidYesID of transform mapping

Responses

StatusSchemaDescription
204ResponseNo Content
HTTP examples

HTTP

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.

cURL
(
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.

JavaScript (HTTP)
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.

Python (HTTP)
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.