{
  "id": "AOioHpxTTmZ5y5gC",
  "name": "From Gmail to Google Drive, Laiye ADP fully automated invoice extraction",
  "tags": [],
  "nodes": [
    {
      "id": "0d9d5f81-fdd0-4ce3-a76b-63d5ff0115b7",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        896,
        480
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "labelIds": [
            "INBOX"
          ],
          "readStatus": "both"
        },
        "options": {
          "downloadAttachments": true,
          "dataPropertyAttachmentsPrefixName": "attachment_"
        },
        "pollTimes": {
          "item": [
            {
              "hour": 23,
              "minute": 59
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "aaec31d1-c60a-4fde-bbf7-6a9bac5e09da",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        2192,
        544
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "9960d10f-3231-4bbb-8265-9a051cced530",
      "name": "Upload Unprocessed file",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        2384,
        544
      ],
      "parameters": {
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "inputDataFieldName": "=file"
      },
      "retryOnFail": false,
      "typeVersion": 3
    },
    {
      "id": "6035c47b-6f5d-4d47-88bf-ad908cb55adf",
      "name": "Extract attachment information",
      "type": "n8n-nodes-base.code",
      "position": [
        1232,
        480
      ],
      "parameters": {
        "jsCode": "const outputItems = [];\n\n$input.all().forEach((inputItem) => {\n  const { json, binary } = inputItem;\n\n  // Iterate all binary attachments (attachment_0, attachment_1...)\n  const binaryKeys = Object.keys(binary || {});\n\n  if (binaryKeys.length === 0) {\n    // Return empty metadata when no attachments to avoid process interruption\n    outputItems.push({\n      json: {\n        \"attachment_name\": \"\",\n        \"attachment_extension\": \"\",\n        \"mime_type\": \"\",\n        \"attachment_size_bytes\": 0,\n        \"attachment_size_mb\": 0,\n        \"has_attachment\": false,\n        \"email_id\": json.id || \"\"\n      },\n      binary: {}\n    });\n    return;\n  }\n\n  // Process each attachment when available\n  binaryKeys.forEach((binaryKey) => {\n    const att = binary[binaryKey];\n    if (!att) return;\n\n    // Extract metadata from binary\n    const fileName = att.fileName || \"\";\n    const fileExt = att.fileExtension || \"\";\n    const mimeType = att.mimeType || \"\";\n    const fileSizeStr = att.fileSize || \"0 kB\";\n\n    // Parse file size to bytes\n    let fileSizeBytes = 0;\n    const sizeMatch = fileSizeStr.match(/(\\d+(?:\\.\\d+)?)\\s*(kB|MB|GB|B)/i);\n    if (sizeMatch) {\n      const value = parseFloat(sizeMatch[1]);\n      const unit = sizeMatch[2].toUpperCase();\n      switch (unit) {\n        case \"B\":\n          fileSizeBytes = value;\n          break;\n        case \"KB\":\n          fileSizeBytes = value * 1024;\n          break;\n        case \"MB\":\n          fileSizeBytes = value * 1024 * 1024;\n          break;\n        case \"GB\":\n          fileSizeBytes = value * 1024 * 1024 * 1024;\n          break;\n      }\n    }\n\n    const fileSizeMB = (fileSizeBytes / 1024 / 1024).toFixed(2);\n\n    outputItems.push({\n      json: {\n        \"attachment_name\": fileName,\n        \"attachment_extension\": fileExt,\n        \"mime_type\": mimeType,\n        \"attachment_size_bytes\": Math.round(fileSizeBytes),\n        \"attachment_size_mb\": parseFloat(fileSizeMB),\n        \"has_attachment\": true,\n        \"email_id\": json.id || \"\",\n        \"binary_key\": binaryKey // Used to associate binary files later\n      },\n      // Core modification: Rename all attachments to \"file\" instead of attachment_X\n      binary: {\n        file: att\n      }\n    });\n  });\n});\n\nreturn outputItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "c898a939-f009-414a-a57a-0cf935901aa7",
      "name": "Filter documents",
      "type": "n8n-nodes-base.if",
      "position": [
        1552,
        480
      ],
      "parameters": {
        "options": {
          "ignoreCase": true
        },
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1d3c412e-2393-4c9f-9505-031b0a9b9f4c",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "leftValue": "={{ $json.attachment_name }}",
              "rightValue": "={{ \"invoice|receipt|expenses|fee\" }}"
            },
            {
              "id": "b51b0700-dc6c-4342-b67d-f0c060e3e526",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "leftValue": "={{ $json.attachment_extension }}",
              "rightValue": "={{ \"^(jpeg|jpg|png|bmp|tiff|pdf|doc|docx|xls|xlsx)$\" }}"
            },
            {
              "id": "16f33e86-b523-4fa8-b1be-3cc4ad5a78df",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.attachment_size_mb }}",
              "rightValue": "={{ 50 }}"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "7ed141be-6fd4-4729-b700-0d28039273bc",
      "name": "Base64 Encode Document",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1920,
        288
      ],
      "parameters": {
        "options": {
          "encoding": "base64"
        },
        "operation": "binaryToPropery",
        "binaryPropertyName": "=file"
      },
      "typeVersion": 1.1
    },
    {
      "id": "fff5863f-fe64-4458-91df-25dfc423bf30",
      "name": "Laiye ADP HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2160,
        288
      ],
      "parameters": {
        "url": "https://adp.laiye.com/open/agentic_doc_processor/laiye/v1/app/doc/extract",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"app_key\": \"enter_your_app_key\",\n  \"app_secret\":\"enter_your_app_secret\",\n  \"file_base64\": \"{{ $json.data }}\"\n}",
        "sendBody": true,
        "jsonHeaders": "={\n  \"X-Access-Key\": \"enter_your_access_key\",\n  \"X-Timestamp\": \"{{ $now.valueOf() }}\",\n  \"X-Signature\": \"enter_your_access_key\"\n}",
        "sendHeaders": true,
        "specifyBody": "json",
        "specifyHeaders": "json"
      },
      "typeVersion": 4.3
    },
    {
      "id": "b45702da-c2c7-411b-a9b6-adeb511b37f4",
      "name": "Result Processor",
      "type": "n8n-nodes-base.code",
      "position": [
        2368,
        288
      ],
      "parameters": {
        "jsCode": "  var data = $node['Laiye ADP HTTP Request'].json.data.extraction_result;                                                                                            var mainFields = {};\n  var detailHeaders = [];                                                                                                                                \n  var detailRows = [];\n\n  for (var i = 0; i < data.length; i++) {\n    if (data[i].table_values == null) {\n      var name = data[i].field_name;\n      var val = 'No Result';\n      if (data[i].field_values && data[i].field_values[0]) {\n        val = data[i].field_values[0].field_value || 'No Result';\n      }\n      mainFields[name] = val;\n    } else {\n      for (var j = 0; j < data[i].table_values[0].length; j++) {\n        detailHeaders.push(data[i].table_values[0][j].field_name);\n      }\n      for (var k = 0; k < data[i].table_values.length; k++) {\n        var row = [];\n        for (var m = 0; m < data[i].table_values[k].length; m++) {\n          var cell = 'No Result';\n          if (data[i].table_values[k][m].field_values && data[i].table_values[k][m].field_values[0]) {\n            cell = data[i].table_values[k][m].field_values[0].field_value || 'No Result';\n          }\n          row.push(cell);\n        }\n        detailRows.push(row);\n      }\n    }\n  }\n\n  var keys = Object.keys(mainFields);\n  var vals = Object.values(mainFields);\n\n  var xml = '';\n  xml = xml + '<?xml version=\"1.0\"?>';\n  xml = xml + '<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\">';\n  xml = xml + '<Worksheet ss:Name=\"Fields\"><Table>';\n  xml = xml + '<Row>';\n  for (var n = 0; n < keys.length; n++) {\n    xml = xml + '<Cell><Data ss:Type=\"String\">' + keys[n] + '</Data></Cell>';\n  }\n  xml = xml + '</Row>';\n  xml = xml + '<Row>';\n  for (var p = 0; p < vals.length; p++) {\n    xml = xml + '<Cell><Data ss:Type=\"String\">' + vals[p] + '</Data></Cell>';\n  }\n  xml = xml + '</Row>';\n  xml = xml + '</Table></Worksheet>';\n\n  if (detailHeaders.length > 0) {\n    xml = xml + '<Worksheet ss:Name=\"Product Details Table\"><Table>';\n    xml = xml + '<Row>';\n    for (var q = 0; q < detailHeaders.length; q++) {\n      xml = xml + '<Cell><Data ss:Type=\"String\">' + detailHeaders[q] + '</Data></Cell>';\n    }\n    xml = xml + '</Row>';\n    for (var r = 0; r < detailRows.length; r++) {\n      xml = xml + '<Row>';\n      for (var s = 0; s < detailRows[r].length; s++) {\n        xml = xml + '<Cell><Data ss:Type=\"String\">' + detailRows[r][s] + '</Data></Cell>';\n      }\n      xml = xml + '</Row>';\n    }\n    xml = xml + '</Table></Worksheet>';\n  }\n\n  xml = xml + '</Workbook>';\n\n  return [{\n    binary: {\n      data: {\n        data: Buffer.from(xml, 'utf-8').toString('base64'),\n        mimeType: 'application/vnd.ms-excel',\n        fileName: 'data.xls'\n      }\n    }\n  }];"
      },
      "typeVersion": 2
    },
    {
      "id": "d2224313-0c64-4b3e-9d0d-327e184eff88",
      "name": "Upload the extracted result document",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        2592,
        288
      ],
      "parameters": {
        "name": "=n8n_laiye_adp_{{ $('Gmail Trigger').item.json.from.value[0].name }}_{{ $('Filter documents').item.json.attachment_name }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "inputDataFieldName": "=data"
      },
      "typeVersion": 3
    },
    {
      "id": "3b44154e-b2cb-45c7-8b89-84584a0681e8",
      "name": "Create a pending folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1952,
        640
      ],
      "parameters": {
        "name": "Untreated document",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root",
          "cachedResultName": "/ (Root folder)"
        },
        "resource": "folder"
      },
      "typeVersion": 3
    },
    {
      "id": "34d8cd58-5538-40cd-8525-b4f67029b927",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        320
      ],
      "parameters": {
        "width": 528,
        "height": 560,
        "content": "## How it works\nThis workflow automates the full invoice processing pipeline: it monitors Gmail for invoice emails, extracts key data from multiple file formats, and automatically organizes files into Google Drive. It replaces manual invoice sorting, checking, and data entry.\n\n## Setup steps\n\n**Step 1:** \nConnect your Gmail account. The workflow automatically processes emails that match these rules:\n- Filename includes: invoice, receipt, expenses, or fee\n- File size < 50MB\n- Supported formats: jpeg, jpg, png, bmp, tiff, pdf, doc, docx, xls, xlsx\n- Default schedule: once per day (adjustable in the Gmail Trigger)\n\n\n**Step 2:**\nConnect your Google Drive.\n- Valid invoices are extracted and saved as Excel.\n- Unqualified files are stored in a folder named \u201cUntreated document\u201d for later manual review."
      },
      "typeVersion": 1
    },
    {
      "id": "a050a268-fc08-4eea-b6cf-4a1178e539ac",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        656
      ],
      "parameters": {
        "color": 7,
        "width": 272,
        "height": 112,
        "content": "**Step 1: Connect your Gmail account.**\n\nYou can set the execution frequency here. The default is once per day."
      },
      "typeVersion": 1
    },
    {
      "id": "1e948b3e-5089-4b3a-b9b1-0f39f19a3c3c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 224,
        "content": "**File Filter Tips:**\n\n1. You can edit``` {{ $json.attachment_name }}``` to use your own filter keywords.\n2. We recommend not modifying file size and format settings to ensure smooth execution with the Laiye ADP nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "be830e84-3cf6-4480-bbf5-ade1c44f69ea",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 976,
        "height": 560,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "08414006-ae87-4ff1-bc81-6c004d8edc8b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 80,
        "content": "**Step 2: Connect your Google Drive.**\n\nEnsure all files are safely stored in Google Drive."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "18039fcc-f568-4c58-bbfb-ed5187be221b",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Upload Unprocessed file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Extract attachment information",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter documents": {
      "main": [
        [
          {
            "node": "Base64 Encode Document",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create a pending folder",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Result Processor": {
      "main": [
        [
          {
            "node": "Upload the extracted result document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Base64 Encode Document": {
      "main": [
        [
          {
            "node": "Laiye ADP HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Laiye ADP HTTP Request": {
      "main": [
        [
          {
            "node": "Result Processor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a pending folder": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract attachment information": {
      "main": [
        [
          {
            "node": "Filter documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}