AutomationFlowsEmail & Gmail › Inbox Attachment Organizer

Inbox Attachment Organizer

Inbox-Attachment-Organizer. Uses googleSheets, gmail, telegram, lmChatGroq. Event-driven trigger; 51 nodes.

Event trigger★★★★★ complexityAI-powered51 nodesGoogle SheetsGmailTelegramGroq ChatOutput Parser StructuredGoogle DriveExecute Workflow TriggerChain Llm
Email & Gmail Trigger: Event Nodes: 51 Complexity: ★★★★★ AI nodes: yes Added:

This workflow follows the Chainllm → Execute Workflow 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 →

Download .json
{
  "nodes": [
    {
      "parameters": {
        "content": "Sender whitelist.\nEdit the JS array to\nadd/remove senders.\n\n",
        "height": 80,
        "width": 150
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1744,
        208
      ],
      "id": "931680a0-7cc1-4776-b776-a1621bad2efe",
      "name": "Sticky Note17"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "GOOGLE_SHEET_ID_BILLING_LEDGER",
          "mode": "list",
          "cachedResultName": "Billing_Ledger",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/GOOGLE_SHEET_ID_BILLING_LEDGER/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/GOOGLE_SHEET_ID_BILLING_LEDGER/edit#gid=0"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [
            {
              "id": "accounting_category",
              "displayName": "accounting_category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invoice_status",
              "displayName": "invoice_status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invoice_number",
              "displayName": "invoice_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "attachment_count",
              "displayName": "attachment_count",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "email_id",
              "displayName": "email_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "counterparty_name",
              "displayName": "counterparty_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invoice_date",
              "displayName": "invoice_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "subtotal_amount",
              "displayName": "subtotal_amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "currency_code",
              "displayName": "currency_code",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "payment_method",
              "displayName": "payment_method",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "due_date_or_payment_terms",
              "displayName": "due_date_or_payment_terms",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "payment_reference",
              "displayName": "payment_reference",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "date_paid",
              "displayName": "date_paid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "tax_amount",
              "displayName": "tax_amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "discount_amount",
              "displayName": "discount_amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "purchase_order_number",
              "displayName": "purchase_order_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.6,
      "position": [
        2160,
        544
      ],
      "id": "976a53e1-32ad-48c3-a83e-e5de3412f66e",
      "name": "insert doc record",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "\u2699\ufe0f insert doc record\n---\nMapping Column Mode: Auto-Map\nInput: Prepare Ledger Row (flat JSON)\nSheet: Billing_Ledger\n\nSchema: github.com/runfish5/micro-services\n  \u2026/04_inbox-attachment-organizer/docs/setup-guide.md\n\nEasy setup \u2014 paste into Sheet row 1:\naccounting_category\tinvoice_status\tinvoice_number\tattachment_count\temail_id\tcounterparty_name\tinvoice_date\tsubtotal_amount\tcurrency_code\tpayment_method\tdue_date_or_payment_terms\tpayment_reference\tdate_paid\ttax_amount\tdiscount_amount\tpurchase_order_number",
        "height": 80,
        "width": 150,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2112,
        544
      ],
      "id": "174b6924-3db8-4f66-b933-58b8c935fe65",
      "name": "Sticky Note27"
    },
    {
      "parameters": {
        "numberInputs": 3
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2528,
        48
      ],
      "id": "d0e654c0-25bf-4759-873a-981811d1a5fd",
      "name": "Merge"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "6V3qbVqk2Wp81WU0DeVjR",
          "mode": "list",
          "cachedResultUrl": "/workflow/6V3qbVqk2Wp81WU0DeVjR",
          "cachedResultName": "smart-CRM-fill"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {},
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {}
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        2320,
        -160
      ],
      "id": "ee5a6c54-3ce6-4599-b07f-b659d7fe7b79",
      "name": "Call 'smart-CRM-fill'"
    },
    {
      "parameters": {
        "content": "## After Import\n1. Create Gmail label `inProgress`\n   (`n8n` and `gdr` labels already exist)\n2. Update Set File ID \u2192 label_ID\n   with your inProgress label ID\n   (Tag inProgress + Remove inProgress\n   reference it via expression)",
        "height": 172,
        "width": 668,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        672,
        -256
      ],
      "id": "83c13b8c-6e43-473e-852b-c6f69ca7939a",
      "name": "Sticky Note29"
    },
    {
      "parameters": {
        "operation": "addLabels",
        "messageId": "={{ $('Gmail').item.json.id }}",
        "labelIds": [
          "Label_83"
        ]
      },
      "id": "e08816fd-2e5f-43c6-a85c-5db8a6522631",
      "name": "Tag gdr",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1744,
        416
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "\ud83d\udd17 [any-file2json-converter source](https://github.com/runfish5/micro-services/blob/main/projects/n8n/03_any-file2json-converter/workflows/any-file2json-converter.json)",
        "height": 80,
        "width": 150
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1104,
        192
      ],
      "id": "80685f86-62bc-4221-8363-17008b6aac04",
      "name": "Sticky Note28"
    },
    {
      "parameters": {
        "jsCode": "const inv = $('Accountant-concierge-LM').first().json.output.invoice_data;\nconst cat = $('Accountant-concierge-LM').first().json.output.accounting_category;\nconst email = $('email-info-hub').first().json;\n\nreturn [{\n  json: {\n    accounting_category:        cat                            || \"-\",\n    invoice_number:             inv.invoice_number             || \"-\",\n    attachment_count:           String(email.attachement_names.length),\n    email_id:                   email.contact_email,\n    counterparty_name:          inv.counterparty_name,\n    invoice_date:               inv.invoice_date,\n    subtotal_amount:            inv.total_amount_due           || \"-\",\n    currency_code:              inv.currency_code              || \"-\",\n    payment_method:             inv.payment_method             || \"-\",\n    due_date_or_payment_terms:  inv.due_date_or_payment_terms  || \"-\",\n    payment_reference:          inv.payment_reference          || \"-\",\n    date_paid:                  inv.date_paid                  || \"-\",\n    tax_amount:                 inv.tax_amount                 || \"-\",\n    discount_amount:            inv.discount_amount            || \"-\",\n    purchase_order_number:      inv.purchase_order_number      || \"-\"\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1936,
        544
      ],
      "id": "cfe38e63-eeea-48e7-b614-a64f9c64a2f3",
      "name": "Prepare Ledger Row"
    },
    {
      "parameters": {
        "content": "\u2699\ufe0f gdrive-recursion params\n---\ntarget_path: ={{ $json['folder-path'] }}\nroot_folder_id: GDRIVE_ROOT_FOLDER_ID\nroot_path: /Accounting",
        "height": 80,
        "width": 150,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1200,
        416
      ],
      "id": "0a008555-370c-48d8-ad8b-b501eaa650db",
      "name": "Sticky Note26"
    },
    {
      "parameters": {
        "operation": "removeLabels",
        "messageId": "={{ $json.id }}",
        "labelIds": "={{ $('Set File ID').item.json.label_ID }}"
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2976,
        64
      ],
      "id": "0c7b9537-eb52-4f02-8d93-56bbc81921fb",
      "name": "Remove inProgress",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const whitelist = [\n    \"payments-noreply@google.com\",\n    \"noreply@notifications.digitec.ch\",\n    \"invoice+statements@mail.anthropic.com\",\n    \"uniqued4ve@gmail.com\"\n];\n\nconst senderEmail = $('Gmail').first().json.from.value[0].address.toLowerCase();\n\nif (whitelist.some(e => e.toLowerCase() === senderEmail)) {\n    return $input.all();\n} else {\n    return [];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1776,
        208
      ],
      "id": "c7b086ee-13f0-4315-854b-783cb9f236da",
      "name": "sender_whitelist",
      "disabled": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "6bde56d4-3365-48dc-985c-215425097c74",
              "leftValue": "={{ $('email-info-hub').item.json.attachement_names }}",
              "rightValue": "",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        864,
        528
      ],
      "id": "e88f6297-b187-430f-b1f6-fcb25238e400",
      "name": "If"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "zBC03d42z8A_SjE0JSM5G",
          "mode": "list",
          "cachedResultUrl": "/workflow/zBC03d42z8A_SjE0JSM5G",
          "cachedResultName": "gdrive-recursion"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "target_path": "={{ $json['folder-path'] }}",
            "root_folder_id": "GDRIVE_ROOT_FOLDER_ID",
            "root_path": "/Accounting"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "target_path",
              "displayName": "target_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "root_folder_id",
              "displayName": "root_folder_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "root_path",
              "displayName": "root_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "current_folder_id",
              "displayName": "current_folder_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "current_path",
              "displayName": "current_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "mode": "each",
        "options": {}
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        1248,
        416
      ],
      "id": "f47b7579-6cdc-47c7-83c3-69643855c151",
      "name": "Call 'gdrive-recursion'",
      "notes": "Set this as debugging folder destination: `\"/Accounting/2025/11_November/Expense\"`"
    },
    {
      "parameters": {
        "chatId": "YOUR_CHAT_ID_1",
        "text": "=https://mail.google.com/mail/u/0/#inbox/{{ $('Gmail').first().json.id }} \n\ud83d\udce7 {{ $('subject-classifier-LM').item.json.output.type_of_document }} (no-log){{ $('subject-classifier-LM').item.json.output.type_of_document === 'actionable' ? '\\n' + ($('subject-classifier-LM').item.json.output.requires_action ? (/urgent|deadline|overdue|immediately|asap|critical/i.test($('subject-classifier-LM').item.json.output.message + ' ' + $('subject-classifier-LM').item.json.output.classification_rationale) ? '\ud83d\udfe0' : '\ud83d\udfe1') : '\ud83d\udfe2') + ' ' + $('subject-classifier-LM').item.json.output.message : '' }} ",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2096,
        32
      ],
      "id": "bedab3b3-5cf1-4bf7-b91a-77d7b2456d60",
      "name": "notify the category",
      "alwaysOutputData": false,
      "executeOnce": false,
      "retryOnFail": false,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "disabled": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "42bc6d75-2bc9-4596-8dde-39a437b8d086",
              "name": "body_core",
              "value": "={{ $('subject-classifier-LM').item.json.output.body_core }}",
              "type": "string"
            },
            {
              "id": "c53001df-d38b-4855-a247-dee7f28eab38",
              "name": "contact_email",
              "value": "={{ $('email-info-hub').item.json.contact_email }}",
              "type": "string"
            },
            {
              "id": "80a05ba9-38bc-4e19-b5a9-ab0cf0ce78c3",
              "name": "found",
              "value": "={{ $json.found }}",
              "type": "string"
            },
            {
              "id": "875e4349-7d59-4471-908e-5216e0d7f639",
              "name": "matchType",
              "value": "={{ $json.matchType }}",
              "type": "string"
            },
            {
              "id": "bb62a143-9b62-4977-bb96-42b8a7e821e6",
              "name": "contact_name",
              "value": "={{ $('email-info-hub').item.json.contact_name }}",
              "type": "string"
            },
            {
              "id": "a505d61c-dfe3-4ab8-ab3c-9cab31a57496",
              "name": "existing_contact",
              "value": "={{ $json.found ? JSON.stringify($json.contact || {}) : '{}' }}",
              "type": "string"
            },
            {
              "id": "837b8479-431a-4a77-84d6-64f9d52b8e39",
              "name": "subject",
              "value": "={{ $('Gmail').item.json.subject }}",
              "type": "string"
            },
            {
              "id": "5e2e6bab-9566-4f03-a8c1-f6094f17629f",
              "name": "direction",
              "value": "={{ $('email-info-hub').item.json.direction }}",
              "type": "string"
            },
            {
              "id": "1f3607b9-1150-4ce3-bd86-0146eb9a88f4",
              "name": "owner_name",
              "value": "={{ $('Set File ID').first().json.owner_name }}",
              "type": "string"
            },
            {
              "id": "extract-depth-flag",
              "name": "extract_depth",
              "value": "={{ $('subject-classifier-LM').item.json.output.extract_depth }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2096,
        -160
      ],
      "id": "2d19e58c-e491-4686-8541-fac93188f266",
      "name": "Prepare Contact Input"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "9YhVTcTyX-7B11d_Tcd17",
          "mode": "list",
          "cachedResultUrl": "/workflow/9YhVTcTyX-7B11d_Tcd17",
          "cachedResultName": "record-search"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {},
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {}
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        1872,
        -160
      ],
      "id": "26394bc8-49f9-4bea-b06d-47ed6bfcb624",
      "name": "Call 'record-search'"
    },
    {
      "parameters": {
        "content": "### ContactManager-lineage",
        "height": 204,
        "width": 680,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1808,
        -192
      ],
      "id": "1058419a-8d56-4605-8d01-fe2ffea6673e",
      "name": "Sticky Note25",
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "const cleanedData = $('email-info-hub').item.json.cleaned_email?.data?.[0] || {};\nconst bodyCore = $('subject-classifier-LM').item.json.output?.body_core || '';\nconst gmailBinary = $('Gmail').first().binary || {};\nconst attachmentNames = Object.keys(gmailBinary);\n\n// Early return if no attachments - still include email context for LLM\nif (attachmentNames.length === 0) {\n  return [{\n    json: {\n      body_core: bodyCore,\n      attachmentTextForLLM: 'No attachments found',\n      attachmentName: '',\n      subject: cleanedData.subject,\n      from: cleanedData.from,\n      date: cleanedData.date\n    }\n  }];\n}\n\nconst results = [];\nfor (let i = 0; i < attachmentNames.length; i++) {\n  const binaryKey = attachmentNames[i];\n\n  results.push({\n    json: {\n      body_core: bodyCore,\n      attachmentTextForLLM: cleanedData.attachment_texts_map?.[binaryKey] || '',\n      attachmentName: binaryKey,\n      subject: cleanedData.subject,\n      from: cleanedData.from,\n      date: cleanedData.date\n    },\n    binary: { file1: gmailBinary[binaryKey] }\n  });\n}\n\nreturn results.length > 0 ? results : [{ json: { message: 'No relevant attachments' } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        512
      ],
      "id": "e0f4a2ed-7e88-46da-aaed-e5c560b5f93d",
      "name": "Prepare Attachments"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b42ef4b6-3228-4fca-87ea-ad1ebe176a1e",
              "name": "cleaned_email",
              "value": "={{ $json }}",
              "type": "object"
            },
            {
              "id": "e885d0fb-fab0-4703-985a-44236baddfa1",
              "name": "attachement_names",
              "value": "={{ $('Gmail').first().binary ? Object.keys($('Gmail').first().binary) : [] }}",
              "type": "array"
            },
            {
              "id": "db43a7cc-60e3-4847-af0f-0be2859b5a83",
              "name": "attachment_check",
              "value": "={{ $json.data[0].attachment_texts_map.attachment_0.length > 700 ? 'Length of attachment: ' + $json.data[0].attachment_texts_map.attachment_0.length + '. Above 700 characters, Not displayed.' : 'Attachment:\\n' + $json.data[0].attachment_texts_map.attachment_0 }}",
              "type": "string"
            },
            {
              "id": "77709029-2c66-4334-a783-e4961fe81644",
              "name": "direction",
              "value": "={{ $json.direction }}",
              "type": "string"
            },
            {
              "id": "1afbd918-8ea2-4ee2-a37a-aba694a150db",
              "name": "owner_email",
              "value": "={{ $json.owner_email }}",
              "type": "string"
            },
            {
              "id": "485ebe37-e605-4c88-91dc-1f1047444a82",
              "name": "contact_email",
              "value": "={{ $json.contact_email }}",
              "type": "string"
            },
            {
              "id": "f50cba9f-dd97-407f-a63b-e955d1e4d50f",
              "name": "contact_name",
              "value": "={{ $json.contact_name }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1136,
        16
      ],
      "id": "f64b57eb-2933-49bc-833b-f30bdd78f1a5",
      "name": "email-info-hub"
    },
    {
      "parameters": {
        "operation": "addLabels",
        "messageId": "={{ $('Gmail').item.json.id }}",
        "labelIds": [
          "Label_82"
        ]
      },
      "id": "8aaa3cbd-7e09-43af-8e5c-e89ea2c3a95d",
      "name": "Tag n8n",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2752,
        64
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": "openai/gpt-oss-120b",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "typeVersion": 1,
      "position": [
        1408,
        96
      ],
      "id": "e136c3e3-12d3-488f-b615-dda01e459ef5",
      "name": "Any LM",
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "addLabels",
        "messageId": "={{ $json.id }}",
        "labelIds": "={{ $('Set File ID').item.json.label_ID }}"
      },
      "id": "34bb0c26-70da-4b57-8cbc-1b2cf8d5527f",
      "name": "Tag inProgress",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        240,
        256
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "#  Invoices & Receipts\nNodes 19-30",
        "height": 304,
        "width": 2128,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        416
      ],
      "typeVersion": 1,
      "id": "0f4724bb-5650-451e-93de-100dcee84bae",
      "name": "Sticky Note24"
    },
    {
      "parameters": {
        "content": "# subject classifier\nNodes 11-18",
        "height": 576,
        "width": 416,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        -192
      ],
      "typeVersion": 1,
      "id": "5b189e7b-6020-4f2c-93a9-1b07486e7409",
      "name": "Sticky Note23"
    },
    {
      "parameters": {
        "content": "# E-Mail trigger\nNodes 1-5",
        "height": 592,
        "width": 448,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        -208
      ],
      "typeVersion": 1,
      "id": "1836694c-c66d-457a-a331-6ab48261b913",
      "name": "Sticky Note22"
    },
    {
      "parameters": {
        "content": "# attachment processer\nNodes 6-10",
        "height": 448,
        "width": 672,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        -64
      ],
      "typeVersion": 1,
      "id": "7db33e93-5612-41d1-938a-54233cbe64f0",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1c259a48-b3c0-4d98-a5b0-bc81786bc978",
              "name": "file1",
              "value": "={{ $('Prepare Attachments').item.binary.file1 }}",
              "type": "binary"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1440,
        416
      ],
      "id": "ca3d504a-c6e4-49be-b42b-cca38b1b5309",
      "name": "Get binary data2"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e489f8d0-446e-464b-9446-ae9b4078b32e",
              "name": "month",
              "value": "={{ ['', '01_January', '02_February', '03_March', '04_April', '05_May', '06_June', '07_July', '08_August', '09_September', '10_October', '11_November', '12_December'][$json.output.month] }}",
              "type": "string"
            },
            {
              "id": "77d51103-cc09-473b-9898-a8a89ab90a7f",
              "name": "accounting_category",
              "value": "={{ $json.output.accounting_category }}",
              "type": "string"
            },
            {
              "id": "088d52d1-02c6-4acf-bef6-f37a002d24c7",
              "name": "doc_type",
              "value": "={{ $json.output.document_type }}",
              "type": "string"
            },
            {
              "id": "894ce439-67f8-4dae-812a-d372bff27a65",
              "name": "folder-path",
              "value": "=/Accounting/{{ $json.output.year }}/{{ ['', '01_January', '02_February', '03_March', '04_April', '05_May', '06_June', '07_July', '08_August', '09_September', '10_October', '11_November', '12_December'][$json.output.month] }}/{{ $json.output.accounting_category }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1040,
        416
      ],
      "id": "088452ce-c2d3-4a1e-afe3-2684e4fc0afb",
      "name": "input folder lookup"
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"accounting_category\": {\n      \"type\": \"string\",\n      \"enum\": [\"Revenue\", \"Expense\"],\n      \"description\": \"Revenue = we bill customers; Expense = we pay suppliers\"\n    },\n    \"document_type\": {\n      \"type\": \"string\",\n      \"enum\": [\"Invoice\", \"Receipt\"],\n      \"description\": \"Invoice = formal billing; Receipt = payment proof\"\n    },\n    \"year\": {\n      \"type\": \"integer\",\n      \"description\": \"Year from document date\"\n    },\n    \"month\": {\n      \"type\": \"integer\",\n      \"description\": \"Month from document date (1-12)\"\n    },\n    \"invoice_data\": {\n      \"type\": \"object\",\n      \"description\": \"Extracted transaction fields in snake_case (Billing_Ledger schema)\",\n      \"properties\": {\n        \"invoice_number\": { \"type\": \"string\", \"description\": \"Invoice/receipt reference number\" },\n        \"counterparty_name\": { \"type\": \"string\", \"description\": \"The other party: supplier (for Expense) or customer (for Revenue)\" },\n        \"invoice_date\": { \"type\": \"string\", \"description\": \"Document date (YYYY-MM-DD preferred)\" },\n        \"total_amount_due\": { \"type\": \"number\", \"description\": \"Total amount\" },\n        \"currency_code\": { \"type\": \"string\", \"description\": \"Currency: CHF, EUR, USD, etc.\" },\n        \"subtotal_amount\": { \"type\": \"number\", \"description\": \"Amount before tax\" },\n        \"tax_amount\": { \"type\": \"number\", \"description\": \"VAT/tax amount\" },\n        \"discount_amount\": { \"type\": \"number\", \"description\": \"Discount applied, if any\" },\n        \"due_date_or_payment_terms\": { \"type\": \"string\", \"description\": \"Payment deadline (YYYY-MM-DD) or terms (e.g., 'net 30', 'upon receipt')\" },\n        \"payment_method\": { \"type\": \"string\", \"description\": \"How paid: TWINT, bank transfer, credit card, etc.\" },\n        \"payment_reference\": { \"type\": \"string\", \"description\": \"Transaction/payment ID\" },\n        \"date_paid\": { \"type\": \"string\", \"description\": \"When payment was made (YYYY-MM-DD)\" },\n        \"purchase_order_number\": { \"type\": \"string\", \"description\": \"PO number if referenced\" }\n      },\n      \"required\": [\"invoice_number\", \"counterparty_name\", \"invoice_date\", \"total_amount_due\", \"currency_code\", \"subtotal_amount\", \"tax_amount\", \"discount_amount\", \"due_date_or_payment_terms\", \"payment_method\", \"payment_reference\", \"date_paid\", \"purchase_order_number\"]\n    }\n  },\n  \"required\": [\"accounting_category\", \"document_type\", \"year\", \"month\", \"invoice_data\"]\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        688,
        592
      ],
      "id": "dcf98772-8e29-46b5-b8ef-d7328c076a4b",
      "name": "Structured output"
    },
    {
      "parameters": {
        "model": "openai/gpt-oss-120b",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "typeVersion": 1,
      "position": [
        544,
        592
      ],
      "id": "58ee3305-5296-4eac-b50a-d21df0e46770",
      "name": "any LM1",
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1e51a7f2-4bda-4d41-895f-1bacac630ed9",
              "name": "Message",
              "value": "=EMAIL: completed\n---\nTitle \u270d: {{ $('Set File ID').first().json.subject }}\nContact \ud83d\udce4: `{{ $('email-info-hub').first().json.contact_email }}`\nFinished \ud83d\uddc4\ufe0f: {{ $('subject-classifier-LM').item.json.output.type_of_document }}\n<mail-summary>\n{{$('subject-classifier-LM').item.json.output.message}}\n</mail-summary>",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2384,
        544
      ],
      "id": "c76b5fb1-5cff-4836-ae49-f00661b09ac1",
      "name": "craft report note"
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n\t\"type\": \"object\",\n\t\"properties\": {\n        \"classification_rationale\": {\n\t\t\t\"type\": \"string\",\n\t\t\t\"description\": \"The logical reasoning and accounting principles used to determine the appropriate classification of this transaction\"\n\t\t},\t\n        \"type_of_document\": {\n\t\t\t\"type\": \"string\",\n\t\t\t\"description\": \"The action category of this email. financial = involves money (invoices, receipts, payment confirmations, billing). actionable = needs human response, not financial. informational = FYI only. Other = none of the above.\",\n            \"enum\" : [\"financial\", \"actionable\", \"informational\", \"Other\"]\n\t\t},\n        \"requires_action\": {\n        \"type\": \"boolean\",\n        \"description\": \"Should the owner look at this soon (within days)? True if: unexpected outcome, problem, decision needed, upcoming deadline, or noteworthy update. False for: routine confirmations.\"\n        },\n        \"message\": {\n            \"type\": \"string\",\n\t\t\t\"description\": \"One sentence summary, purpose: Concise, actionable message for Telegram (max 100 chars)\"\n        },\n        \"contact_name_extracted\": {\n          \"type\": \"string\",\n          \"description\": \"Full name of the contact person if clearer than email headers. Must be a legal person. Leave empty if only company name available or uncertain.\"\n        },\n        \"extract_depth\": {\n            \"type\": \"integer\",\n            \"enum\": [1, 2, 3],\n\t\t\t\"description\": \"1 = shallow (automated notifications, receipts, brief \u2014 only identity). 2 = medium (normal correspondence \u2014 adds relationship context). 3 = deep (rich biographical content \u2014 all fields).\"\n        },\n        \"body_core\": {\n            \"type\": \"string\",\n            \"description\": \"Core content only. Strip: email footers, newsletter promos, legal disclaimers, company registration info, repeated contact blocks, social media links, marketing content. Keep: main message, key facts, dates, amounts, tracking numbers, addresses.\"\n       }\n\t},\n\t\"required\": [\n      \"classification_rationale\",\n      \"body_core\",\n      \"type_of_document\",\n      \"message\",\n      \"requires_action\",\n      \"extract_depth\",\n      \"type_of_document\"\n\t]\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        1552,
        96
      ],
      "id": "156955f1-eb96-4694-ba7d-e85a2d503b1d",
      "name": "output profile"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "_1S4oK3bb1_1ofRy22eKo",
          "mode": "list",
          "cachedResultUrl": "/workflow/_1S4oK3bb1_1ofRy22eKo",
          "cachedResultName": "any-file2json-converter"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {},
          "matchingColumns": [],
          "schema": [
            {
              "id": "binary_files",
              "displayName": "binary_files",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "object"
            },
            {
              "id": "extraction",
              "displayName": "extraction",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "object"
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "mode": "each",
        "options": {
          "waitForSubWorkflow": true
        }
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        1136,
        192
      ],
      "id": "518e5617-8bdb-49a0-80d5-a0458f0e28cb",
      "name": "Create Attachment Profile",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "// Clean Email object - Smart dual-extraction for n8n Code node\n// Handles both Google One (text=footer only) and Digitec (text=HTML) cases\n// Copy this code into the \"Clean Email object\" Code node in inbox-attachment-organizer workflow\n\nconst items = $input.all();\nconst gmail = $('Gmail').first().json;\n\n// Helper: strip HTML tags and decode entities\nfunction stripHtml(str) {\n  return str\n    .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<!--[\\s\\S]*?-->/gi, '')\n    .replace(/<\\/(p|div|tr|li|h[1-6])>/gi, '\\n')\n    .replace(/<br\\s*\\/?>/gi, '\\n')\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/&nbsp;/gi, ' ')\n    .replace(/&amp;/gi, '&')\n    .replace(/&lt;/gi, '<')\n    .replace(/&gt;/gi, '>')\n    .replace(/&quot;/gi, '\"')\n    .replace(/&#39;/gi, \"'\")\n    .replace(/&#\\d+;/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim();\n}\n\n// Helper: detect if string contains HTML tags\nfunction containsHtml(str) {\n  return /<(table|tr|td|div|span|p|a|br|img|style|script)\\b/i.test(str);\n}\n\n// Helper: normalize for comparison\nfunction normalize(str) {\n  return str.toLowerCase().replace(/\\s+/g, ' ').trim();\n}\n\n// 1. Extract and clean from both sources\nconst rawText = gmail.text || '';\nconst rawHtml = gmail.html || '';\n\n// Clean text - strip HTML if it contains HTML tags (Digitec case)\nconst textContent = containsHtml(rawText) ? stripHtml(rawText) : rawText.trim();\n// Clean HTML\nconst htmlContent = rawHtml ? stripHtml(rawHtml) : '';\n\n// 2. Determine what to use (smart dual-extraction)\nlet rawEmailText;\n\nif (!textContent && !htmlContent) {\n  rawEmailText = '';\n} else if (!htmlContent) {\n  rawEmailText = textContent;\n} else if (!textContent) {\n  rawEmailText = htmlContent;\n} else {\n  const normalizedText = normalize(textContent);\n  const normalizedHtml = normalize(htmlContent);\n\n  // Helper: check if content A is contained in content B using fuzzy word matching\n  function isContentSubset(contentA, contentB) {\n    const normalizedA = normalize(contentA);\n    const normalizedB = normalize(contentB);\n\n    // First try exact substring\n    if (normalizedB.includes(normalizedA)) return true;\n\n    // Fuzzy: check if 80%+ of A's significant words appear in B\n    const wordsA = normalizedA.split(' ').filter(w => w.length > 4);\n    if (wordsA.length === 0) return true; // No significant words = consider subset\n    const matchingWords = wordsA.filter(w => normalizedB.includes(w));\n    return matchingWords.length >= wordsA.length * 0.8;\n  }\n\n  // Case 1: HTML is subset of text - text has additional content (reply case)\n  if (isContentSubset(htmlContent, textContent)) {\n    rawEmailText = textContent;\n  }\n  // Case 2: Text is subset of HTML - text is redundant (Google One case)\n  else if (isContentSubset(textContent, htmlContent)) {\n    rawEmailText = htmlContent;\n  }\n  // Case 3: Neither is subset - check for unique text content\n  else {\n    // Split into chunks and find truly unique content in text\n    const textChunks = textContent\n      .split(/[.!?\\n]+/)\n      .map(s => s.trim())\n      .filter(s => s.length > 30);\n\n    const uniqueChunks = textChunks.filter(chunk => {\n      const words = normalize(chunk).split(' ').filter(w => w.length > 4);\n      if (words.length === 0) return false;\n      const matchingWords = words.filter(w => normalizedHtml.includes(w));\n      // Chunk is unique if less than 50% of its words are in HTML\n      return matchingWords.length < words.length * 0.5;\n    });\n\n    if (uniqueChunks.length >= 2) {\n      // Text has significant unique content - merge with HTML first, text after\n      rawEmailText = htmlContent + '\\n\\n---\\n\\n' + uniqueChunks.join(' ');\n    } else {\n      // Mostly overlap - use the longer one\n      rawEmailText = htmlContent.length > textContent.length ? htmlContent : textContent;\n    }\n  }\n}\n\n// 3. Sanitize the extracted text\nlet body_sanitized = '';\nif (rawEmailText.trim()) {\n  body_sanitized = rawEmailText\n    .normalize('NFD')\n    .replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, ' ')\n    .replace(/[\\u200B-\\u200F\\u2028-\\u202F\\u205F-\\u206F\\uFEFF\\u034F\\u00AD]/g, '')\n    .replace(/[\\u00A0\\u1680\\u2000-\\u200A\\u3000]/g, ' ')\n    .replace(/https?:\\/\\/[^\\s]*\\.(jpg|jpeg|png|gif|svg|webp)[^\\s]*/gi, '')\n    .replace(/https?:\\/\\/[^\\s]+/gi, '[link]')\n    .replace(/\\s+([.,;!?])/g, '$1')\n    .replace(/\\s+/g, ' ')\n    .trim();\n}\n\n// 4. Build attachment texts MAP\nconst attachment_texts_map = {};\nconst gmailBinaryKeys = Object.keys($('Gmail').first().binary || {});\nconst hasAttachments = gmailBinaryKeys.length > 0;\n\nfor (let i = 0; i < items.length; i++) {\n  const $json = items[i].json;\n  const text = $json.data?.text || $json.textForLLM || $json.text || '';\n  const key = gmailBinaryKeys[i] || `attachment_${i}`;\n  if (text.trim()) {\n    attachment_texts_map[key] = text.trim();\n  }\n}\n\n// 5. Compute direction-based fields\nconst isOutbound = gmail.labelIds.includes('SENT');\nconst from = gmail.from.value[0];\nconst to = gmail.to.value[0];\n\n// 6. Return ONE item\nreturn [{\n  json: {\n    data: [{\n      body_sanitized,\n      attachment_texts_map,\n      attachmentNames: gmailBinaryKeys,\n      hasAttachments,\n      subject: gmail.subject || 'No subject',\n      from: gmail.from || 'Unknown',\n      date: gmail.date || new Date().toISOString()\n    }],\n    direction: isOutbound ? 'outbound' : 'inbound',\n    owner_email: isOutbound ? from.address : to.address,\n    contact_email: isOutbound ? to.address : from.address,\n    contact_name: isOutbound ? (to.name || '') : (from.name || '')\n  },\n  pairedItem: { item: 0 }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        912,
        16
      ],
      "id": "ba35ffa3-9d13-44e3-828c-71c3e1be2f5b",
      "name": "Clean Email object"
    },
    {
      "parameters": {
        "content": "Connect to the created google sheet.\n\n\n",
        "height": 172,
        "width": 184,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2096,
        480
      ],
      "id": "274449ec-9b13-41ae-8cd2-7c908c423c0c",
      "name": "Sticky Note20"
    },
    {
      "parameters": {
        "content": "\n\n\n\n\n\n\n\n\n\nConfigure your  `root_path` and `root_folder_id`.\n1. root_path: \"/Accounting\". \n2. `'root_folder_id'`:\nOpen \"/Accounting\" in the browser and take the last section of the URL, for example: `1tiI-FHH-yeWju4gS` in the case of `https://drive.google.com/drive/folders/1tiI-FHH-yeWju4gS`\n",
        "height": 252,
        "width": 728,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        976,
        416
      ],
      "id": "63119480-daab-4645-a866-fe1040c14f7e",
      "name": "Sticky Note19"
    },
    {
      "parameters": {
        "content": "\n\n\n\n\n\n\n\n\n\n\nOptions:\na. Deactivate with `ctrl + d`.\nb. **Follow n8n instructions on how to use the telegram node.** [DOCs](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.telegram/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-bas)",
        "height": 364,
        "width": 296,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1968,
        48
      ],
      "id": "8a3feb6a-8161-4ca3-9fa9-3c3e93327a1c",
      "name": "Sticky Note18"
    },
    {
      "parameters": {
        "content": "## Auto-File Email Attachments to Google Drive\n\nReads email attachment content, extracts key data to determine filing location (Accounting/2025/May/Expense/), and records details to Google Sheets.\n\n### How it works\n1. Monitors Gmail for new emails with attachments\n2. Classifies documents (financial, actionable, informational, Other)\n   - *any-file2json-converter*: Extracts text from PDFs, images (OCR), and documents\n3. For financial documents, extracts structured data.\n4. Files attachments to organized Google Drive folders (Accounting/2025/05_May/Expense/)\n   - *gdrive-recursion*: Locates or creates correct folder structure\n5. Logs invoice details to Google Sheets\n6. Sends notifications via Telegram\n\n### Setup\n- [ ] Import all 5 workflows (1 main + 2 subworkflows + 1 recursive + 1 batch processor)\n- [ ] Connect Google OAuth (Gmail + Drive + Sheets)\n- [ ] Add AI provider credentials (Groq or Gemini)\n- [ ] Create Gmail label `inProgress` and update Set File ID (see setup-guide.md step 3)\n- [ ] Create Google Sheet named \"Billing_Ledger\" with required columns\n- [ ] Download and upload folder structure template to Google Drive\n- [ ] Configure sender whitelist: add your own email address to test\n- [ ] Activate Gmail trigger\n\n- [ ] Test: forward an existing invoice email with attachments to yourself\n\n---\n#### After First Run\n- [ ] Run `gmail-processor-datesize` workflow to clean up and process all existing emails/attachments in your inbox for a truly organized system (the Gmail trigger only catches new incoming emails)\n- [ ] Connect Telegram bot for notifications\n\n---\n\ud83d\udcc2 **[View full docs & source code on GitHub \u2192](https://github.com/runfish5/micro-services/tree/main/projects/n8n/04_inbox-attachment-organizer)**\n",
        "height": 880,
        "width": 544
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        -208
      ],
      "typeVersion": 1,
      "id": "0a1cc3eb-87e8-4f42-a269-dc095849201c",
      "name": "Sticky Note13"
    },
    {
      "parameters": {
        "inputDataFieldName": "file1",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $('Call \\'gdrive-recursion\\'').item.json.folder_id }}",
          "mode": "id"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1600,
        416
      ],
      "id": "57dcb262-fe5a-4499-8fbf-df77d7030072",
      "name": "save doc to folder",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        64
      ],
      "id": "ed49abc7-054b-4628-92d7-6cf770ab20a9",
      "name": "When Executed by Another Workflow"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=<email-context>\nSubject: {{ $json.subject }}\nFrom: {{ $json.from }}\nDate: {{ $json.date }}\n\n{{ $json.body_core }}\n</email-context>\n\n<parsed-document-content>\n{{ $json.attachmentTextForLLM }}\n</parsed-document-content>\n\nClassify this document and extract all available data.",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "=You are an AI invoice processor for a Swiss SaaS company. You classify documents and extract their data.\n\n## COMPANY CONTEXT:\nYOUR COMPANY: {{ $('Set File ID').first().json.company_name }}\n\n## YOUR TASKS (in order):\n1. **Classify** the document type and accounting category\n2. **Extract** all invoice data into structured fields\n\n## CLASSIFICATION RULES:\n- accounting_category:\n  - \"Expense\" = {{ $('Set File ID').first().json.company_name }} is paying (we receive the invoice)\n  - \"Revenue\" = {{ $('Set File ID').first().json.company_name }} is billing (we send the invoice)\n- If counterparty_name contains \"{{ $('Set File ID').first().json.company_name }}\" -> always \"Revenue\"\n- document_type: \"Invoice\" (billing doc) | \"Receipt\" (payment proof)\n\n## EXTRACTION RULES:\n- Use **snake_case** keys in English\n- Prioritize standard keys: invoice_number, counterparty_name, invoice_date, total_amount_due, currency_code, subtotal_amount, tax_amount, discount_amount, due_date_or_payment_terms, payment_method, payment_reference, date_paid, purchase_order_number\n- counterparty_name = the OTHER party on the invoice (supplier for Expense, customer for Revenue)\n- For line items: array of objects with item_description, quantity, unit_price, line_item_total\n- Only include keys for data that exists\u2014do not invent values\n- If unsure about a field's purpose, prefix with `uncertain_`\n\n## OUTPUT:\nJSON only. No explanations."
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        544,
        416
      ],
      "id": "0a52d145-dedd-46ff-affc-c0d64011fdcf",
      "name": "Accountant-concierge-LM",
      "retryOnFail": true,
      "maxTries": 4
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=**document TEXT INPUT:**  \n{{ $json.cleaned_email.data[0].body_sanitized }}\n---\n** Attachements content**\n{{ $json.attachment_check }}\n** Company profile context **\nJust regular SaaS business.",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "=# Role\nYou are an email classifier for {{ $('Set File ID').first().json.owner_name }}'s inbox.\n\n## Context\n- INBOX OWNER: {{ $('Set File ID').first().json.owner_name }} ({{ $('email-info-hub').first().json.owner_email }})\n- EMAIL DIRECTION: {{ $('email-info-hub').first().json.direction }}\n- CONTACT: {{ $('email-info-hub').first().json.contact_name }} <{{ $('email-info-hub').first().json.contact_email }}>\n\n## TASK\nClassify the email and extract key information. The contact has already been identified above.\n\n## Category Rules\n- financial: ANY email involving money (invoices, receipts, payment confirmations, billing statements, refunds, subscription charges). If money is mentioned, classify as financial.\n- actionable: Needs human response but no money involved (meeting requests, decisions, deadlines).\n- informational: No action needed (newsletters, marketing, shipping updates, routine confirmations without financial content).\n- Other: None of the above.\n\n## EXTRACT DEPTH\nSet extract_depth based on how much personal detail the email reveals about the sender:\n- 1: Automated notifications, receipts, shipping updates, brief confirmations \u2014 no personal details\n- 2: Normal correspondence discussing topics, meetings, plans \u2014 mentions context but not biographical data\n- 3: Rich biographical content \u2014 contains addresses, phone numbers, job titles, company names, birthdays, interests, expertise, or other personal/professional profile data\n\n## Output\nA JSON object with the following keys:"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1408,
        -80
      ],
      "id": "b6f8fa01-1216-41b1-8b8e-97e949437cea",
      "name": "subject-classifier-LM",
      "retryOnFail": true,
      "maxTries": 2
    },
    {
      "parameters": {
        "fieldToSplitOut": "$binary",
        "include": "selectedOtherFields",
        "fieldsToInclude": "binary",
        "options": {
          "includeBinary": true
        }
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        912,
        192
      ],
      "id": "ecfa206b-9517-47ec-bac9-cc0b60dbee22",
      "name": "sp"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "1d5d56dd-157e-48ea-9e70-eba290d4f997",
              "leftValue": "={{Object.keys($('Gmail').item.binary || {}).length}}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        704,
        64
      ],
      "id": "d14916c4-5dc5-4fb8-b16c-54e93761515d",
      "name": "Empty?",
      "notesInFlow": false,
      "notes": "Null-safe: binary || {} guards against emails with no attachments (binary=undefined)."
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.output.type_of_document }}",
                    "rightValue": "financial",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "id": "d5660317-6722-49c5-adf5-fd6bce026987"
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        1552,
        208
      ],
      "id": "5ed77ebb-11d6-49e3-a621-e913b3094350",
      "name": "financial doc router"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "45df079e-7a07-406b-8cee-c236cf13c0d3",
              "leftValue": "={{ $json.labelIds }}",
              "rightValue": "=CATEGORY_PROMOTIONS",
              "operator": {
                "type": "array",
                "operation": "notContains",
                "rightType": "any"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        464,
        -112
      ],
      "id": "a8941d6d-a477-429b-ae5c-a89439d266eb",
      "name": "Stop promotions"
    },
    {
      "parameters": {
        "chatId": "YOUR_CHAT_ID_1",
        "text": "={{ $json.Message || \"SANTA ERROR\" }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2096,
        272
      ],
      "id": "71a49238-6e71-4f06-b2bc-300eb3ecba20",
      "name": "Telegram & done",
      "alwaysOutputData": false,
      "executeOnce": false,
      "retryOnFail": false,
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "10646eae-ae46-4327-a4dc-9987c2d76173",
              "name": "email_ID",
              "value": "={{ $json.id }}",
              "type": "string"
            },
            {
              "id": "ac0b0536-a1f6-4813-9e72-8a2334935fbe",
              "name": "owner_name",
              "value": "David Streuli",
              "type": "string"
            },
            {
              "id": "84f495c6-5380-4717-97d6-701ddc50694a",
              "name": "company_name",
              "value": "Runfish-data",
              "type": "string"
            },
            {
              "id": "20a51c7e-5962-4ce3-b7c4-8abf6f53b97b",
              "name": "label_ID",
              "value": "Label_8225839616655499948",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "70ec0f6c-a713-4130-b833-44fc8502ddc1",
      "name": "Set File ID",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        464,
        64
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "messageId": "={{ $('Set File ID').item.json.email_ID }}",
        "simple": false,
        "options": {
          "dataPropertyAttachmentsPrefixName": "attachment_",
          "downloadAttachments": true
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        464,
        256
      ],
      "id": "bd62d5a9-29fa-4bc3-9a11-dd7e751effcb",
      "name": "Gmail",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "simple": false,
        "filters": {},
        "options": {
          "dataPropertyAttachmentsPrefixName": "attachment_",
          "downloadAttachments": false
        }
      },
      "type": "n8n-nodes-base.

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Inbox-Attachment-Organizer. Uses googleSheets, gmail, telegram, lmChatGroq. Event-driven trigger; 51 nodes.

