{
  "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
          }
        ]
      ]
    }
  }
}