This workflow corresponds to n8n.io template #16266 — we link there as the canonical source.
This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "qqEyPIiXqpshnSKv",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Create a work4all incoming invoice from email attachments using AI",
"tags": [],
"nodes": [
{
"id": "e66e1fd3-d063-4ee3-9c6f-62cf8b3e0776",
"name": "Sticky Note - Description",
"type": "n8n-nodes-base.stickyNote",
"position": [
12368,
7072
],
"parameters": {
"width": 460,
"height": 680,
"content": "## Create a work4all incoming invoice from email attachments using AI\n\nThis **sub-workflow** is called by a parent workflow with one email item whose attachments are passed as binary data. The attachments are read by AI, structured invoice data is extracted, and the **work4all** node books the incoming invoice with the original files attached.\n\n### How it works\n1. Keeps only emails that carry a readable PDF / XML / JSON attachment.\n2. Extracts invoice data in one of two modes (**Extraction config**):\n - **`backend`** (default) \u2014 work4all AI backend, uses the **work4all** credential.\n - **`llm`** \u2014 your own **OpenAI-compatible** model (*Call LLM*).\n3. Normalises the result, books the incoming invoice and attaches the files.\n\n### Setup\n1. Add your **work4all** credential.\n2. Call this workflow from a parent that supplies the email + attachments.\n\n### Customization\n- **Extraction mode** \u2014 default is `backend` (work4all AI). For your own model, set **Extraction config** to `llm` and add an **OpenAI-compatible** credential on *Call LLM*.\n- Switch provider/model in *Call LLM* and the `MODEL` constant in *Build LLM messages* (must support **PDF / file input**).\n\nRequires a work4all account and the verified `n8n-nodes-work4all` node."
},
"typeVersion": 1
},
{
"id": "78f97643-5f70-480a-b97b-026145b05ebe",
"name": "Sticky Note - Input",
"type": "n8n-nodes-base.stickyNote",
"position": [
12912,
7680
],
"parameters": {
"color": 7,
"width": 704,
"height": 368,
"content": "## 1 \u00b7 Input & validation\nReceives the email from the parent workflow and keeps only items that carry a readable PDF / XML / JSON attachment."
},
"typeVersion": 1
},
{
"id": "637606f7-39aa-4f71-99eb-988f55e33661",
"name": "Sticky Note - Extract",
"type": "n8n-nodes-base.stickyNote",
"position": [
14304,
7376
],
"parameters": {
"color": 7,
"width": 470,
"height": 492,
"content": "## 3 \u00b7 Extract invoice data\nBoth modes read the attachments and return the same structured invoice JSON \u2014 via the work4all AI backend or an OpenAI-compatible LLM (*Build LLM messages* \u2192 *Call LLM*)."
},
"typeVersion": 1
},
{
"id": "455c54ab-3f25-4139-a6d0-ae08ce274bf6",
"name": "Sticky Note - Book",
"type": "n8n-nodes-base.stickyNote",
"position": [
15520,
7488
],
"parameters": {
"color": 7,
"width": 736,
"height": 312,
"content": "## 5 \u00b7 Book in work4all\nNormalises currency and dates, creates the incoming invoice in work4all and attaches the original files."
},
"typeVersion": 1
},
{
"id": "8dac390a-f06a-4a85-bb4f-130559fc4448",
"name": "When called by parent workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
12960,
7872
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
},
{
"id": "592be2b9-c567-4474-b715-ecbd62727f5a",
"name": "Has attachments?",
"type": "n8n-nodes-base.if",
"position": [
13184,
7872
],
"parameters": {
"options": {
"ignoreCase": false
},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7ea39fb4-a88f-4f20-85d2-6daea27f1c16",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ Object.keys($binary ?? {}).length > 0 }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "166ec2d0-b6a8-464c-b876-1ad81472a68c",
"name": "Build LLM messages",
"type": "n8n-nodes-base.code",
"position": [
14352,
7696
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// \u2699\ufe0f CONFIG: set your OpenAI-compatible model here\nconst MODEL = \"policy/EU-Sonnet-4-6\";\n\nconst item = $input.item;\n\nlet content = [\n {\n type: \"text\",\n text: `\nReturn ONLY valid JSON.\nDo not use markdown.\nDo not wrap in triple backticks.\nDo not add explanations before or after the JSON.\n\nUse exactly this shape:\n{\n \"found_invoice\": true,\n \"reason\": \"short explanation\",\n \"relevantAttachmentKeys\": [\"attachment_0\"],\n \"invoiceData\": {\n \"supplierCode\": null,\n \"supplierName\": null,\n \"supplierCustomerNumberAtSupplier\": null,\n \"supplierContactMailAddress\": null,\n \"supplierIban\": null,\n \"projectCode\": null,\n \"projectNumber\": null,\n \"projectName\": null,\n \"invoiceNumberSupplier\": null,\n \"note\": null,\n \"invoiceDate\": null,\n \"paymentTermDays\": null,\n \"discount1Rate\": null,\n \"discount1Days\": null,\n \"discount2Rate\": null,\n \"discount2Days\": null,\n \"currencyCode\": null,\n \"invoiceItems\": [\n {\n \"account\": null,\n \"costCenter\": null,\n \"costGroup\": null,\n \"projectCode\": null,\n \"taxRate\": null,\n \"netAmount\": null,\n \"grossAmount\": null,\n \"vatAmount\": null,\n \"note\": null\n }\n ]\n }\n}\n\nRules:\n- found_invoice = true only if at least one attachment is clearly an incoming supplier invoice / Eingangsrechnung / XRechnung / receipt relevant for accounting.\n- relevantAttachmentKeys must contain the original attachment keys like \"attachment_0\".\n- invoiceData field names must match exactly.\n- The note field should hold a short description, e.g. the supplier company name combined with the invoice period.\n- Each invoiceItems note should be a short note, not more than 50 characters.\n- Dates must be ISO-8601 strings if known, otherwise null.\n- Do not invent values.\n- Aggregate invoiceItems by VAT rate (Mehrwertsteuersatz), not by raw line item.\n- Return one invoiceItems entry per VAT bucket.\n- Sum netAmount, vatAmount and grossAmount per VAT bucket.\n- Only fill account, costCenter, costGroup, projectCode, taxCode if explicitly present in the documents.\n- If no invoice is found, return:\n {\n \"found_invoice\": false,\n \"reason\": \"...\",\n \"relevantAttachmentKeys\": [],\n \"invoiceData\": {\n \"supplierCode\": null,\n \"supplierName\": null,\n \"supplierCustomerNumberAtSupplier\": null,\n \"supplierContactMailAddress\": null,\n \"supplierIban\": null,\n \"projectCode\": null,\n \"projectNumber\": null,\n \"projectName\": null,\n \"invoiceNumberSupplier\": null,\n \"note\": null,\n \"invoiceDate\": null,\n \"paymentTermDays\": null,\n \"discount1Rate\": null,\n \"discount1Days\": null,\n \"discount2Rate\": null,\n \"discount2Days\": null,\n \"currencyCode\": null,\n \"invoiceItems\": []\n }\n }\n`\n }\n];\n\nlet has_xml_or_json = false;\n\nif (item.binary) {\n for (const key of Object.keys(item.binary)) {\n const file = item.binary[key];\n\n const mimeType = file.mimeType || \"\";\n const fileName = file.fileName || \"file\";\n\n const buffer = await this.helpers.getBinaryDataBuffer(0, key);\n const base64 = buffer.toString(\"base64\");\n\n const isPdf =\n mimeType.includes(\"pdf\") ||\n fileName.toLowerCase().endsWith(\".pdf\");\n\n if (isPdf) {\n has_xml_or_json = true;\n content.push({\n type: \"input_file\",\n filename: fileName,\n file_data: `data:application/pdf;base64,${base64}`\n });\n } else if (\n mimeType.includes(\"xml\") ||\n mimeType.includes(\"json\") ||\n fileName.endsWith(\".xml\") ||\n fileName.endsWith(\".json\")\n ) {\n has_xml_or_json = true;\n const text = buffer.toString(\"utf-8\");\n\n content.push({\n type: \"text\",\n text: `Content of ${fileName}:\\n${text}`\n });\n }\n }\n}\n\nreturn {\n json: {\n has_xml_or_json: has_xml_or_json,\n model: MODEL,\n messages: [\n {\n role: \"user\",\n content\n }\n ]\n }\n};"
},
"typeVersion": 2
},
{
"id": "f656a11f-2883-48ac-8b73-3a9151cd4237",
"name": "Has readable attachment?",
"type": "n8n-nodes-base.if",
"position": [
13424,
7776
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a5d662cb-f633-4648-bc88-bcc9362b9630",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ Object.values($binary ?? {}).some(f => /pdf|xml|json/i.test(((f.mimeType || '') + ' ' + (f.fileName || '')))) }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "9fc590c7-a747-4619-9612-df85a34d6bb7",
"name": "Call LLM",
"type": "n8n-nodes-base.httpRequest",
"position": [
14576,
7696
],
"parameters": {
"url": "https://router.eu.requesty.ai/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={{ $json }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"typeVersion": 4.4
},
{
"id": "416e69af-4831-4438-bc55-47645db328b4",
"name": "Parse extraction result",
"type": "n8n-nodes-base.code",
"position": [
14976,
7600
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "function removeNullish(value) {\n if (Array.isArray(value)) {\n return value.map(removeNullish);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value)\n .filter(([, v]) => v !== undefined && v !== null && v !== \"\")\n .map(([k, v]) => [k, removeNullish(v)])\n );\n }\n return value;\n}\n\n// LLM path returns an OpenAI envelope ($json.choices[0].message.content);\n// the work4all AI backend returns the payload directly (often as a plain JSON string).\nlet raw = $json && $json.choices ? $json.choices[0].message.content : $json;\n\nlet parsed;\nif (typeof raw === \"string\") {\n raw = raw\n .replace(/^```json\\s*/i, \"\")\n .replace(/^```\\s*/i, \"\")\n .replace(/\\s*```$/, \"\")\n .trim();\n parsed = raw ? JSON.parse(raw) : {};\n} else {\n parsed = raw ?? {};\n}\n\nreturn {\n json: {\n found_invoice: parsed.found_invoice === true,\n reason: parsed.reason ?? \"\",\n relevantAttachmentKeys: parsed.relevantAttachmentKeys ?? [],\n invoiceData: removeNullish(parsed.invoiceData ?? {}),\n },\n binary: $input.item.binary,\n};"
},
"typeVersion": 2
},
{
"id": "6ce97f81-cbb8-4c0d-af82-2f2ee828293c",
"name": "Has invoice?",
"type": "n8n-nodes-base.if",
"position": [
15200,
7600
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6573bc49-61be-476e-95d7-3f9b4e26b1c9",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.found_invoice }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "a76861d5-716f-48d8-bf94-6174f511f99d",
"name": "Filter attachments & normalize",
"type": "n8n-nodes-base.code",
"position": [
15568,
7584
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const relevantKeys = $json.relevantAttachmentKeys || [];\nconst sourceBinary = $('When called by parent workflow').item.binary || {};\n\nconst keptBinary = Object.fromEntries(\n relevantKeys\n .filter((key) => sourceBinary[key])\n .map((key) => [key, sourceBinary[key]])\n);\n\nconst invoiceData = { ...($json.invoiceData || {}) };\ninvoiceData.currency = \"EUR\";\ndelete invoiceData.currencyCode;\n\nfor (const key of [\"invoiceDate\", \"entryDate\", \"receiptDate\"]) {\n if (\n typeof invoiceData[key] === \"string\" &&\n /^\\d{4}-\\d{2}-\\d{2}$/.test(invoiceData[key])\n ) {\n invoiceData[key] = `${invoiceData[key]}T00:00:00Z`;\n }\n}\n\nconst receiptDate = $('When called by parent workflow').item.json.receivedDateTime;\n\ninvoiceData.receiptDate = receiptDate;\ninvoiceData.entryDate = receiptDate;\n\nreturn {\n json: {\n ...$json,\n invoiceData,\n relevantAttachmentKeys: relevantKeys,\n },\n binary: keptBinary,\n};"
},
"typeVersion": 2
},
{
"id": "14556c93-cc8c-41bb-9b00-e2cd136406c0",
"name": "Create incoming invoice",
"type": "n8n-nodes-work4all.work4all",
"position": [
15792,
7584
],
"parameters": {
"dataMode": "json",
"attachmentsJson": "={{ ($json.relevantAttachmentKeys || []).map(k => ({ binaryPropertyName: k })) }}",
"attachmentsMode": "json",
"invoiceDataJson": "={{$json.invoiceData}}"
},
"typeVersion": 1
},
{
"id": "b6f276dc-7eb3-4022-a8a2-53267f220c80",
"name": "Format work4all result",
"type": "n8n-nodes-base.code",
"position": [
16016,
7584
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "return {\n json: {\n messageId: $('When called by parent workflow').item.json.id,\n errors: $json.errors,\n invoiceData: $json.data\n }\n};"
},
"typeVersion": 2
},
{
"id": "30af0f13-15e8-482a-8209-a000b190baa3",
"name": "No invoice - skip",
"type": "n8n-nodes-base.noOp",
"position": [
15776,
7968
],
"parameters": {},
"typeVersion": 1
},
{
"id": "75dcd732-3065-4e5a-bba9-f87fdda02d50",
"name": "Build result (no invoice)",
"type": "n8n-nodes-base.code",
"position": [
16016,
7968
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "return {\n json: {\n messageId: $('When called by parent workflow').item.json.id,\n success: true\n }\n};"
},
"typeVersion": 2
},
{
"id": "cc7f976e-5085-42ab-9a8e-7a78155009d3",
"name": "Extraction config",
"type": "n8n-nodes-base.set",
"position": [
13744,
7600
],
"parameters": {
"options": {
"stripBinary": false
},
"assignments": {
"assignments": [
{
"id": "a1b2c3d4-+123456789001",
"name": "extractionMode",
"type": "string",
"value": "backend"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "cc5a7d76-6063-4f73-a15e-3c4382b0bb8e",
"name": "Use work4all AI backend?",
"type": "n8n-nodes-base.if",
"position": [
13968,
7600
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b1b2c3d4-+123456789002",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.extractionMode }}",
"rightValue": "backend"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "2c7e4d91-6af3-458a-9fa7-f4d32917b300",
"name": "Extract invoice data",
"type": "n8n-nodes-work4all.work4all",
"position": [
14576,
7504
],
"parameters": {
"operation": "extractInvoiceData"
},
"credentials": {
"work4allOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "147f7d49-6d33-4547-a819-86c90e39ca4a",
"name": "Sticky Note - Extraction mode",
"type": "n8n-nodes-base.stickyNote",
"position": [
13712,
7472
],
"parameters": {
"color": 7,
"width": 446,
"height": 336,
"content": "## 2 \u00b7 Choose extraction mode\nSet `extractionMode` in **Extraction config**: `backend` (work4all AI backend) or `llm` (your own OpenAI-compatible model)."
},
"typeVersion": 1
},
{
"id": "4e9262e1-c41b-481c-97d4-8678e1cd5810",
"name": "Sticky Note - Parse",
"type": "n8n-nodes-base.stickyNote",
"position": [
14928,
7488
],
"parameters": {
"color": 7,
"width": 420,
"height": 340,
"content": "## 4 \u00b7 Parse & check\nNormalises either output into the same shape and checks whether an invoice was found. If not, booking is skipped."
},
"typeVersion": 1
},
{
"id": "68c12587-e1d6-4efa-8be4-58b5403e4bb1",
"name": "Sticky Note - No invoice",
"type": "n8n-nodes-base.stickyNote",
"position": [
15728,
7872
],
"parameters": {
"color": 7,
"width": 488,
"height": 256,
"content": "## 6 \u00b7 No invoice\nItems without a usable invoice end here and return a simple skipped result."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "987ac2a4-e699-42e6-ba8b-35ccc0c4fbe7",
"connections": {
"Call LLM": {
"main": [
[
{
"node": "Parse extraction result",
"type": "main",
"index": 0
}
]
]
},
"Has invoice?": {
"main": [
[
{
"node": "Filter attachments & normalize",
"type": "main",
"index": 0
}
],
[
{
"node": "No invoice - skip",
"type": "main",
"index": 0
}
]
]
},
"Has attachments?": {
"main": [
[
{
"node": "Has readable attachment?",
"type": "main",
"index": 0
}
],
[
{
"node": "No invoice - skip",
"type": "main",
"index": 0
}
]
]
},
"Extraction config": {
"main": [
[
{
"node": "Use work4all AI backend?",
"type": "main",
"index": 0
}
]
]
},
"No invoice - skip": {
"main": [
[
{
"node": "Build result (no invoice)",
"type": "main",
"index": 0
}
]
]
},
"Build LLM messages": {
"main": [
[
{
"node": "Call LLM",
"type": "main",
"index": 0
}
]
]
},
"Extract invoice data": {
"main": [
[
{
"node": "Parse extraction result",
"type": "main",
"index": 0
}
]
]
},
"Create incoming invoice": {
"main": [
[
{
"node": "Format work4all result",
"type": "main",
"index": 0
}
]
]
},
"Parse extraction result": {
"main": [
[
{
"node": "Has invoice?",
"type": "main",
"index": 0
}
]
]
},
"Has readable attachment?": {
"main": [
[
{
"node": "Extraction config",
"type": "main",
"index": 0
}
],
[
{
"node": "No invoice - skip",
"type": "main",
"index": 0
}
]
]
},
"Use work4all AI backend?": {
"main": [
[
{
"node": "Extract invoice data",
"type": "main",
"index": 0
}
],
[
{
"node": "Build LLM messages",
"type": "main",
"index": 0
}
]
]
},
"Filter attachments & normalize": {
"main": [
[
{
"node": "Create incoming invoice",
"type": "main",
"index": 0
}
]
]
},
"When called by parent workflow": {
"main": [
[
{
"node": "Has attachments?",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
work4allOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This sub-workflow receives an email item with binary attachments, uses an OpenAI-compatible chat completions endpoint to detect and extract invoice fields from PDF/XML/JSON files, and then creates an incoming invoice in work4all with the relevant original attachments. Receives…
Source: https://n8n.io/workflows/16266/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.
This template is a powerful, reusable utility for managing stateful, long-running processes. It allows a main workflow to be paused indefinitely at "checkpoints" and then be resumed by external, async
Upload files from any source to your account Kommo or AmoCRM with a simple and reusable workflow. It can split a large file into small ones and upload chunks. Works for Kommo and amoCRM There are 3 re
Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.
Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.