{
  "name": "Parse E-Invoices from Gmail to Google Sheets",
  "nodes": [
    {
      "parameters": {},
      "id": "aea27ebd-49c8-467e-a5ac-21539fc9bc1d",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        32,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-spreadsheet",
              "name": "spreadsheetId",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-sheet-name",
              "name": "sheetName",
              "value": "Invoices",
              "type": "string"
            },
            {
              "id": "cfg-gmail-query",
              "name": "gmailSearchQuery",
              "value": "has:attachment filename:pdf OR filename:xml",
              "type": "string"
            },
            {
              "id": "cfg-gmail-limit",
              "name": "gmailLimit",
              "value": 20,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "bfa04cf4-4404-4ab4-9f04-04a404a404a4",
      "name": "Set Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        272,
        320
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "limit": "={{ $('Set Config').first().json.gmailLimit }}",
        "simple": false,
        "filters": {
          "q": "={{ $('Set Config').first().json.gmailSearchQuery }}"
        },
        "options": {
          "dataPropertyAttachmentsPrefixName": "attachment_",
          "downloadAttachments": true
        }
      },
      "id": "ed0a12fa-145d-4a1f-8043-7eb11938ad7d",
      "name": "Gmail: Search Emails",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "webhookId": "5990ff65-80ae-45af-8732-4dd3be1b2a4c",
      "position": [
        512,
        320
      ],
      "credentials": {
        "gmailOAuth2": {
          "id": "",
          "name": "Gmail account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const results = [];\nfor (const item of $input.all()) {\n  if (!item?.binary) continue;\n\n  for (const [key, binaryData] of Object.entries(item.binary)) {\n    if (!binaryData) continue;\n\n    const filename = String(binaryData.fileName || '').toLowerCase();\n    const mimeType = String(binaryData.mimeType || '').toLowerCase();\n\n    const isXml = mimeType.includes('xml') || filename.endsWith('.xml');\n    const isPdf = mimeType.includes('pdf') || filename.endsWith('.pdf');\n\n    if (isXml || isPdf) {\n      const jsonData = item.json || {};\n      results.push({\n        json: {\n          hasAttachment: true,\n          binaryProperty: 'data',\n          filename: binaryData.fileName || 'unknown',\n          mimeType: binaryData.mimeType || 'application/octet-stream',\n          detectedCountry: 'DE',\n          detectedFormat: isXml ? 'xrechnung' : 'zugferd',\n          emailSubject: jsonData.Subject || jsonData.subject || '',\n          emailFrom: jsonData.From || jsonData.from || '',\n          emailDate: jsonData.Date || jsonData.date || new Date().toISOString(),\n          emailId: jsonData.id || jsonData.messageId || ''\n        },\n        binary: { data: binaryData }\n      });\n    }\n  }\n}\n\nif (results.length === 0) {\n  results.push({ json: { hasAttachment: false, reason: 'No PDF/XML attachments' } });\n}\n\nreturn results;"
      },
      "id": "a8a968c0-ef7e-4f93-b74c-6b820327b5f7",
      "name": "Extract PDF/XML Attachments",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        752,
        320
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-has-attachment",
              "leftValue": "={{ $json.hasAttachment }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "52d5b3f7-6fac-4b15-8b2c-66e22fab5565",
      "name": "Has Attachments?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        992,
        320
      ]
    },
    {
      "parameters": {
        "operation": "parseAutoDetect",
        "options": {
          "filename": "={{ $json.filename }}",
          "includeWarnings": true
        }
      },
      "id": "3e710ffd-d9f1-49c5-9b00-d33e00b41952",
      "name": "Parse Invoice",
      "type": "n8n-nodes-invoice-api-xhub.invoiceXhub",
      "typeVersion": 1,
      "position": [
        1232,
        224
      ],
      "credentials": {
        "invoiceXhubApi": {
          "id": "",
          "name": "Invoice-api.xhub Account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-is-einvoice",
              "leftValue": "={{ $json.success }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "1bae3e1d-e5c6-4a0f-90c5-8a5357d89224",
      "name": "Is E-Invoice?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1472,
        224
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const p = $json;\nconst s = $('Extract PDF/XML Attachments').item.json;\nconst inv = p.invoice || {};\nconst seller = inv.seller || {};\nconst buyer = inv.buyer || {};\nconst bank = seller.bankAccount || {};\nconst items = Array.isArray(inv.items) ? inv.items : [];\nconst pt = inv.paymentTerms || {};\nconst cs = inv.countrySpecific || {};\nconst warnings = Array.isArray(p.warnings) ? p.warnings : [];\nconst sellerBank = bank.iban ? `${bank.iban}${bank.bic ? ' / ' + bank.bic : ''}${bank.bankName ? ' (' + bank.bankName + ')' : ''}` : '';\nconst lineItemsSummary = items.map(i => `${i.quantity || ''}x ${i.description || ''} @ ${i.unitPrice || ''}`).join(' | ').slice(0, 500);\nconst paymentTermsText = [pt.description, pt.dueDays ? `${pt.dueDays} Tage` : ''].filter(Boolean).join(' — ');\nreturn { json: {\n  invoiceNumber: inv.invoiceNumber || '',\n  invoiceDate: inv.issueDate || '',\n  dueDate: inv.dueDate || '',\n  sellerName: seller.name || '',\n  sellerVatId: seller.vatId || seller.taxId || '',\n  sellerStreet: seller.street || '',\n  sellerCity: seller.city || '',\n  sellerPostalCode: seller.postalCode || '',\n  sellerCountry: seller.countryCode || '',\n  sellerEmail: seller.email || '',\n  sellerBank,\n  buyerName: buyer.name || '',\n  buyerVatId: buyer.vatId || buyer.taxId || '',\n  buyerStreet: buyer.street || '',\n  buyerCity: buyer.city || '',\n  buyerPostalCode: buyer.postalCode || '',\n  buyerEmail: buyer.email || '',\n  subtotal: inv.subtotal != null ? inv.subtotal : '',\n  totalVat: Array.isArray(inv.taxSummary) ? inv.taxSummary.reduce((sum, t) => sum + (Number(t.taxAmount) || 0), 0) : '',\n  total: inv.total != null ? inv.total : 0,\n  currency: inv.currency || 'EUR',\n  lineItemsCount: items.length,\n  lineItemsSummary,\n  paymentTerms: paymentTermsText,\n  notes: inv.notes || '',\n  buyerReference: cs.buyerReference || cs.leitwegId || '',\n  sourceFilename: s.filename,\n  sourceFormat: p.format || s.detectedFormat || '',\n  emailSubject: s.emailSubject,\n  emailFrom: s.emailFrom,\n  emailDate: s.emailDate,\n  documentHash: p.hash || '',\n  warningCount: warnings.length,\n  processedAt: new Date().toISOString()\n} };"
      },
      "id": "598849d5-aae4-4431-aba0-36c8284a3557",
      "name": "Transform for Sheets",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1712,
        128
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "invoiceNumber": "={{ $json.invoiceNumber }}",
            "invoiceDate": "={{ $json.invoiceDate }}",
            "dueDate": "={{ $json.dueDate }}",
            "sellerName": "={{ $json.sellerName }}",
            "sellerVatId": "={{ $json.sellerVatId }}",
            "sellerStreet": "={{ $json.sellerStreet }}",
            "sellerCity": "={{ $json.sellerCity }}",
            "sellerPostalCode": "={{ $json.sellerPostalCode }}",
            "sellerCountry": "={{ $json.sellerCountry }}",
            "sellerEmail": "={{ $json.sellerEmail }}",
            "sellerBank": "={{ $json.sellerBank }}",
            "buyerName": "={{ $json.buyerName }}",
            "buyerVatId": "={{ $json.buyerVatId }}",
            "buyerStreet": "={{ $json.buyerStreet }}",
            "buyerCity": "={{ $json.buyerCity }}",
            "buyerPostalCode": "={{ $json.buyerPostalCode }}",
            "buyerEmail": "={{ $json.buyerEmail }}",
            "subtotal": "={{ $json.subtotal }}",
            "totalVat": "={{ $json.totalVat }}",
            "total": "={{ $json.total }}",
            "currency": "={{ $json.currency }}",
            "lineItemsCount": "={{ $json.lineItemsCount }}",
            "lineItemsSummary": "={{ $json.lineItemsSummary }}",
            "paymentTerms": "={{ $json.paymentTerms }}",
            "notes": "={{ $json.notes }}",
            "buyerReference": "={{ $json.buyerReference }}",
            "sourceFilename": "={{ $json.sourceFilename }}",
            "sourceFormat": "={{ $json.sourceFormat }}",
            "emailSubject": "={{ $json.emailSubject }}",
            "emailFrom": "={{ $json.emailFrom }}",
            "emailDate": "={{ $json.emailDate }}",
            "documentHash": "={{ $json.documentHash }}",
            "warningCount": "={{ $json.warningCount }}",
            "processedAt": "={{ $json.processedAt }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "invoiceNumber",
              "displayName": "invoiceNumber",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invoiceDate",
              "displayName": "invoiceDate",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "dueDate",
              "displayName": "dueDate",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerName",
              "displayName": "sellerName",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerVatId",
              "displayName": "sellerVatId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerStreet",
              "displayName": "sellerStreet",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerCity",
              "displayName": "sellerCity",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerPostalCode",
              "displayName": "sellerPostalCode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerCountry",
              "displayName": "sellerCountry",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerEmail",
              "displayName": "sellerEmail",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sellerBank",
              "displayName": "sellerBank",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerName",
              "displayName": "buyerName",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerVatId",
              "displayName": "buyerVatId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerStreet",
              "displayName": "buyerStreet",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerCity",
              "displayName": "buyerCity",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerPostalCode",
              "displayName": "buyerPostalCode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerEmail",
              "displayName": "buyerEmail",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "subtotal",
              "displayName": "subtotal",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "totalVat",
              "displayName": "totalVat",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "total",
              "displayName": "total",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "currency",
              "displayName": "currency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "lineItemsCount",
              "displayName": "lineItemsCount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "lineItemsSummary",
              "displayName": "lineItemsSummary",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "paymentTerms",
              "displayName": "paymentTerms",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "displayName": "notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "buyerReference",
              "displayName": "buyerReference",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sourceFilename",
              "displayName": "sourceFilename",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "sourceFormat",
              "displayName": "sourceFormat",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "emailSubject",
              "displayName": "emailSubject",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "emailFrom",
              "displayName": "emailFrom",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "emailDate",
              "displayName": "emailDate",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "documentHash",
              "displayName": "documentHash",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "warningCount",
              "displayName": "warningCount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "processedAt",
              "displayName": "processedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {}
      },
      "id": "a36041db-3f68-4305-b98f-114e607fd193",
      "name": "Google Sheets: Append",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1952,
        128
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {},
      "id": "4a082958-d959-426f-94bf-88fcbe71b8b6",
      "name": "No Attachments - Skip",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1232,
        448
      ]
    },
    {
      "parameters": {},
      "id": "1de612b9-e9ad-4420-8789-310c3aa40ece",
      "name": "Not E-Invoice - Skip",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1712,
        320
      ]
    },
    {
      "parameters": {
        "content": "## Bulk-parse e-invoices from Gmail\n\nManual trigger → Gmail search → parse XMLs and PDFs with `invoice-api.xhub` → flatten each result → append to Sheets. Re-run whenever; you change the query, not the workflow.\n\n**The Gmail query** lives in `Set Config`. Default is `has:attachment newer_than:30d` capped at 20 messages. Widen the date window, narrow the `from:`, or scope to a label depending on the run.\n\n**Output:** ~25 columns per invoice — seller, buyer, net/VAT/gross, line-item summary, source email fields, detected format. Missing columns are created on first append.",
        "height": 340,
        "width": 1820,
        "color": 1
      },
      "id": "e8f9a0b1-c2d3-44e5-f6a7-b8c9d0e1f2a3",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -16,
        -672
      ]
    },
    {
      "parameters": {
        "content": "## 1. Fetch & Filter Emails\nSearch Gmail and extract PDF / XML attachments.",
        "height": 300,
        "width": 1164
      },
      "id": "edb1e57d-7ebb-450a-8afe-45bdfe8f0aa2",
      "name": "Section: Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -16,
        240
      ]
    },
    {
      "parameters": {
        "content": "## 2. Parse E-Invoice\nAuto-detect format and extract structured data.",
        "height": 432,
        "width": 460
      },
      "id": "dc9a55ac-d9f6-45a5-9060-28a5abf78883",
      "name": "Section: Parse",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1184,
        160
      ]
    },
    {
      "parameters": {
        "content": "## 3. Transform & Log\nFlatten to a 25-column row and append to Google Sheets.",
        "height": 216,
        "width": 536
      },
      "id": "850dfc6a-9843-49c5-919a-7732597f1089",
      "name": "Section: Log",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1664,
        64
      ]
    },
    {
      "parameters": {
        "content": "⚠️ **Configure here:** Replace `your-spreadsheet-id`, set `sheetName`, and refine the `gmailSearchQuery` before activating.",
        "height": 100,
        "width": 300,
        "color": 3
      },
      "id": "warn-setconfig-04",
      "name": "Warning: Configure Set Config",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        64,
        48
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail: Search Emails": {
      "main": [
        [
          {
            "node": "Extract PDF/XML Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF/XML Attachments": {
      "main": [
        [
          {
            "node": "Has Attachments?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Attachments?": {
      "main": [
        [
          {
            "node": "Parse Invoice",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Attachments - Skip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Invoice": {
      "main": [
        [
          {
            "node": "Is E-Invoice?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is E-Invoice?": {
      "main": [
        [
          {
            "node": "Transform for Sheets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Not E-Invoice - Skip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transform for Sheets": {
      "main": [
        [
          {
            "node": "Google Sheets: Append",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config": {
      "main": [
        [
          {
            "node": "Gmail: Search Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}