Source: https://github.com/runfish5/micro-services/blob/main/projects/n8n/04_inbox-attachment-organizer/workflows/inbox-attachment-organizer.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

Support Ticket Triage. Uses gmailTrigger, googleSheets, slack, gmail. Event-driven trigger; 9 nodes.

Gmail Trigger, Google Sheets, Slack +3
Email & Gmail

Alrouf AI Integration (Production). Uses googleSheets, chainLlm, lmChatGoogleGemini, outputParserStructured. Manual trigger; 21 nodes.

Google Sheets, Chain Llm, Google Gemini Chat +5
Email & Gmail

Attachments Gmail to drive and google sheets. Uses stickyNote, gmailTrigger, httpRequest, googleDrive. Event-driven trigger; 17 nodes.

Gmail Trigger, HTTP Request, Google Drive +5
Email & Gmail

📩🤖 This workflow automatically processes emails received in Gmail, extracts their attachments, and organizes them into specific folders in Google Drive based on the sender's email address.

Gmail Trigger, Google Drive, Execute Workflow Trigger +1
Email & Gmail

Stay ahead in your trading game with this powerful n8n automation workflow. Designed for real-time efficiency, this setup continuously scans your Gmail inbox for trading alerts from TradingView and en

Gmail, Telegram, Google Sheets +1