Extract invoice fields from PDF attachments
Documentation navigation
Receive a PDF invoice, extract typed fields and validate them before writing to your accounting system.
Use attachment extraction when the invoice fields are inside a PDF or image rather than in the email body. Keep the source email and attachment IDs with the extracted values so a reviewer can inspect the document behind a record.
Receive and select the attachment
Create an inbox or connect your existing mailbox. Send a representative invoice to it and wait for the matching email. List the email's attachments and choose the invoice by its metadata; do not assume the first attachment is the invoice because signatures can contain inline images.
See attachments for listing, downloading and inspecting files. Use the returned attachment ID, not its filename, in extraction requests.
Define the fields
This schema keeps the invoice number as text so leading zeros survive. The total is numeric and its currency is a separate required field. Add line items or tax only when the downstream workflow needs them.
const invoiceSchema = {
type: 'object',
required: ['invoiceNumber', 'supplier', 'currency', 'total'],
properties: {
invoiceNumber: {
type: 'string',
description: 'Invoice identifier exactly as printed, including leading zeros',
},
supplier: {
type: 'string',
description: 'Supplier or legal entity issuing the invoice',
},
purchaseOrderNumber: {
type: 'string',
nullable: true,
description: 'Purchase order identifier when one appears on the invoice',
},
currency: {
type: 'string',
pattern: '^[A-Z]{3}$',
description: 'Three-letter ISO currency code',
},
total: {
type: 'number',
minimum: 0,
description: 'Final invoice total in the stated currency',
},
},
};
Extract and validate
Use Node.js 22+ and set MAILSLURP_API_KEY on your server. The helper checks HTTP failures; the extraction function also checks the returned business fields.
const API_BASE_URL = 'https://api.mailslurp.com';
function createApi(apiKey) {
if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
return async function api(path, { method = 'GET', body } = {}) {
const response = await fetch(`${API_BASE_URL}${path}`, {
method,
headers: {
'x-api-key': apiKey,
...(body ? { 'content-type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const detail = await response.text();
throw new Error(`MailSlurp ${method} ${path} failed (${response.status}): ${detail}`);
}
return response.status === 204 ? null : response.json();
};
}
async function extractInvoice(api, attachmentId) {
const response = await api('/ai/structured-content/attachment', {
method: 'POST',
body: {
attachmentId,
instructions: 'Extract only values present in the invoice. Do not infer missing fields.',
outputSchema: invoiceSchema,
},
});
const invoice = response.result;
if (!invoice || typeof invoice !== 'object') throw new Error('No invoice data returned');
if (typeof invoice.invoiceNumber !== 'string' || !invoice.invoiceNumber.trim()) {
throw new Error('Missing invoice number');
}
if (typeof invoice.supplier !== 'string' || !invoice.supplier.trim()) {
throw new Error('Missing supplier');
}
if (!/^[A-Z]{3}$/.test(invoice.currency)) throw new Error('Invalid currency');
if (typeof invoice.total !== 'number' || !Number.isFinite(invoice.total)) {
throw new Error('Invalid invoice total');
}
return invoice;
}
Call extractInvoice(createApi(process.env.MAILSLURP_API_KEY), attachmentId) after selecting the attachment. Compare the result with your expected supplier, purchase order, currency and permitted amounts before creating a payment or accounting entry. Schema validity alone does not establish that a document is genuine or that its values match your records.
/ai/structured-content/attachment
Generate structured content for an attachment
Use output schemas to extract data from an attachment using AI
Request, parameters, and responses
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
attachmentId | string | Yes | Attachment ID to read and pass to AI |
instructions | string | No | Optional instructions for the AI to follow. Try to be precise and clear. You can include examples and hints. |
outputSchema | StructuredOutputSchema | No | |
transformId | string:uuid | No | ID of transformer to apply |
emailId | string:uuid | No | Optional email ID for more context |
{
"attachmentId": "00000000-0000-4000-8000-000000000000",
"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"
},
"transformId": "00000000-0000-4000-8000-000000000000",
"emailId": "00000000-0000-4000-8000-000000000000"
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | StructuredContentResultDto | OK |
HTTP and SDK snippets
HTTP
POST /ai/structured-content/attachment HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
Content-Type: application/json
{
"attachmentId": "00000000-0000-4000-8000-000000000000",
"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"
},
"transformId": "00000000-0000-4000-8000-000000000000",
"emailId": "00000000-0000-4000-8000-000000000000"
}
cURL
curl -X POST "https://api.mailslurp.com/ai/structured-content/attachment" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"attachmentId":"00000000-0000-4000-8000-000000000000","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"},"transformId":"00000000-0000-4000-8000-000000000000","emailId":"00000000-0000-4000-8000-000000000000"}'
JavaScript SDK
import { Configuration, AIControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const aIController = new AIControllerApi(config);
const request = {
"generateStructuredContentAttachmentOptions": {
"attachmentId": "00000000-0000-4000-8000-000000000000",
"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"
},
"transformId": "00000000-0000-4000-8000-000000000000",
"emailId": "00000000-0000-4000-8000-000000000000"
}
};
const result = await aIController.generateStructuredContentFromAttachment(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.ai_controller_api import AIControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
aIController = AIControllerApi(api_client)
generate_structured_content_attachment_options = {
"attachmentId": "00000000-0000-4000-8000-000000000000",
"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"
},
"transformId": "00000000-0000-4000-8000-000000000000",
"emailId": "00000000-0000-4000-8000-000000000000"
}
result = aIController.generate_structured_content_from_attachment(generate_structured_content_attachment_options)
A missing, ambiguous, unreadable or unexpected value should enter a review path. Keep the original attachment accessible to the reviewer and avoid substituting zero or an empty string for missing financial data. Test representative invoices, scans, multiple attachments and non-invoice files before enabling automatic processing.
Process future invoices
Save the schema and instructions as an AI transformer. Test it on known attachments through invocation before mapping it to an inbox. Consume stored results or receive result webhooks, deduplicating writes to your destination.
Message-testing AI endpoints have their own outcome and evidence contract. Use match, assert and extract for assertions about email or SMS text; use the attachment workflow here for file content.