This workflow corresponds to n8n.io template #13215 — we link there as the canonical source.
This workflow follows the Agent → Form Trigger 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": "4D3NONV7VkCOx2Rs",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Inbox2Ledger",
"tags": [
{
"id": "F4Qszas4M5o8aEXt",
"name": "Content Creation",
"createdAt": "2026-01-12T08:09:51.754Z",
"updatedAt": "2026-01-12T08:09:51.754Z"
}
],
"nodes": [
{
"id": "3ba62e63-5a3d-4cbc-a110-833bb0199d98",
"name": "Get Email Content",
"type": "n8n-nodes-base.gmail",
"position": [
-1472,
176
],
"parameters": {
"simple": false,
"filters": {
"labelIds": [
"INBOX"
],
"readStatus": "both",
"receivedAfter": "={{ (() => {\n const d = new Date($json['Date till which you want your mails to be summarized']);\n d.setDate(d.getDate() - 1);\n return d.toISOString().split('T')[0] + 'T00:00:00';\n})() }}"
},
"options": {},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "44797303-c942-44ac-a083-a02d1ed6b360",
"name": "Guardrail: Is Finance?",
"type": "@n8n/n8n-nodes-langchain.guardrails",
"position": [
-1152,
176
],
"parameters": {
"text": "={{ $('Get Email Content').item.json.text }} and {{ $json.headers.subject }}",
"guardrails": {
"topicalAlignment": {
"value": {
"prompt": "=You are a financial controller AI. \n\nSCOPE:\n- The text MUST contain evidence of a financial transaction, invoice, receipt, or bill.\n- It is ON-TOPIC if it mentions payments, amounts owed, subscription renewals with prices, or attached invoices.\n- It is OFF-TOPIC if it is a newsletter, a marketing email without a specific transaction, or a personal conversation.",
"threshold": 0.5
}
}
}
},
"typeVersion": 1
},
{
"id": "143a677b-1c72-4b0f-b5bc-bab4c0c3cd72",
"name": "IF (Guardrail Passed)",
"type": "n8n-nodes-base.if",
"position": [
-800,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "check-aligned",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.checks.triggered }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "6b7923e4-1b33-4154-8ce5-a93835dfa616",
"name": "Filter Finance Keywords",
"type": "n8n-nodes-base.filter",
"position": [
-576,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "filter-invoice",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Get Email Content').item.json.subject }}",
"rightValue": "Invoice"
},
{
"id": "filter-receipt",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Get Email Content').item.json.subject }}",
"rightValue": "Receipt"
},
{
"id": "filter-bill",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Get Email Content').item.json.subject }}",
"rightValue": "Bill"
},
{
"id": "filter-payment",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Get Email Content').item.json.subject }}",
"rightValue": "Payment Confirmation"
},
{
"id": "1468776a-505d-4693-a45d-6772b744e9b9",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Get Email Content').item.json.subject }}",
"rightValue": "Payment"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9ae08dc6-847e-417f-b7f6-2f62675799ce",
"name": "AI Agent (Email OCR)",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-240,
176
],
"parameters": {
"text": "={{ $('Get Email Content').item.json.text }}",
"options": {
"systemMessage": "=You are a Financial OCR Expert. Extract receipt data from the email body text.\n\nCRITICAL INSTRUCTIONS:\n1. Return ONLY a JSON object.\n2. Normalize dates to YYYY-MM-DD.\n3. Clean currency symbols from amounts (return numbers).\n\nREQUIRED JSON STRUCTURE:\n{\n \"vendor_name\": \"string\",\n \"invoice_date\": \"YYYY-MM-DD\",\n \"invoice_id\": \"string (or 'EMAIL-RECEIPT' if missing)\",\n \"total_amount\": number,\n \"tax_amount\": number,\n \"currency\": \"USD/EUR/GBP\",\n \"items_summary\": \"string (brief description)\",\n \"vendor_tax_id\": \"string or null\"\n}"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.9,
"continueOnFail": true
},
{
"id": "81c5e6d4-a43b-442c-b6f7-f24502830cdc",
"name": "Validate Extraction",
"type": "n8n-nodes-base.code",
"position": [
112,
176
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Check if AI extraction succeeded\nif ($json.error) {\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"AI_EXTRACTION_FAILED\",\n\t\t\terror_message: $json.error.message || \"AI extraction failed\",\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}\n\nlet data;\ntry {\n\tdata = typeof $json.output === 'string' ? JSON.parse($json.output) : $json.output;\n} catch (e) {\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"JSON_PARSE_ERROR\",\n\t\t\terror_message: e.message,\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}\n\n// Validation Logic\nconst required = ['vendor_name', 'total_amount', 'invoice_date'];\nconst missing = required.filter(field => !data[field]);\n\nif (missing.length > 0) {\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"MISSING_FIELDS\",\n\t\t\terror_message: `Missing: ${missing.join(', ')}`,\n\t\t\textracted_data: data,\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}\n\n// Amount Validation\nif (isNaN(parseFloat(data.total_amount))) {\n\treturn {\n\t\tjson: {\n\t\t\terror_occurred: true,\n\t\t\terror_type: \"INVALID_AMOUNT\",\n\t\t\terror_message: \"Total Amount is not a number\",\n\t\t\textracted_data: data,\n\t\t\ttimestamp: new Date().toISOString()\n\t\t}\n\t};\n}\n\nreturn {\n\tjson: {\n\t\terror_occurred: false,\n\t\tvalidated_data: data\n\t}\n};"
},
"typeVersion": 2
},
{
"id": "dc6ce290-b2d6-4ae9-9c34-1cb962b385a2",
"name": "Check for Errors",
"type": "n8n-nodes-base.if",
"position": [
336,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "check-error",
"operator": {
"type": "boolean",
"operation": "false"
},
"leftValue": "={{ $json.error_occurred }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "4b8ddd2d-2fcd-47c1-aa13-95c1b6c09394",
"name": "Apply Finance Rules",
"type": "n8n-nodes-base.code",
"position": [
560,
176
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const data = $json.validated_data || {};\n\n// Helper: normalize text\nconst norm = txt => (txt || \"\").toString().toLowerCase().trim();\n\n// Combine searchable text fields\nconst combinedText = [\n norm(data.vendor_name),\n norm(data.merchant_name),\n norm(data.description),\n norm(data.notes),\n norm(data.expense_type),\n].filter(Boolean).join(\" \");\n\n// Category rules (SIMPLE NAMES)\nconst CATEGORY_MAP = [\n { keys: [\"aws\", \"amazon\", \"amazon web services\", \"google cloud\", \"gcp\", \"digitalocean\", \"azure\", \"cloudflare\", \"heroku\"], category: \"Software and hosting\" },\n { keys: [\"stripe\", \"paypal\", \"razorpay\"], category: \"Payment processing fees\" },\n { keys: [\"uber\", \"ola\", \"lyft\", \"taxi\", \"cab\", \"airline\", \"flight\", \"train\", \"bus\", \"travel\"], category: \"Travel and meals\" },\n { keys: [\"wework\", \"regus\", \"coworking\", \"office rent\", \"rent\", \"lease\"], category: \"Rent and utilities\" },\n { keys: [\"electricity\", \"water bill\", \"gas bill\", \"utility\"], category: \"Rent and utilities\" },\n { keys: [\"stationery\", \"office supplies\", \"pens\", \"paper\"], category: \"Office supplies\" },\n { keys: [\"consultant\", \"contractor\", \"freelancer\", \"agency\", \"legal\", \"lawyer\", \"accountant\"], category: \"Professional services\" },\n { keys: [\"google ads\", \"facebook ads\", \"meta ads\", \"marketing\"], category: \"Advertising and marketing\" },\n { keys: [\"zoom\", \"slack\", \"notion\", \"asana\", \"github\", \"figma\", \"subscription\", \"saas\"], category: \"Subscriptions\" },\n { keys: [\"phone\", \"telecom\", \"airtel\", \"jio\", \"vodafone\"], category: \"Telecom\" },\n { keys: [\"laptop\", \"computer\", \"hardware\", \"printer\", \"equipment\"], category: \"Equipment\" },\n { keys: [\"salary\", \"payroll\", \"wages\"], category: \"Payroll\" },\n { keys: [\"tax\", \"gst\", \"vat\", \"license\"], category: \"Taxes and licenses\" },\n { keys: [\"insurance\", \"premium\"], category: \"Insurance\" },\n { keys: [\"bank fee\", \"bank charges\", \"neft\", \"rtgs\", \"imps\"], category: \"Bank charges\" },\n { keys: [\"meal\", \"lunch\", \"dinner\", \"restaurant\", \"cafe\"], category: \"Meals and entertainment\" },\n { keys: [\"training\", \"course\", \"udemy\", \"coursera\", \"workshop\"], category: \"Training and development\" }\n];\n\n// Determine category\nlet category = null;\n\nfor (const map of CATEGORY_MAP) {\n for (const key of map.keys) {\n if (combinedText.includes(key)) {\n category = map.category;\n break;\n }\n }\n if (category) break;\n}\n\n// MCC fallback\nif (!category && data.mcc) {\n const mcc = data.mcc.toString();\n if (mcc.startsWith(\"5\")) category = \"Travel and meals\";\n else if (mcc.startsWith(\"7\")) category = \"Professional services\";\n}\n\n// Vendor fallback\nif (!category && data.vendor_name) {\n const vendor = norm(data.vendor_name);\n if (vendor.includes(\"airbnb\")) category = \"Travel and meals\";\n else if (vendor.includes(\"amazon\") || vendor.includes(\"flipkart\")) category = \"Office supplies\";\n}\n\n// FINAL fallback \u2014 always filled\nif (!category) {\n category = \"Other expenses\";\n}\n\n// Return result\nreturn {\n json: {\n ...data,\n expense_category: category,\n case_id: `INV-${Date.now()}-${Math.random().toString(36).slice(2, 8).toUpperCase()}`,\n processed_at: new Date().toISOString()\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "22378f77-6fee-40d6-a3f6-c185855106b3",
"name": "Log to Invoices Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
784,
176
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "invoice_id",
"type": "string",
"display": true,
"required": false,
"displayName": "invoice_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "vendor_name",
"type": "string",
"display": true,
"required": false,
"displayName": "vendor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "invoice_date",
"type": "string",
"display": true,
"required": false,
"displayName": "invoice_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "total_amount",
"type": "string",
"display": true,
"required": false,
"displayName": "total_amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "currency",
"type": "string",
"display": true,
"required": false,
"displayName": "currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "tax_amount",
"type": "string",
"display": true,
"required": false,
"displayName": "tax_amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "gl_category",
"type": "string",
"display": true,
"required": false,
"displayName": "gl_category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "approval_status",
"type": "string",
"display": true,
"required": false,
"displayName": "approval_status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "case_id",
"type": "string",
"display": true,
"required": false,
"displayName": "case_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "items_summary",
"type": "string",
"display": true,
"required": false,
"displayName": "items_summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "vendor_tax_id",
"type": "string",
"display": true,
"required": false,
"displayName": "vendor_tax_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "processed_at",
"type": "string",
"display": true,
"required": false,
"displayName": "processed_at",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1260157166,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pHyqJsfwCIL-XrsWnE0WZsJPxtrd7DeXEGbrKMU_XJk/edit#gid=1260157166",
"cachedResultName": "Invoices"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1pHyqJsfwCIL-XrsWnE0WZsJPxtrd7DeXEGbrKMU_XJk",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pHyqJsfwCIL-XrsWnE0WZsJPxtrd7DeXEGbrKMU_XJk/edit?usp=drivesdk",
"cachedResultName": "Finance OCR"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.5,
"waitBetweenTries": 2000
},
{
"id": "c09451c0-a59f-4d60-bd6a-91308d937a33",
"name": "gpt 4o mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-240,
336
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {
"temperature": 0.2,
"responseFormat": "json_object"
}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"notesInFlow": false,
"typeVersion": 1.2
},
{
"id": "c3d58a12-9d19-4c92-a9d5-845e6b254a38",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1776,
-48
],
"parameters": {
"color": 6,
"width": 512,
"height": 544,
"content": "# Get all emails\n## Connect Gmail and fetch incoming messages or selected folders; batch or trigger-based retrieval ensures full inbox coverage for automatic processing."
},
"typeVersion": 1
},
{
"id": "912cd585-df40-42fc-a1f6-5ee0d4fd6c51",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-1136,
336
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1",
"cachedResultName": "gpt-4.1"
},
"options": {},
"responsesApiEnabled": false
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": false,
"typeVersion": 1.3
},
{
"id": "6acc74c6-538f-40de-a356-226cde040aee",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2544,
-48
],
"parameters": {
"width": 688,
"height": 544,
"content": "Inbox2Ledger is an end-to-end **n8n template** that turns a noisy finance inbox into a clean, structured ledger. It fetches emails, uses AI guardrails to keep only finance-relevant messages, extracts invoice/receipt fields via an OCR-style agent, validates and auto-categorizes each expense, generates a unique case ID, and appends the result to a Google Sheet for accounting or downstream automations.\n\n\n## How it works\nTriggered by a form submission or scheduled fetch, the workflow pulls emails, applies AI guardrails and keyword filters to isolate invoices and receipts, then runs an LLM-powered extraction to return normalized JSON (vendor, date, invoice_id, amounts, currency, items). A validation step checks formats and required fields; categorization rules assign GL categories and generate a unique case ID. Validated records are appended to a Google Sheet and are ready for downstream processes.\n\n\n## Quick Setup Guide\n\ud83d\udc49 [Demo & Setup Video](https://drive.google.com/file/d/1OeDKjgO0a9yXMMShGTO08JGJDzvc9wl8/view?usp=sharing)\n1. Import the template into **n8n**\n2. Connect and authorize credentials: Gmail, Google Sheets, OpenAI (or preferred LLM)\n3. Update the Google Sheet ID / sheet `gid` if using your own sheet\n4. *(Optional)* Adjust the Guardrail `topicalAlignment` threshold or filter keywords\n5. Test using the form trigger or a single email, then enable the workflow\n\n---"
},
"typeVersion": 1
},
{
"id": "ad53c100-afae-4642-80d3-a54a7fc6f15b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1216,
-48
],
"parameters": {
"color": 6,
"width": 848,
"height": 544,
"content": "# Filter finance emails\n## Apply AI guardrails and keyword filters to isolate invoices, receipts, and payments; reduce false positives and route only finance messages."
},
"typeVersion": 1
},
{
"id": "fd07a032-91ce-44c4-9bb3-dc59595368bf",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
-48
],
"parameters": {
"color": 6,
"width": 1360,
"height": 544,
"content": "# Extract details and log\n## Use LLM/OCR agent to extract vendor, date, invoice_id, amounts; validate entries, categorize expenses, and append structured rows to Google Sheets."
},
"typeVersion": 1
},
{
"id": "15ad6c48-520c-41e0-9ad0-1c9da8bce60f",
"name": "Enter Date till which you want email to be fetched",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1680,
176
],
"parameters": {
"options": {},
"formTitle": "AI Financial Mail Detector and Summarizer",
"formFields": {
"values": [
{
"fieldType": "date",
"fieldLabel": "Date till which you want your mails to be summarized",
"requiredField": true
}
]
}
},
"typeVersion": 2.3
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "0dea5173-ae7c-4e85-b9b6-57b8a861e051",
"connections": {
"gpt 4o mini": {
"ai_languageModel": [
[
{
"node": "AI Agent (Email OCR)",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Check for Errors": {
"main": [
[
{
"node": "Apply Finance Rules",
"type": "main",
"index": 0
}
]
]
},
"Get Email Content": {
"main": [
[
{
"node": "Guardrail: Is Finance?",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Guardrail: Is Finance?",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Apply Finance Rules": {
"main": [
[
{
"node": "Log to Invoices Sheet",
"type": "main",
"index": 0
}
]
]
},
"Validate Extraction": {
"main": [
[
{
"node": "Check for Errors",
"type": "main",
"index": 0
}
]
]
},
"AI Agent (Email OCR)": {
"main": [
[
{
"node": "Validate Extraction",
"type": "main",
"index": 0
}
]
]
},
"IF (Guardrail Passed)": {
"main": [
[
{
"node": "Filter Finance Keywords",
"type": "main",
"index": 0
}
]
]
},
"Guardrail: Is Finance?": {
"main": [
[
{
"node": "IF (Guardrail Passed)",
"type": "main",
"index": 0
}
]
]
},
"Filter Finance Keywords": {
"main": [
[
{
"node": "AI Agent (Email OCR)",
"type": "main",
"index": 0
}
]
]
},
"Enter Date till which you want email to be fetched": {
"main": [
[
{
"node": "Get Email Content",
"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.
gmailOAuth2googleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Inbox2Ledger is an end-to-end n8n template that turns a noisy finance inbox into a clean, structured ledger. It fetches emails, uses AI guardrails to keep only finance-relevant messages, extracts invoice/receipt fields via an OCR-style agent, validates and auto-categorizes each…
Source: https://n8n.io/workflows/13215/ — 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 n8n workflow template automates your lead generation and follow-up process using AI. It captures leads through a form, enriches them with company data, classifies them into different categories,
The workflow runs every hour with a randomized delay of 5–20 minutes to help distribute load. It records the exact date and time a lead is emailed so you can track outreach. Follow-ups are automatical
This workflow is for hotel managers, travel agencies, and hospitality teams who receive booking requests via email. It eliminates the need for manual data entry by automatically parsing emails and att
This n8n workflow automates turning short user ideas into production-ready real-estate marketing assets (photorealistic images and optional 360° videos). A form submission seeds a prompt board → an LL
Transform your manual hiring process into an intelligent evaluation system that saves 15-20 minutes per candidate! This workflow automates the entire candidate assessment pipeline - from CSV/XLSX uplo