发票处理系统用于提交
高级
这是一个Invoice Processing, AI Summarization领域的自动化工作流,包含 24 个节点。主要使用 If, Code, Xero, Gmail, Slack 等节点。 通过 Gmail、OCR.space、Slack 和 Xero 实现发票处理的自动化
前置要求
- •Google 账号和 Gmail API 凭证
- •Slack Bot Token 或 Webhook URL
- •可能需要目标 API 的认证凭证
- •Google Sheets API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "QHTTTSIjepK3ipPO",
"meta": {
"instanceId": "a9990cea8ba499cd2caa870c8ec5e719a88b31e6ed62907b6084053e5dc1857a",
"templateCredsSetupCompleted": true
},
"name": "Invoice Processing System for submission",
"tags": [
{
"id": "CMJL1dF3mMlRu0Jb",
"name": "Invoice Processing",
"createdAt": "2025-10-11T20:13:04.997Z",
"updatedAt": "2025-10-11T20:13:04.997Z"
}
],
"nodes": [
{
"id": "189e9199-b377-4acc-a0e7-d5769e69a762",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-96,
16
],
"parameters": {
"simple": false,
"filters": {
"q": "has:attachment filename:pdf"
},
"options": {
"downloadAttachments": true
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "d2kfFLUV2qlW2o43",
"name": "Gmail account"
}
},
"typeVersion": 1.3
},
{
"id": "7c5278ea-c32f-4f55-9ba4-4448c06c21f0",
"name": "Code in JavaScript",
"type": "n8n-nodes-base.code",
"position": [
80,
16
],
"parameters": {
"jsCode": "// Get email data\nconst email = $input.item.json;\nconst binary = $input.item.binary;\n\n// Check if we have binary attachments\nif (!binary || Object.keys(binary).length === 0) {\n return { \n json: { \n error: 'No invoice attachment found',\n emailId: email.id,\n emailSubject: email.subject\n } \n };\n}\n\n// Find first PDF or image attachment\nlet invoiceFile = null;\nlet invoiceKey = null;\n\nfor (const key in binary) {\n const file = binary[key];\n if (file.mimeType?.includes('pdf') || \n file.mimeType?.includes('image') ||\n file.mimeType?.includes('png') ||\n file.mimeType?.includes('jpg')) {\n invoiceFile = file;\n invoiceKey = key;\n break;\n }\n}\n\nif (!invoiceFile) {\n return { \n json: { \n error: 'No PDF or image attachment found',\n emailId: email.id,\n emailSubject: email.subject\n } \n };\n}\n\n// The binary data in n8n is already base64 encoded\n// Just use it directly without re-encoding\nlet base64Data = invoiceFile.data;\n\n// If it's a Buffer, convert to base64 string\nif (Buffer.isBuffer(base64Data)) {\n base64Data = base64Data.toString('base64');\n}\n\n// Build the data URL for OCR.space\nconst dataUrl = `data:${invoiceFile.mimeType};base64,${base64Data}`;\n\nreturn {\n json: {\n emailId: email.id,\n emailSubject: email.subject,\n emailFrom: email.from,\n emailDate: email.date,\n attachmentData: base64Data,\n attachmentName: invoiceFile.fileName,\n mimeType: invoiceFile.mimeType,\n dataUrl: dataUrl\n }\n};"
},
"typeVersion": 2
},
{
"id": "4ddb1479-1b7f-4f8d-ad41-1a92654c98c8",
"name": "Append or update row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1296,
-48
],
"parameters": {
"columns": {
"value": {
"Amount": "={{ $json.amount }}",
"Status": "Pending",
"Vendor": "={{ $json.vendor }}",
"Currency": "={{ $json.currency }}",
"Due Date": "={{ $json.dueDate }}",
"Email ID": "={{ $node[\"Code in JavaScript\"].json.emailId }}",
"Timestamp": "={{ $now.format('yyyy-MM-dd HH:mm:ss') }}",
"Description": "={{ $json.description }}",
"Invoice Date": "={{ $json.invoiceDate }}",
"Invoice Number": "={{ $json.invoiceNumber }}"
},
"schema": [
{
"id": "Invoice Number",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Invoice Number",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Vendor",
"type": "string",
"display": true,
"required": false,
"displayName": "Vendor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Amount",
"type": "string",
"display": true,
"required": false,
"displayName": "Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Currency",
"type": "string",
"display": true,
"required": false,
"displayName": "Currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoice Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Invoice Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Due Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Due Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email ID",
"type": "string",
"display": true,
"required": false,
"displayName": "Email ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Processed By",
"type": "string",
"display": true,
"required": false,
"displayName": "Processed By",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Invoice Number"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1TWE856dG0g0bSRr3M4SKPcpnJ32veWlq3m_DWJoSaTQ/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "=\"YOUR_SPREADSHEET_ID\""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "hVo2bSobLUbf5b3a",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "abea5730-c60f-47f4-83b3-50cb42c6c9e0",
"name": "Slack Approval Request",
"type": "n8n-nodes-base.slack",
"position": [
1904,
-48
],
"webhookId": "4f4fa1f8-f072-4ceb-b802-68904622847d",
"parameters": {
"select": "channel",
"message": "=🧾 *New Invoice Requires Approval*\n\n*Vendor:* {{ $json.vendor }}\n*Amount:* ${{ $json.amount }} {{ $json.currency }}\n*Invoice #:* {{ $json.invoiceNumber }}\n*Due Date:* {{ $json.dueDate }}\n*Description:* {{ $json.description }}",
"options": {},
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09LDKZJGBB",
"cachedResultName": "invoice-approvals"
},
"operation": "sendAndWait",
"authentication": "oAuth2",
"approvalOptions": {
"values": {
"approvalType": "double"
}
}
},
"credentials": {
"slackOAuth2Api": {
"id": "plTTzKNjrWG2CYjh",
"name": "Slack account"
}
},
"typeVersion": 2.3
},
{
"id": "955f6439-8d79-4f9f-b35c-7bf3e39f6aad",
"name": "Slack Rejection message",
"type": "n8n-nodes-base.slack",
"position": [
1296,
144
],
"webhookId": "e4b6a100-ab6c-4908-8909-a37c5c210aad",
"parameters": {
"text": "=Channel: #invoice-approvals\n\nText:\n❌ *Invoice Auto-Rejected*\n\nAn invoice was rejected by AI analysis:\n\n- From: {{ $node[\"Code in JavaScript\"].json.emailFrom }}\n- Subject: {{ $node[\"Code in JavaScript\"].json.emailSubject }}\n- Reason: Did not pass qualification checks\n- Confidence: {{ $json.confidence }}%\n- Red Flags: {{ $json.redFlags.join(', ') }}\n\nThe email has been labeled \"Rejected\" in Gmail.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09LDKZJGBB",
"cachedResultName": "invoice-approvals"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "plTTzKNjrWG2CYjh",
"name": "Slack account"
}
},
"typeVersion": 2.3
},
{
"id": "424382f8-ff99-457e-9714-569c83e29157",
"name": "Check Qualification",
"type": "n8n-nodes-base.if",
"position": [
1040,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1237bc0c-6f1a-4a3b-98ff-45369c8ebc06",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.qualified }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "be9d4c45-2bb3-4ca4-944e-65045fa75a60",
"name": "Parse Invoice Data",
"type": "n8n-nodes-base.code",
"position": [
720,
0
],
"parameters": {
"jsCode": "// Get OCR result\nconst ocrResult = $json;\n\n// Extract the text\nlet extractedText = '';\n\nif (ocrResult.ParsedResults && ocrResult.ParsedResults[0]) {\n extractedText = ocrResult.ParsedResults[0].ParsedText;\n} else {\n return {\n json: {\n error: 'OCR failed to extract text',\n qualified: false,\n redFlags: ['Could not read invoice text']\n }\n };\n}\n\n// Helper function to parse dates correctly without timezone issues\nfunction parseDate(dateString) {\n // Try to parse dates like \"October 1, 2025\"\n const date = new Date(dateString + ' 12:00:00 UTC'); // Add noon UTC to avoid timezone shifts\n \n if (!isNaN(date)) {\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n }\n \n return '';\n}\n\n// Parse invoice fields using regex\nconst invoiceData = {\n qualified: true,\n vendor: '',\n invoiceNumber: '',\n amount: 0,\n currency: 'USD',\n invoiceDate: '',\n dueDate: '',\n description: '',\n confidence: 100,\n redFlags: []\n};\n\n// Extract Vendor (line after \"From:\")\nconst vendorMatch = extractedText.match(/From:\\s*([^\\n]+)/i);\nif (vendorMatch) {\n invoiceData.vendor = vendorMatch[1].trim();\n}\n\n// Extract Invoice Number\nconst invoiceNumMatch = extractedText.match(/Invoice Number:\\s*([^\\n]+)/i);\nif (invoiceNumMatch) {\n invoiceData.invoiceNumber = invoiceNumMatch[1].trim();\n}\n\n// Extract Amount (from \"Total:\" line) - CRITICAL!\nconst totalMatch = extractedText.match(/Total:\\s*\\$\\s*([0-9,]+\\.?[0-9]*)/i);\nif (totalMatch) {\n invoiceData.amount = parseFloat(totalMatch[1].replace(/,/g, ''));\n}\n\n// If Total not found, try Amount line\nif (invoiceData.amount === 0) {\n const amountMatch = extractedText.match(/Amount:\\s*\\$\\s*([0-9,]+\\.?[0-9]*)/i);\n if (amountMatch) {\n invoiceData.amount = parseFloat(amountMatch[1].replace(/,/g, ''));\n }\n}\n\n// Extract Currency\nconst currencyMatch = extractedText.match(/(USD|EUR|GBP|CAD)/i);\nif (currencyMatch) {\n invoiceData.currency = currencyMatch[1].toUpperCase();\n}\n\n// Extract Invoice Date - FIXED!\nconst dateMatch = extractedText.match(/Date:\\s*([^\\n]+)/i);\nif (dateMatch) {\n const dateStr = dateMatch[1].trim();\n invoiceData.invoiceDate = parseDate(dateStr);\n}\n\n// Extract Due Date - FIXED!\nconst dueMatch = extractedText.match(/Due Date:\\s*([^\\n]+)/i);\nif (dueMatch) {\n const dateStr = dueMatch[1].trim();\n invoiceData.dueDate = parseDate(dateStr);\n}\n\n// Extract Description\nconst descMatch = extractedText.match(/Description:\\s*([^\\n]+)/i);\nif (descMatch) {\n invoiceData.description = descMatch[1].trim();\n}\n\n// Validation checks\nif (!invoiceData.vendor) {\n invoiceData.qualified = false;\n invoiceData.redFlags.push('Missing vendor name');\n}\n\nif (!invoiceData.invoiceNumber) {\n invoiceData.qualified = false;\n invoiceData.redFlags.push('Missing invoice number');\n}\n\nif (invoiceData.amount === 0) {\n invoiceData.qualified = false;\n invoiceData.redFlags.push('Missing or invalid amount');\n}\n\nif (invoiceData.amount > 10000) {\n invoiceData.qualified = false;\n invoiceData.redFlags.push('Amount exceeds $10,000');\n}\n\nreturn {\n json: invoiceData\n};"
},
"typeVersion": 2
},
{
"id": "36afc95e-af94-4c98-a458-744e9bfd1139",
"name": "Format for Slack",
"type": "n8n-nodes-base.code",
"position": [
1504,
-48
],
"parameters": {
"jsCode": "const sheetData = $node[\"Append or update row in sheet\"].json;\n\nreturn {\n json: {\n vendor: sheetData.Vendor,\n amount: sheetData.Amount,\n currency: sheetData.Currency,\n invoiceNumber: sheetData[\"Invoice Number\"],\n invoiceDate: sheetData[\"Invoice Date\"], // ← ADD THIS LINE!\n dueDate: sheetData[\"Due Date\"],\n description: sheetData.Description\n }\n};"
},
"typeVersion": 2
},
{
"id": "b524a093-e827-4049-b0c9-809dc839465e",
"name": "Rejection message",
"type": "n8n-nodes-base.slack",
"position": [
2896,
16
],
"webhookId": "019dcb52-375d-4fff-9e0d-d4d5b4ac0282",
"parameters": {
"text": "❌ Invoice rejected",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09LDKZJGBB",
"cachedResultName": "invoice-approvals"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "plTTzKNjrWG2CYjh",
"name": "Slack account"
}
},
"typeVersion": 2.3
},
{
"id": "afcf2f08-41ec-4ce5-bb32-98733317379f",
"name": "Success message",
"type": "n8n-nodes-base.slack",
"position": [
3248,
-176
],
"webhookId": "ae7fc4b9-99a3-41ad-ba4e-4e4a15a52303",
"parameters": {
"text": "✅ Invoice posted to Xero",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09LDKZJGBB",
"cachedResultName": "invoice-approvals"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "plTTzKNjrWG2CYjh",
"name": "Slack account"
}
},
"typeVersion": 2.3
},
{
"id": "2a70f5c0-db5f-44eb-8eb8-24c978892049",
"name": "Update Rejected Status",
"type": "n8n-nodes-base.googleSheets",
"position": [
2704,
16
],
"parameters": {
"columns": {
"value": {
"Status": "Rejected",
"Invoice Number": "={{ $('Clean Invoice Payload').item.json.invoiceNumber }}"
},
"schema": [
{
"id": "Invoice Number",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Invoice Number",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Vendor",
"type": "string",
"display": true,
"required": false,
"displayName": "Vendor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Amount",
"type": "string",
"display": true,
"required": false,
"displayName": "Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Currency",
"type": "string",
"display": true,
"required": false,
"displayName": "Currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoice Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Invoice Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Due Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Due Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email ID",
"type": "string",
"display": true,
"required": false,
"displayName": "Email ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Processed By",
"type": "string",
"display": true,
"required": false,
"displayName": "Processed By",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Invoice Number"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1TWE856dG0g0bSRr3M4SKPcpnJ32veWlq3m_DWJoSaTQ/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "=\"YOUR_SPREADSHEET_ID\""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "hVo2bSobLUbf5b3a",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "f775d6e5-a97b-4aea-be73-f6a3030cbfc3",
"name": "Update Approved Status",
"type": "n8n-nodes-base.googleSheets",
"position": [
3024,
-176
],
"parameters": {
"columns": {
"value": {
"Status": "Approved",
"Invoice Number": "={{ $('Clean Invoice Payload').item.json.invoiceNumber }}"
},
"schema": [
{
"id": "Invoice Number",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Invoice Number",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Vendor",
"type": "string",
"display": true,
"required": false,
"displayName": "Vendor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Amount",
"type": "string",
"display": true,
"required": false,
"displayName": "Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Currency",
"type": "string",
"display": true,
"required": false,
"displayName": "Currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoice Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Invoice Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Due Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Due Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email ID",
"type": "string",
"display": true,
"required": false,
"displayName": "Email ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Processed By",
"type": "string",
"display": true,
"required": false,
"displayName": "Processed By",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Invoice Number"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "=Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "=\"YOUR_SPREADSHEET_ID\""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "hVo2bSobLUbf5b3a",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "b062f234-61a1-4913-8135-be9d9be322a8",
"name": "Add Processed label",
"type": "n8n-nodes-base.gmail",
"position": [
2832,
-176
],
"webhookId": "a5cbed4c-0c43-4bf0-8114-e8fbbf987e9e",
"parameters": {
"labelIds": [
"Label_3710233975993850496"
],
"messageId": "={{$node[\"Code in JavaScript\"].json.emailId}}",
"operation": "addLabels"
},
"credentials": {
"gmailOAuth2": {
"id": "d2kfFLUV2qlW2o43",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "854afcd4-6559-4753-b75d-2a65766c492d",
"name": "Add Rejected label",
"type": "n8n-nodes-base.gmail",
"position": [
2432,
16
],
"webhookId": "2e5f020d-ed42-438c-8744-4fdd602145c7",
"parameters": {
"labelIds": [
"Label_8899302689673155790"
],
"messageId": "={{$node[\"Code in JavaScript\"].json.emailId}}",
"operation": "addLabels"
},
"credentials": {
"gmailOAuth2": {
"id": "d2kfFLUV2qlW2o43",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "025efb16-32dd-4a7e-8349-baef18b32040",
"name": "Get many contacts",
"type": "n8n-nodes-base.xero",
"position": [
2416,
-176
],
"parameters": {
"limit": 1,
"options": {},
"resource": "contact",
"operation": "getAll",
"organizationId": "=9a46b4a9-feac-446e-a04d-4e7421565898"
},
"credentials": {
"xeroOAuth2Api": {
"id": "XfSNoqDEDyivPRYX",
"name": "Xero account"
}
},
"typeVersion": 1
},
{
"id": "b43a1017-8ecb-4c39-b92f-8865349ccec2",
"name": "Clean Invoice Payload",
"type": "n8n-nodes-base.code",
"position": [
1696,
-48
],
"parameters": {
"jsCode": "const invoiceData = { ...$node[\"Format for Slack\"].json };\n\n// Strip off any vendor or itemCode field that might be passed\ndelete invoiceData.vendor;\ndelete invoiceData.itemCode;\ndelete invoiceData[\"Item Code Name or ID\"]; // Just in case\n\nreturn [{ json: invoiceData }];\n"
},
"typeVersion": 2
},
{
"id": "a77a3860-e69a-428f-b74e-d926859c83e3",
"name": "Create invoice",
"type": "n8n-nodes-base.httpRequest",
"position": [
2624,
-176
],
"parameters": {
"url": "https://api.xero.com/api.xro/2.0/Invoices",
"method": "POST",
"options": {},
"jsonBody": "={\n \"Type\": \"ACCPAY\",\n \"Contact\": {\n \"ContactID\": \"{{$node[\"Get many contacts\"].json.ContactID}}\"\n },\n \"Date\": \"{{$node[\"Format for Slack\"].json.invoiceDate}}\",\n \"DueDate\": \"{{$node[\"Format for Slack\"].json.dueDate}}\",\n \"InvoiceNumber\": \"{{$node[\"Format for Slack\"].json.invoiceNumber}}\",\n \"Reference\": \"{{$node[\"Format for Slack\"].json.invoiceNumber}}\",\n \"Status\": \"DRAFT\",\n \"LineAmountTypes\": \"Exclusive\",\n \"LineItems\": [\n {\n \"Description\": \"{{$node[\"Format for Slack\"].json.description}}\",\n \"Quantity\": 1,\n \"UnitAmount\": {{$node[\"Format for Slack\"].json.amount}},\n \"AccountCode\": \"463\",\n \"TaxType\": \"INPUT2\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "application/json"
},
{
"name": "xero-tenant-id",
"value": "=\"{{$credentials.xero.apiKey}}\""
}
]
},
"nodeCredentialType": "xeroOAuth2Api"
},
"credentials": {
"xeroOAuth2Api": {
"id": "XfSNoqDEDyivPRYX",
"name": "Xero account"
}
},
"typeVersion": 4.2
},
{
"id": "270ea877-b081-4704-8137-3cbcf62606fb",
"name": "Has Valid Attachment?",
"type": "n8n-nodes-base.if",
"position": [
288,
16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5b78e677-d068-411a-8c43-e41a9725f370",
"operator": {
"type": "number",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{$json.error}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "4128986d-09d7-44b5-a9f5-5f8a532c1712",
"name": "Was Approved?",
"type": "n8n-nodes-base.if",
"position": [
2128,
-48
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b58f3729-809c-4b21-9210-6c581dbeed50",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.data.approved }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "8400ed04-51ba-4cc6-92cc-abb03a09e258",
"name": "Extract Text",
"type": "n8n-nodes-base.httpRequest",
"position": [
544,
0
],
"parameters": {
"url": "https://api.ocr.space/parse/image",
"method": "POST",
"options": {},
"jsonBody": "={\n \"name\": \"apikey\",\n \"value\": \"{{$credentials.ocrspaceApi.apiKey}}\"\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "d132fdec-f017-49f6-a4bc-8a938df37d98",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-912,
-464
],
"parameters": {
"width": 608,
"height": 1696,
"content": "### Automate Invoice Processing with Gmail, OCR.space, Slack & Xero\n\nThis n8n template demonstrates how to automatically extract, validate, approve, and sync invoices received via email using AI and automation — reducing manual processing time and minimizing errors.\n\n### Use Cases\n\n- Finance teams looking to eliminate invoice approval bottlenecks \n- Businesses that receive PDF/image invoices via email \n- Companies currently logging invoices manually into spreadsheets or accounting tools \n\n### How It Works\n\n1. **Trigger**: Watches for new emails in Gmail with PDF/image attachments. \n2. **OCR**: Sends the attachment to OCR.space API (https://ocr.space/OCRAPI) to extract invoice text. \n3. **Parsing**: Extracts key fields: \n - Vendor \n - Invoice number \n - Amount \n - Currency \n - Invoice date \n - Due date \n - Description \n4. **Validation Logic**: \n - Checks if amount is valid \n - Ensures vendor and invoice number are present \n - Flags high-value invoices (e.g., over $10,000) \n5. **Routing**: \n - If invalid: \n - Sends a Slack message highlighting issues\n - Labels email as **Rejected** \n - If valid: \n - Logs the invoice into Google Sheets \n - Sends a Slack message to the finance team for approval \n - After approval, creates a draft invoice in Xero \n - Labels the email as **Processed** in Gmail \n\n### Customisation Options\n\n- Swap Gmail trigger with a file uploader or webhook \n- Adjust the Slack notification channels or approval thresholds \n- Replace Xero with QuickBooks, Zoho, or another tool \n- Adapt log storage from Google Sheets to Airtable or Notion \n- Add a reminder flow if no approval is received within 24h \n\n### Prerequisites/Credential Setup\n\nTo use this workflow securely, you'll need the following credentials set up in n8n:\n\n- **Gmail OAuth2** – to watch for incoming invoice emails and apply labels\n- **OCR.space API** – create an HTTP Credential in n8n named `ocrspaceApi`, with your OCR.space key\n- **Slack OAuth2 or Webhook (with connected bot)** – to send approval/rejection messages to your team\n- **Google Sheets OAuth2 credentials** – to log processed invoices for audit and tracking\n- **Xero OAuth2 credentials (via n8n’s native integration)** – connect your Xero account to push approved invoices in Draft status\n \n### Secure Configuration\n- All credential fields use **n8n Credential Types** \n- No sensitive data (API keys, spreadsheet IDs) is hardcoded \n\n### Why This Helps\n\n- Reduces time spent manually reviewing and copying invoices\n- Maintains structured logs with clean data\n- Automates Slack approvals and reduces email back-and-forth\n- Improves audit trails and AP visibility across tools\n\n---\nWith this template, finance teams can streamline invoice approvals, reduce processing time, and maintain a clean audit trail, all fully automated."
},
"typeVersion": 1
},
{
"id": "b16d6e58-0125-4735-b939-040945fcc89b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
-336
],
"parameters": {
"width": 1248,
"height": 640,
"content": "## 2. Approve or Reject Invoice in Slack\n[Read more about Slack Approval workflows in n8n](https://docs.n8n.io/integrations/slack/)\n\nAfter parsing, the workflow evaluates whether the invoice meets preset criteria (e.g., approved vendors or amount limits). It logs the result in a Google Sheet and sends a Slack message for manual approval. Based on the response, it either proceeds to Xero or marks it as rejected.\n\nSetup Tip: Replace YOUR_SLACK_CLIENT_ID with the Client ID from your Slack apps page - https://api.slack.com/apps.\n"
},
"typeVersion": 1
},
{
"id": "af22b982-18c5-4adc-9e88-d7ecf53f357c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
-336
],
"parameters": {
"width": 1120,
"height": 640,
"content": "## 1. Extract & Parse Invoice from Gmail\n[Read more about the Gmail and HTTP Request nodes](https://docs.n8n.io/integrations/builtin/email-read-imap/)\n \nThis section listens for new emails via Gmail, checks if an attachment exists and is valid, then extracts and parses invoice data using OCR (via OCR.Space API). The data is cleaned and prepared for downstream steps.\n\nSetup Tip: Replace YOUR_GOOGLE_CLIENT_ID with the Client ID from your Google Console - https://console.cloud.google.com/ for your Google Sheets, Google Docs, Gmail."
},
"typeVersion": 1
},
{
"id": "422c5b66-d77a-4224-b689-e4b57152373e",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2288,
-336
],
"parameters": {
"width": 1168,
"height": 640,
"content": "## 3. Sync Outcome to Xero & Notify\n[Read more about Xero and Gmail automation](https://docs.n8n.io/integrations/xero/)\n\nIf the invoice is approved, the workflow fetches the contact from Xero, creates the invoice, updates Gmail labels, logs final status in Google Sheets, and sends a success message in Slack. If rejected, it applies a different Gmail label, updates status, and notifies the team.\n"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "a1d9da45-06e7-460c-9c56-bc430ee568b6",
"connections": {
"8400ed04-51ba-4cc6-92cc-abb03a09e258": {
"main": [
[
{
"node": "be9d4c45-2bb3-4ca4-944e-65045fa75a60",
"type": "main",
"index": 0
}
]
]
},
"189e9199-b377-4acc-a0e7-d5769e69a762": {
"main": [
[
{
"node": "7c5278ea-c32f-4f55-9ba4-4448c06c21f0",
"type": "main",
"index": 0
}
]
]
},
"4128986d-09d7-44b5-a9f5-5f8a532c1712": {
"main": [
[
{
"node": "025efb16-32dd-4a7e-8349-baef18b32040",
"type": "main",
"index": 0
}
],
[
{
"node": "854afcd4-6559-4753-b75d-2a65766c492d",
"type": "main",
"index": 0
}
]
]
},
"a77a3860-e69a-428f-b74e-d926859c83e3": {
"main": [
[
{
"node": "b062f234-61a1-4913-8135-be9d9be322a8",
"type": "main",
"index": 0
}
]
]
},
"36afc95e-af94-4c98-a458-744e9bfd1139": {
"main": [
[
{
"node": "b43a1017-8ecb-4c39-b92f-8865349ccec2",
"type": "main",
"index": 0
}
]
]
},
"025efb16-32dd-4a7e-8349-baef18b32040": {
"main": [
[
{
"node": "a77a3860-e69a-428f-b74e-d926859c83e3",
"type": "main",
"index": 0
}
]
]
},
"854afcd4-6559-4753-b75d-2a65766c492d": {
"main": [
[
{
"node": "2a70f5c0-db5f-44eb-8eb8-24c978892049",
"type": "main",
"index": 0
}
]
]
},
"7c5278ea-c32f-4f55-9ba4-4448c06c21f0": {
"main": [
[
{
"node": "270ea877-b081-4704-8137-3cbcf62606fb",
"type": "main",
"index": 0
}
]
]
},
"be9d4c45-2bb3-4ca4-944e-65045fa75a60": {
"main": [
[
{
"node": "424382f8-ff99-457e-9714-569c83e29157",
"type": "main",
"index": 0
}
]
]
},
"b062f234-61a1-4913-8135-be9d9be322a8": {
"main": [
[
{
"node": "f775d6e5-a97b-4aea-be73-f6a3030cbfc3",
"type": "main",
"index": 0
}
]
]
},
"424382f8-ff99-457e-9714-569c83e29157": {
"main": [
[
{
"node": "4ddb1479-1b7f-4f8d-ad41-1a92654c98c8",
"type": "main",
"index": 0
}
],
[
{
"node": "955f6439-8d79-4f9f-b35c-7bf3e39f6aad",
"type": "main",
"index": 0
}
]
]
},
"b43a1017-8ecb-4c39-b92f-8865349ccec2": {
"main": [
[
{
"node": "abea5730-c60f-47f4-83b3-50cb42c6c9e0",
"type": "main",
"index": 0
}
]
]
},
"270ea877-b081-4704-8137-3cbcf62606fb": {
"main": [
[
{
"node": "8400ed04-51ba-4cc6-92cc-abb03a09e258",
"type": "main",
"index": 0
}
]
]
},
"abea5730-c60f-47f4-83b3-50cb42c6c9e0": {
"main": [
[
{
"node": "4128986d-09d7-44b5-a9f5-5f8a532c1712",
"type": "main",
"index": 0
}
]
]
},
"f775d6e5-a97b-4aea-be73-f6a3030cbfc3": {
"main": [
[
{
"node": "afcf2f08-41ec-4ce5-bb32-98733317379f",
"type": "main",
"index": 0
}
]
]
},
"2a70f5c0-db5f-44eb-8eb8-24c978892049": {
"main": [
[
{
"node": "b524a093-e827-4049-b0c9-809dc839465e",
"type": "main",
"index": 0
}
]
]
},
"4ddb1479-1b7f-4f8d-ad41-1a92654c98c8": {
"main": [
[
{
"node": "36afc95e-af94-4c98-a458-744e9bfd1139",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 发票处理, AI 摘要总结
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
基于HubSpot上下文的AI邮件回复与Slack审批
基于HubSpot上下文的AI邮件回复与Slack审批
If
Code
Gmail
+
If
Code
Gmail
19 节点Miha
客户关系管理
AI 客户支持分流与摘要系统
使用GPT-4o、Slack和CRM集成自动处理客户支持
If
Set
Code
+
If
Set
Code
32 节点NodeAlchemy
工单管理
主动智能客户支持中心
使用GPT-4、Gmail和Trello的自动邮件支持分诊
If
Code
Gmail
+
If
Code
Gmail
14 节点Marth
工单管理
AI简历处理与GitHub分析(VLM运行)
AI简历处理与GitHub分析(VLM运行)
If
Code
Gmail
+
If
Code
Gmail
18 节点Shahrear
人工智能
AI驱动的发票提取与自动化系统
使用Gemini AI、Google Sheets和Gmail通知提取和处理发票
If
Set
Code
+
If
Set
Code
32 节点Yashraj singh sisodiya
发票处理
AI邮件助手:通过ChatGPT摘要和Slack摘要优先处理Gmail邮件
AI邮件助手:通过ChatGPT摘要和Slack摘要优先处理Gmail邮件
If
Code
Gmail
+
If
Code
Gmail
14 节点Darsheel
工单管理
工作流信息
难度等级
高级
节点数量24
分类2
节点类型9
作者
Abi Odedeyi
@abiodedeyiAutomation Builder with extensive experience helping businesses streamline processes and generate revenue.
外部链接
在 n8n.io 查看 →
分享此工作流