This workflow corresponds to n8n.io template #16643 — we link there as the canonical source.
This workflow follows the Form Trigger → Gmail 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": "LlOeL6O4MzcHr3W7",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Invoice PDF Processor and Validator \u2014 Form + ChatGPT AI + Google Sheets + Gmail",
"tags": [],
"nodes": [
{
"id": "c63c136a-ae4f-471c-b846-3d2e42f6d818",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1536,
-224
],
"parameters": {
"color": 4,
"width": 684,
"height": 1268,
"content": "## Invoice PDF Processor and Validator \u2014 Form + ChatGPT AI + Google Sheets + Gmail\n\nFor finance and operations teams who want every invoice PDF turned into structured, verified data automatically. Upload an invoice PDF via a form. A ChatGPT AI model reads the extracted text and returns structured JSON with all invoice details \u2014 vendor, invoice number, dates, PO number, GST details, and every line item (description, quantity, unit price, HSN, GST%). The workflow then checks that the sum of all line totals matches the invoice total, logs the header and every line to Google Sheets, and emails you a complete extraction report.\n\n## How it works\n- **1. Form \u2014 Upload Invoice PDF** collects your name, email, the PDF, currency, and optional notes\n- **2. Extract From File \u2014 PDF to Text** converts the PDF binary to text\n- **3. HTTP \u2014 AI Extract Invoice Fields** sends the text to a ChatGPT AI model and returns structured JSON\n- **4. Code \u2014 Parse Invoice JSON Safely** parses and validates the JSON and attaches the form details\n- **5a. Sheets \u2014 Log Invoice Header** appends one header row (parallel branch)\n- **5b. Split Out \u2014 Line Items** separates each line item (parallel branch)\n- **6. Code \u2014 Add Unique Key and Line Metadata** adds a unique key and carries invoice context to each line\n- **7. Sheets \u2014 Log Each Line Item** appends one row per line item\n- **8. Aggregate \u2014 Collect All Line Items** bundles the lines back into one item\n- **9. Code \u2014 Verify Totals and Build Report** checks the line-sum against the invoice total and builds the HTML report\n- **10. Gmail \u2014 Send Extraction Report** emails the report\n\n## Sheet setup (two tabs in one sheet)\n- Tab **Invoices**: Date, Invoice No, Vendor, Invoice Date, PO Number, Total Amount, Taxable Amount, Total CGST, Total SGST, Line Items Count, Currency, Uploaded By\n- Tab **Line Items**: Date, Invoice No, Vendor, SR No, Description, HSN Code, Quantity, Unit Price, Line Total, CGST %, SGST %, Unique Key\n\n## Set up steps\n1. In **3. HTTP \u2014 AI Extract Invoice Fields** \u2014 connect your OpenAI API credential. A ChatGPT AI model is used for the extraction\n2. In **5a. Sheets \u2014 Log Invoice Header** and **7. Sheets \u2014 Log Each Line Item** \u2014 connect Google Sheets OAuth2 and replace `YOUR_INVOICE_LOG_SHEET_ID` in both. Create the two tabs above\n3. In **10. Gmail \u2014 Send Extraction Report** \u2014 connect Gmail OAuth2. The recipient comes from the form\n4. Activate the workflow, open the Form URL, upload an invoice PDF, and submit. The report is logged and emailed automatically"
},
"typeVersion": 1
},
{
"id": "908fb953-9f3e-4004-8a33-711942aae766",
"name": "Section \u2014 Upload, Extract, and AI",
"type": "n8n-nodes-base.stickyNote",
"position": [
-768,
-16
],
"parameters": {
"color": 5,
"width": 708,
"height": 692,
"content": "## Upload, Extract, and AI\nForm accepts the PDF upload. Extract From File converts the PDF to text. A ChatGPT AI model then extracts all invoice fields as structured JSON."
},
"typeVersion": 1
},
{
"id": "efb797a6-bd64-4aed-bd84-61e8aec9dfd3",
"name": "Section \u2014 Parse, Split, and Log",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
-176
],
"parameters": {
"color": 6,
"width": 900,
"height": 1028,
"content": "## Parse, Split, and Log\nCode parses the JSON safely. One branch logs the invoice header. The other splits each line item, adds a unique key, and logs every line to the second tab."
},
"typeVersion": 1
},
{
"id": "dbbb73dd-f965-44c5-89a9-ef120ac4ea7c",
"name": "Section \u2014 Verify and Email",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
-48
],
"parameters": {
"color": 4,
"width": 740,
"height": 708,
"content": "## Verify and Email\nAggregate collects all line items. Code verifies the line-sum equals the invoice total within tolerance. Gmail sends the full extraction report with a verified or mismatch status."
},
"typeVersion": 1
},
{
"id": "6af86046-b05e-44ce-a658-74357489aef6",
"name": "1. Form \u2014 Upload Invoice PDF",
"type": "n8n-nodes-base.formTrigger",
"position": [
-704,
224
],
"parameters": {
"options": {
"path": "invoice-processor",
"buttonLabel": "Process Invoice",
"appendAttribution": false
},
"formTitle": "Invoice PDF Processor",
"formFields": {
"values": [
{
"fieldLabel": "Your Name",
"placeholder": "e.g. Rahul Kumar",
"requiredField": true
},
{
"fieldLabel": "Your Email",
"placeholder": "your@email.com \u2014 extraction report will be sent here",
"requiredField": true
},
{
"fieldType": "file",
"fieldLabel": "Invoice PDF",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".pdf"
},
{
"fieldType": "dropdown",
"fieldLabel": "Currency",
"fieldOptions": {
"values": [
{
"option": "INR \u2014 Indian Rupee"
},
{
"option": "USD \u2014 US Dollar"
},
{
"option": "EUR \u2014 Euro"
},
{
"option": "GBP \u2014 British Pound"
}
]
},
"requiredField": true
},
{
"fieldLabel": "Notes",
"placeholder": "Optional \u2014 any context about this invoice batch or vendor"
}
]
},
"formDescription": "Upload a vendor invoice PDF. AI will extract all line items, validate totals, log to Google Sheets, and email you a full extraction report."
},
"typeVersion": 2.2
},
{
"id": "f12462ee-3de1-4f57-96d6-d0a81b8b373f",
"name": "2. Extract From File \u2014 PDF to Text",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-464,
224
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1.1
},
{
"id": "c9096ea0-90cd-48b4-b278-8c070cbd7709",
"name": "3. HTTP \u2014 AI Extract Invoice Fields",
"type": "n8n-nodes-base.httpRequest",
"position": [
-224,
224
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {
"timeout": 60000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"temperature\": 0.1,\n \"max_tokens\": 4000,\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a document parsing assistant. Extract structured data from invoice text and return ONLY valid raw JSON. No markdown, no code fences, no explanation text. Output must start with { and end with }.\"\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify(\"Extract all fields from this invoice. Return ONLY valid JSON with this exact structure:\\n{\\n \\\"invoice_number\\\": \\\"string\\\",\\n \\\"vendor_name\\\": \\\"string\\\",\\n \\\"invoice_date\\\": \\\"DD/MM/YYYY\\\",\\n \\\"po_number\\\": \\\"string or null\\\",\\n \\\"po_date\\\": \\\"DD/MM/YYYY or null\\\",\\n \\\"total_amount\\\": float,\\n \\\"taxable_amount\\\": float or null,\\n \\\"total_cgst\\\": float or null,\\n \\\"total_sgst\\\": float or null,\\n \\\"line_items\\\": [\\n {\\n \\\"serial_no\\\": \\\"string\\\",\\n \\\"description\\\": \\\"string \u2014 item name without HSN code\\\",\\n \\\"hsn_code\\\": \\\"string or null\\\",\\n \\\"quantity\\\": float,\\n \\\"unit_price\\\": float,\\n \\\"line_total\\\": float,\\n \\\"cgst_pct\\\": \\\"string e.g. 9% or null\\\",\\n \\\"sgst_pct\\\": \\\"string e.g. 9% or null\\\"\\n }\\n ]\\n}\\n\\nRules:\\n- Extract EVERY line item \u2014 do not skip any\\n- line_total must equal quantity multiplied by unit_price\\n- total_amount is the final payable amount including all taxes\\n- If a field is not found, use null\\n- Return ONLY the JSON object\\n\\nInvoice text:\\n\") + $('2. Extract From File \u2014 PDF to Text').first().json.text }}\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"typeVersion": 4.2
},
{
"id": "a753efd7-27a3-40ee-b4ee-11a5cc45b774",
"name": "4. Code \u2014 Parse Invoice JSON Safely",
"type": "n8n-nodes-base.code",
"position": [
32,
224
],
"parameters": {
"jsCode": "const resp = $input.first().json;\nconst formData = $('1. Form \u2014 Upload Invoice PDF').first().json;\n\nconst raw = resp?.choices?.[0]?.message?.content || '';\n\nif (!raw) throw new Error('AI returned empty response. Check your OpenAI API key and credits.');\n\nlet invoice = {};\ntry {\n const cleaned = raw.trim()\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/```$/i, '')\n .trim();\n const start = cleaned.indexOf('{');\n const end = cleaned.lastIndexOf('}');\n if (start === -1 || end === -1) throw new Error('No JSON object found in response');\n invoice = JSON.parse(cleaned.substring(start, end + 1));\n} catch (e) {\n throw new Error('JSON parsing failed: ' + e.message + ' | Raw: ' + raw.substring(0, 300));\n}\n\nif (!invoice.invoice_number) throw new Error('Invoice number not found in extraction. PDF may be image-only \u2014 OCR required.');\nif (!Array.isArray(invoice.line_items) || invoice.line_items.length === 0) {\n throw new Error('No line items extracted. Check PDF quality or invoice format.');\n}\n\nconst submitter = (formData['Your Name'] || '').trim();\nconst email = (formData['Your Email'] || '').trim();\nconst currency = (formData['Currency'] || 'INR').split('\u2014')[0].trim();\nconst notes = (formData['Notes'] || '').trim();\nconst processedAt = new Date().toISOString();\nconst dateOnly = processedAt.split('T')[0];\n\nreturn [{\n json: {\n ...invoice,\n submitter,\n email,\n currency,\n notes,\n processedAt,\n dateOnly\n }\n}];"
},
"typeVersion": 2
},
{
"id": "bfda95c7-0753-4d32-a6ba-fc4750da3a95",
"name": "5a. Sheets \u2014 Log Invoice Header",
"type": "n8n-nodes-base.googleSheets",
"position": [
272,
64
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $json.dateOnly }}",
"Vendor": "={{ $json.vendor_name }}",
"Currency": "={{ $json.currency }}",
"PO Number": "={{ $json.po_number || '' }}",
"Invoice No": "={{ $json.invoice_number }}",
"Total CGST": "={{ $json.total_cgst || '' }}",
"Total SGST": "={{ $json.total_sgst || '' }}",
"Uploaded By": "={{ $json.submitter }}",
"Invoice Date": "={{ $json.invoice_date }}",
"Total Amount": "={{ $json.total_amount }}",
"Taxable Amount": "={{ $json.taxable_amount || '' }}",
"Line Items Count": "={{ $json.line_items.length }}"
},
"schema": [
{
"id": "Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoice No",
"type": "string",
"display": true,
"required": false,
"displayName": "Invoice No",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Vendor",
"type": "string",
"display": true,
"required": false,
"displayName": "Vendor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoice Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Invoice Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "PO Number",
"type": "string",
"display": true,
"required": false,
"displayName": "PO Number",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Total Amount",
"type": "string",
"display": true,
"required": false,
"displayName": "Total Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Taxable Amount",
"type": "string",
"display": true,
"required": false,
"displayName": "Taxable Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Total CGST",
"type": "string",
"display": true,
"required": false,
"displayName": "Total CGST",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Total SGST",
"type": "string",
"display": true,
"required": false,
"displayName": "Total SGST",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Line Items Count",
"type": "string",
"display": true,
"required": false,
"displayName": "Line Items Count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Currency",
"type": "string",
"display": true,
"required": false,
"displayName": "Currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Uploaded By",
"type": "string",
"display": true,
"required": false,
"displayName": "Uploaded By",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Invoices"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_INVOICE_LOG_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "4a2cc96f-966c-4330-a542-159c91119e68",
"name": "5b. Split Out \u2014 Line Items",
"type": "n8n-nodes-base.splitOut",
"position": [
272,
272
],
"parameters": {
"options": {},
"fieldToSplitOut": "line_items"
},
"typeVersion": 1
},
{
"id": "bd83cbed-1b29-4082-8399-790a0e0ae9ce",
"name": "6. Code \u2014 Add Unique Key and Line Metadata",
"type": "n8n-nodes-base.code",
"position": [
512,
272
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst invoice = $('4. Code \u2014 Parse Invoice JSON Safely').first().json;\nconst idx = $itemIndex;\n\nconst uniqueKey = (invoice.invoice_number || 'INV') + '-' + idx;\n\nconst calcTotal = parseFloat(item.quantity || 0) * parseFloat(item.unit_price || 0);\nconst lineTotal = parseFloat(item.line_total || 0);\nconst lineTotalMatch = Math.abs(calcTotal - lineTotal) < 0.5;\n\nreturn {\n json: {\n uniqueKey,\n invoiceNo: invoice.invoice_number,\n vendor: invoice.vendor_name,\n invoiceDate: invoice.invoice_date,\n currency: invoice.currency,\n dateOnly: invoice.dateOnly,\n submitter: invoice.submitter,\n email: invoice.email,\n serialNo: item.serial_no || String(idx + 1),\n description: item.description || '',\n hsnCode: item.hsn_code || '',\n quantity: parseFloat(item.quantity) || 0,\n unitPrice: parseFloat(item.unit_price) || 0,\n lineTotal: lineTotal,\n cgstPct: item.cgst_pct || '',\n sgstPct: item.sgst_pct || '',\n lineTotalMatch,\n invoiceTotal: parseFloat(invoice.total_amount) || 0\n }\n};"
},
"typeVersion": 2
},
{
"id": "c762da7e-055e-4f93-aec5-8e4255b4eca0",
"name": "7. Sheets \u2014 Log Each Line Item",
"type": "n8n-nodes-base.googleSheets",
"position": [
752,
272
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $json.dateOnly }}",
"SR No": "={{ $json.serialNo }}",
"CGST %": "={{ $json.cgstPct }}",
"SGST %": "={{ $json.sgstPct }}",
"Vendor": "={{ $json.vendor }}",
"HSN Code": "={{ $json.hsnCode }}",
"Quantity": "={{ $json.quantity }}",
"Invoice No": "={{ $json.invoiceNo }}",
"Line Total": "={{ $json.lineTotal }}",
"Unique Key": "={{ $json.uniqueKey }}",
"Unit Price": "={{ $json.unitPrice }}",
"Description": "={{ $json.description }}"
},
"schema": [
{
"id": "Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoice No",
"type": "string",
"display": true,
"required": false,
"displayName": "Invoice No",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Vendor",
"type": "string",
"display": true,
"required": false,
"displayName": "Vendor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "SR No",
"type": "string",
"display": true,
"required": false,
"displayName": "SR No",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "HSN Code",
"type": "string",
"display": true,
"required": false,
"displayName": "HSN Code",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Quantity",
"type": "string",
"display": true,
"required": false,
"displayName": "Quantity",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Unit Price",
"type": "string",
"display": true,
"required": false,
"displayName": "Unit Price",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Line Total",
"type": "string",
"display": true,
"required": false,
"displayName": "Line Total",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "CGST %",
"type": "string",
"display": true,
"required": false,
"displayName": "CGST %",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "SGST %",
"type": "string",
"display": true,
"required": false,
"displayName": "SGST %",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Unique Key",
"type": "string",
"display": true,
"required": false,
"displayName": "Unique Key",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Line Items"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_INVOICE_LOG_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "ef4c76a0-45d8-4b78-9880-f06d905a189b",
"name": "8. Aggregate \u2014 Collect All Line Items",
"type": "n8n-nodes-base.aggregate",
"position": [
992,
272
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "allItems"
},
"typeVersion": 1
},
{
"id": "df9dfbd4-69c7-4320-993a-7468a5d0051b",
"name": "9. Code \u2014 Verify Totals and Build Report",
"type": "n8n-nodes-base.code",
"position": [
1232,
272
],
"parameters": {
"jsCode": "const all = $input.first().json.allItems || [];\nconst first = all[0] || {};\nconst currency = (first.currency || 'INR').split('\u2014')[0].trim();\nconst symbol = currency === 'USD' ? '$' : currency === 'EUR' ? '\u20ac' : currency === 'GBP' ? '\u00a3' : 'Rs ';\n\nconst sumOfLineTotals = all.reduce((s, i) => s + parseFloat(i.lineTotal || 0), 0);\nconst invoiceTotal = parseFloat(first.invoiceTotal || 0);\nconst difference = Math.abs(sumOfLineTotals - invoiceTotal);\nconst totalVerified = difference < 1.0;\n\nconst fmtNum = (n) => Number(n || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n\nconst lineRows = all.map((item, i) =>\n '<tr>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;color:#1a1a2e;\">' + (item.serialNo || (i+1)) + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;color:#333;\">' + (item.description || '') + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;text-align:center;color:#666;\">' + (item.hsnCode || '-') + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;text-align:right;color:#333;\">' + (item.quantity || 0) + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;text-align:right;color:#333;\">' + symbol + fmtNum(item.unitPrice) + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;text-align:right;font-weight:600;color:#1a1a2e;\">' + symbol + fmtNum(item.lineTotal) + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;text-align:center;color:#666;\">' + (item.cgstPct || '-') + '</td>' +\n '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-size:12px;text-align:center;color:#666;\">' + (item.sgstPct || '-') + '</td>' +\n '</tr>'\n).join('');\n\nconst verifyBanner = totalVerified\n ? '<div style=\"background:#e8f5e9;border-left:4px solid #43a047;padding:12px 18px;border-radius:0 6px 6px 0;margin-bottom:20px;\"><strong style=\"color:#1b5e20;\">Total Verified</strong> \u2014 Sum of line items (' + symbol + fmtNum(sumOfLineTotals) + ') matches invoice total (' + symbol + fmtNum(invoiceTotal) + ').</div>'\n : '<div style=\"background:#fff8e1;border-left:4px solid #f57f17;padding:12px 18px;border-radius:0 6px 6px 0;margin-bottom:20px;\"><strong style=\"color:#e65100;\">Total Mismatch</strong> \u2014 Sum of line items (' + symbol + fmtNum(sumOfLineTotals) + ') differs from invoice total (' + symbol + fmtNum(invoiceTotal) + ') by ' + symbol + fmtNum(difference) + '. Some line items may be missing from the extraction.</div>';\n\nconst htmlEmail = '<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head>' +\n'<body style=\"font-family:Arial,sans-serif;max-width:900px;margin:0 auto;background:#f5f5f5;\">' +\n\n'<div style=\"background:#1a1a2e;padding:24px 28px;border-radius:8px 8px 0 0;\">' +\n'<h1 style=\"color:#fff;font-size:20px;margin:0;\">Invoice Extraction Report</h1>' +\n'<p style=\"color:#90caf9;font-size:13px;margin:6px 0 0;\">Processed by ' + (first.submitter || '') + ' on ' + (first.dateOnly || '') + '</p>' +\n'</div>' +\n\n'<div style=\"background:#263159;padding:10px 24px;display:flex;gap:24px;flex-wrap:wrap;\">' +\n'<span style=\"color:#fff;font-size:13px;\">' + (first.vendor || '') + '</span>' +\n'<span style=\"color:#fff;font-size:13px;\">' + (first.invoiceNo || '') + '</span>' +\n'<span style=\"color:#fff;font-size:13px;\">' + (first.invoiceDate || '') + '</span>' +\n'<span style=\"color:#90caf9;font-size:13px;font-weight:700;\">' + symbol + fmtNum(invoiceTotal) + '</span>' +\n'</div>' +\n\n'<div style=\"background:#fff;padding:20px 24px;\">' +\n\nverifyBanner +\n\n'<h2 style=\"font-size:15px;color:#1a1a2e;border-bottom:2px solid #eee;padding-bottom:8px;margin:0 0 14px;\">Line Items (' + all.length + ' extracted)</h2>' +\n'<div style=\"overflow-x:auto;\">' +\n'<table style=\"width:100%;border-collapse:collapse;font-size:13px;min-width:600px;\">' +\n'<thead><tr style=\"background:#f0f4ff;\">' +\n'<th style=\"padding:9px 10px;text-align:left;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">SR</th>' +\n'<th style=\"padding:9px 10px;text-align:left;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">Description</th>' +\n'<th style=\"padding:9px 10px;text-align:center;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">HSN</th>' +\n'<th style=\"padding:9px 10px;text-align:right;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">Qty</th>' +\n'<th style=\"padding:9px 10px;text-align:right;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">Unit Price</th>' +\n'<th style=\"padding:9px 10px;text-align:right;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">Line Total</th>' +\n'<th style=\"padding:9px 10px;text-align:center;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">CGST</th>' +\n'<th style=\"padding:9px 10px;text-align:center;color:#444;border-bottom:2px solid #ddd;font-size:11px;\">SGST</th>' +\n'</tr></thead><tbody>' + lineRows + '</tbody>' +\n'<tfoot><tr style=\"background:#f0f4ff;\">' +\n'<td colspan=\"5\" style=\"padding:10px;text-align:right;font-weight:700;font-size:13px;color:#1a1a2e;\">TOTAL</td>' +\n'<td style=\"padding:10px;text-align:right;font-weight:700;font-size:14px;color:#1a1a2e;\">' + symbol + fmtNum(sumOfLineTotals) + '</td>' +\n'<td colspan=\"2\"></td>' +\n'</tr></tfoot>' +\n'</table></div>' +\n\n'</div>' +\n\n'<div style=\"background:#1a1a2e;padding:12px 24px;text-align:center;border-radius:0 0 8px 8px;\">' +\n'<p style=\"color:#90caf9;font-size:11px;margin:0;\">Invoice Processor \u2014 n8n + ChatGPT AI + Google Sheets</p>' +\n'</div>' +\n\n'</body></html>';\n\nreturn [{\n json: {\n email: first.email,\n invoiceNo: first.invoiceNo,\n vendor: first.vendor,\n lineCount: all.length,\n invoiceTotal,\n sumOfLineTotals: +sumOfLineTotals.toFixed(2),\n totalVerified,\n difference: +difference.toFixed(2),\n htmlEmail,\n emailSubject: (totalVerified ? '[Verified] ' : '[Check Totals] ') + 'Invoice Extracted \u2014 ' + (first.invoiceNo || '') + ' | ' + (first.vendor || '') + ' | ' + symbol + fmtNum(invoiceTotal)\n }\n}];"
},
"typeVersion": 2
},
{
"id": "48075d92-d433-4298-be80-3024d5711bb4",
"name": "10. Gmail \u2014 Send Extraction Report",
"type": "n8n-nodes-base.gmail",
"position": [
1472,
272
],
"parameters": {
"sendTo": "={{ $json.email }}",
"message": "={{ $json.htmlEmail }}",
"options": {
"senderName": "Invoice Processor"
},
"subject": "={{ $json.emailSubject }}"
},
"typeVersion": 2.1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "6cead021-dfe5-4952-a90a-267e43501e37",
"nodeGroups": [],
"connections": {
"5b. Split Out \u2014 Line Items": {
"main": [
[
{
"node": "6. Code \u2014 Add Unique Key and Line Metadata",
"type": "main",
"index": 0
}
]
]
},
"1. Form \u2014 Upload Invoice PDF": {
"main": [
[
{
"node": "2. Extract From File \u2014 PDF to Text",
"type": "main",
"index": 0
}
]
]
},
"7. Sheets \u2014 Log Each Line Item": {
"main": [
[
{
"node": "8. Aggregate \u2014 Collect All Line Items",
"type": "main",
"index": 0
}
]
]
},
"2. Extract From File \u2014 PDF to Text": {
"main": [
[
{
"node": "3. HTTP \u2014 AI Extract Invoice Fields",
"type": "main",
"index": 0
}
]
]
},
"3. HTTP \u2014 AI Extract Invoice Fields": {
"main": [
[
{
"node": "4. Code \u2014 Parse Invoice JSON Safely",
"type": "main",
"index": 0
}
]
]
},
"4. Code \u2014 Parse Invoice JSON Safely": {
"main": [
[
{
"node": "5a. Sheets \u2014 Log Invoice Header",
"type": "main",
"index": 0
},
{
"node": "5b. Split Out \u2014 Line Items",
"type": "main",
"index": 0
}
]
]
},
"8. Aggregate \u2014 Collect All Line Items": {
"main": [
[
{
"node": "9. Code \u2014 Verify Totals and Build Report",
"type": "main",
"index": 0
}
]
]
},
"9. Code \u2014 Verify Totals and Build Report": {
"main": [
[
{
"node": "10. Gmail \u2014 Send Extraction Report",
"type": "main",
"index": 0
}
]
]
},
"6. Code \u2014 Add Unique Key and Line Metadata": {
"main": [
[
{
"node": "7. Sheets \u2014 Log Each Line Item",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects an invoice PDF via an n8n Form, extracts text from the PDF, uses OpenAI to convert it into structured invoice JSON, logs invoice headers and line items to Google Sheets, validates totals, and emails a formatted extraction report via Gmail. Receives an…
Source: https://n8n.io/workflows/16643/ — 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.
This workflow is for eCommerce researchers, affiliate marketers, and anyone who needs to compare product listings across sites like Amazon. It’s perfect for quickly identifying top product picks based
Accountants and office managers who want to automatically classify and organize documents in Google Drive.
This workflow processes passport images submitted through a form, extracts structured data using OpenAI OCR, and generates QR codes with the extracted information. Results are displayed on the form co
This n8n template automates scraping content from Skool communities using the Olostep API. It collects structured data from Skool pages and stores it in a clean format, making it easy to analyze commu
Domain Outbound Machine is an n8n workflow designed to fully automate the domain sales process: lead generation, email extraction, personalized outreach, and automated email sending. It also stores ex