{
  "name": "Archive E-Invoices from Email to Google Drive",
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "has:attachment filename:pdf OR filename:xml"
        },
        "options": {
          "dataPropertyAttachmentsPrefixName": "attachment_",
          "downloadAttachments": true
        }
      },
      "id": "f0f88768-077a-4a48-8d18-4e685f1475bd",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.3,
      "position": [
        32,
        320
      ],
      "credentials": {
        "gmailOAuth2": {
          "id": "",
          "name": "Gmail account"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-drive-folder",
              "name": "driveFolderId",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-spreadsheet",
              "name": "spreadsheetId",
              "value": "",
              "type": "string"
            },
            {
              "id": "cfg-sheet-name",
              "name": "sheetName",
              "value": "Sheet1",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "bfa01cf1-1101-4ab1-9f01-01a101a101a1",
      "name": "Set Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        272,
        320
      ]
    },
    {
      "parameters": {
        "jsCode": "const results = [];\nfor (const item of $input.all()) {\n  if (!item?.binary) continue;\n  for (const [key, binaryData] of Object.entries(item.binary)) {\n    if (!binaryData) continue;\n    const filename = String(binaryData.fileName || '').toLowerCase();\n    const mimeType = String(binaryData.mimeType || '').toLowerCase();\n    const isXml = mimeType.includes('xml') || filename.endsWith('.xml');\n    const isPdf = mimeType.includes('pdf') || filename.endsWith('.pdf');\n    if (isXml || isPdf) {\n      results.push({\n        json: {\n          hasAttachment: true,\n          binaryProperty: 'data',\n          originalFilename: binaryData.fileName || 'unknown',\n          mimeType: binaryData.mimeType || 'application/octet-stream',\n          emailSubject: item.json.Subject || item.json.subject || '',\n          emailFrom: item.json.From || item.json.from || '',\n          emailDate: item.json.Date || item.json.date || new Date().toISOString()\n        },\n        binary: { data: binaryData }\n      });\n    }\n  }\n}\nif (results.length === 0) {\n  results.push({ json: { hasAttachment: false } });\n}\nreturn results;"
      },
      "id": "37719703-323a-43d2-86cc-8312c819a212",
      "name": "Extract PDF/XML Attachments",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        512,
        320
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-attachment",
              "leftValue": "={{ $json.hasAttachment }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "cf68c65d-bb32-482a-8320-c2cb32a3a841",
      "name": "Has Attachments?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        752,
        320
      ]
    },
    {
      "parameters": {
        "operation": "parseAutoDetect",
        "options": {
          "filename": "={{ $json.originalFilename }}",
          "includeWarnings": true
        }
      },
      "id": "0e8b51aa-070b-4866-afa5-16ca83576847",
      "name": "Parse Invoice (Auto-Detect)",
      "type": "n8n-nodes-invoice-api-xhub.invoiceXhub",
      "typeVersion": 1,
      "position": [
        992,
        224
      ],
      "credentials": {
        "invoiceXhubApi": {
          "id": "",
          "name": "Invoice-api.xhub Account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-parse-ok",
              "leftValue": "={{ $json.success }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "e7ed0344-f290-4f13-bd0c-a12837255739",
      "name": "Parse OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1232,
        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(' — ');\nconst invoiceNumber = inv.invoiceNumber || '';\nconst invoiceDate = inv.issueDate || inv.invoiceDate || new Date().toISOString().split('T')[0];\nconst origFile = s.originalFilename || 'unknown';\nconst ext = origFile.split('.').pop() || 'xml';\nconst safeNumber = invoiceNumber.replace(/[^a-zA-Z0-9_-]/g, '_') || 'UNKNOWN';\nconst safeSeller = (seller.name || 'Unknown').replace(/[^a-zA-Z0-9_-]/g, '_').substring(0, 30);\nconst newFilename = `${invoiceDate}_${safeSeller}_${safeNumber}.${ext}`;\nconst monthFolder = invoiceDate.substring(0, 7);\nreturn {\n  json: {\n    newFilename,\n    monthFolder,\n    invoiceNumber,\n    invoiceDate,\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    format: p.format || 'unknown',\n    emailSubject: s.emailSubject || '',\n    emailFrom: typeof s.emailFrom === 'string' ? s.emailFrom : (s.emailFrom?.text || ''),\n    emailDate: s.emailDate || '',\n    documentHash: p.hash || '',\n    warningCount: warnings.length,\n    processedAt: new Date().toISOString()\n  },\n  binary: $('Extract PDF/XML Attachments').item.binary\n};"
      },
      "id": "0029b16b-ac97-427a-a643-51a390a84d53",
      "name": "Rename by Invoice Number",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1472,
        128
      ]
    },
    {
      "parameters": {
        "name": "={{ $json.newFilename }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $('Set Config').first().json.driveFolderId }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "5e334b32-d1db-486e-90f8-5a5059b80d6a",
      "name": "Google Drive: Upload",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1712,
        128
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "",
          "name": "Google Drive account"
        }
      }
    },
    {
      "parameters": {},
      "id": "b4666863-4b4d-42f3-a946-ac2527ef0eab",
      "name": "No Attachments",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        992,
        448
      ]
    },
    {
      "parameters": {},
      "id": "8a31727b-48cd-4895-8225-9707542bd14e",
      "name": "Not an E-Invoice",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1472,
        352
      ]
    },
    {
      "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": {
            "newFilename": "={{ $json.newFilename }}",
            "monthFolder": "={{ $json.monthFolder }}",
            "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 }}",
            "format": "={{ $json.format }}",
            "emailSubject": "={{ $json.emailSubject }}",
            "emailFrom": "={{ $json.emailFrom }}",
            "emailDate": "={{ $json.emailDate }}",
            "documentHash": "={{ $json.documentHash }}",
            "warningCount": "={{ $json.warningCount }}",
            "processedAt": "={{ $json.processedAt }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "newFilename",
              "displayName": "newFilename",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "monthFolder",
              "displayName": "monthFolder",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "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": "format",
              "displayName": "format",
              "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": "f0de7b5d-6bbb-481d-8698-03d1d22e24d0",
      "name": "Google Sheets: Log Invoice",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1712,
        320
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "content": "## Inbound e-invoice archive\n\nGmail attachments → `invoice-api.xhub` parse → Drive (renamed, monthly folders) + Sheet log. Non-invoices are skipped; unparseable files log a note and don't block the batch.\n\n**Sheet columns written on each run:** `newFilename`, `monthFolder`, `invoiceNumber`, `invoiceDate`, `sellerName`, `buyerName`, `total`, `currency`, `format`. Missing columns are created automatically.\n\n**Configure in `Set Config`:** Drive folder ID, Spreadsheet ID. Default `sheetName` is `Sheet1`. Only emails arriving after activation are processed — no backlog import.",
        "height": 360,
        "width": 1560,
        "color": 1
      },
      "id": "a3d8e1f2-7b4c-49a6-8e15-d2f0c3b1a492",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -48,
        -656
      ]
    },
    {
      "parameters": {
        "content": "## 1. Fetch & Filter Emails\nMonitor Gmail for new emails with PDF or XML attachments.",
        "height": 288,
        "width": 940
      },
      "id": "78c02dfc-dbef-411b-ab80-310d158efaa2",
      "name": "Section: Fetch & Filter",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -32,
        240
      ]
    },
    {
      "parameters": {
        "content": "## 2. Auto-Detect & Parse\nAuto-detect format and extract structured invoice data.",
        "height": 256,
        "width": 440
      },
      "id": "8a55761d-c0b8-48f7-805a-dbfa88a39286",
      "name": "Section: Parse",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        944,
        144
      ]
    },
    {
      "parameters": {
        "content": "## 3. Rename, Upload & Log\nRename by invoice metadata, upload to Drive, append to Sheets.",
        "height": 436,
        "width": 490
      },
      "id": "22bd5989-6167-4e56-8c17-7d310940db2c",
      "name": "Section: Archive",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1424,
        48
      ]
    },
    {
      "parameters": {
        "content": "⚠️ **Configure here:** Replace `your-folder-id`, `your-spreadsheet-id`, and (if needed) the `sheetName` with your real values before activating.",
        "height": 100,
        "width": 280,
        "color": 3
      },
      "id": "warn-setconfig-01",
      "name": "Warning: Configure Set Config",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        80,
        80
      ]
    }
  ],
  "connections": {
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF/XML Attachments": {
      "main": [
        [
          {
            "node": "Has Attachments?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Attachments?": {
      "main": [
        [
          {
            "node": "Parse Invoice (Auto-Detect)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Invoice (Auto-Detect)": {
      "main": [
        [
          {
            "node": "Parse OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse OK?": {
      "main": [
        [
          {
            "node": "Rename by Invoice Number",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Not an E-Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rename by Invoice Number": {
      "main": [
        [
          {
            "node": "Google Drive: Upload",
            "type": "main",
            "index": 0
          },
          {
            "node": "Google Sheets: Log Invoice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config": {
      "main": [
        [
          {
            "node": "Extract PDF/XML Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}