AutomationFlowsEmail & Gmail › AI-Powered PDF Email Attachment Analyzer

AI-Powered PDF Email Attachment Analyzer

Original n8n title: Webhook Code (open Ai)

Webhook Code. Uses openAi, noOp, googleDrive, respondToWebhook. Webhook trigger; 20 nodes.

Webhook trigger★★★★☆ complexityAI-powered20 nodesOpenAIGoogle DriveGmailRead Pdf
Email & Gmail Trigger: Webhook Nodes: 20 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Gmail → Google Drive 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "c84f3a9a-66b3-4a09-b06a-9b399ea574b8",
      "name": "OpenAI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        420,
        -240
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "GPT-4.1-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Does this PDF file look like a {{ $(\"Configure\").first().json[\"Match on\"] }}? Return \"true\" if it is a {{ $(\"Configure\").first().json[\"Match on\"] }} and \"false\" if not. Only reply with lowercase letters \"true\" or \"false\".\n\nThis is the PDF filename:\n```\n{{ $binary.data.fileName }}\n```\n\nThis is the PDF text content:\n```\n{{ $json.text }}\n```"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "ea1fbc5b-1859-4d65-8401-30baa95fcc52",
      "name": "Configure",
      "type": "n8n-nodes-base.set",
      "position": [
        -700,
        0
      ],
      "parameters": {
        "values": {
          "number": [
            {
              "name": "maxTokenSize",
              "value": 8000
            },
            {
              "name": "replyTokenSize",
              "value": 50
            }
          ],
          "string": [
            {
              "name": "Match on",
              "value": "receipt or invoice that can be considered a software engineering business cost"
            },
            {
              "name": "Google Drive folder to upload matched PDFs",
              "value": "https://drive.google.com/drive/folders/[put_folder_id_here]"
            },
            {
              "name": "sendInvoicesTo"
            }
          ],
          "boolean": [
            {
              "name": "sendEmail",
              "value": "={{ $('Webhook').item.json.body.sendEmail === \"true\" }}"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "3ee63612-c1e7-40e6-a38f-f77f5ee3efa4",
      "name": "Iterate over email attachments",
      "type": "n8n-nodes-base.code",
      "position": [
        -200,
        0
      ],
      "parameters": {
        "jsCode": "// https://community.n8n.io/t/iterating-over-email-attachments/13588/3\nlet results = [];\n\nfor (const item of $input.all()) {\n  console.log(item);\n  for (const key of Object.keys(item.binary)) {\n    results.push({\n        json: {},\n        binary: {\n            data: item.binary[key],\n        }\n    });\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 1
    },
    {
      "id": "3e638471-c1c5-4bab-aa2a-12a1777225ec",
      "name": "Not a PDF",
      "type": "n8n-nodes-base.noOp",
      "position": [
        120,
        80
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "b5af902b-2d59-49ee-b6d8-e387c59b89fd",
      "name": "Is text within token limit?",
      "type": "n8n-nodes-base.if",
      "position": [
        300,
        -100
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.text.length() / 4 <= $('Configure').first().json.maxTokenSize - $('Configure').first().json.replyTokenSize }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a0a8895c-ef8b-44e7-9294-1bcf629d0973",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        720,
        -120
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferInput1"
            }
          }
        },
        "combinationMode": "mergeByPosition"
      },
      "typeVersion": 2
    },
    {
      "id": "7565118a-6d44-4583-a19f-cb4177378d33",
      "name": "Is matched",
      "type": "n8n-nodes-base.if",
      "position": [
        880,
        -120
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.message.content }}",
              "value2": "true"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "074ffb7a-f83e-44b8-84fe-7b85f7245bb0",
      "name": "Upload file to folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1100,
        -140
      ],
      "parameters": {
        "name": "={{ $binary.data.fileName }}",
        "options": {},
        "parents": [
          "={{ $('Create folder').first().json.id }}"
        ],
        "binaryData": true
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "7681eb62-ba86-4c89-9b88-3ce6fc438bd4",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1080,
        0
      ],
      "parameters": {
        "path": "cded3af3-31df-47c2-a826-ff84eb4a41df",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "aab3d940-55c2-40d3-917a-83412d4e378d",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -720,
        -240
      ],
      "parameters": {
        "options": {
          "responseCode": 202
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"Accepted\",\n  \"driveFolderUrl\": \"{{ \"https://drive.google.com/drive/folders/\" + $json.id }}\"\n}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "29a4122f-0112-4157-a50d-0a6cf83ab7fd",
      "name": "Create folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -920,
        0
      ],
      "parameters": {
        "name": "={{ \"invoices_\" + $json.body.startDate.split('T')[0] }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root",
          "cachedResultName": "/ (Root folder)"
        },
        "resource": "folder"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "df86428f-7e63-4fd9-944c-f48af72af495",
      "name": "Aggregate attachments",
      "type": "n8n-nodes-base.code",
      "position": [
        1200,
        -340
      ],
      "parameters": {
        "jsCode": "// \"items\" is the array coming from the previous node (14 items)\nconst merged = { json: {}, binary: {} };\n\nfor (const item of $input.all()) {\n  const data = {\n    [item.binary.data.fileName]: item.binary.data\n  };\n  Object.assign(merged.binary, data); // copy every file property\n}\n\nreturn [merged];     // one single item goes out"
      },
      "typeVersion": 2
    },
    {
      "id": "72a21bfa-6e3b-421a-a4ca-dea9e09a5b0b",
      "name": "Send email with invoices?",
      "type": "n8n-nodes-base.if",
      "position": [
        1000,
        -320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "63caf3d8-39bd-4300-aa7e-8c0ecfc87576",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Configure').first().json.sendEmail }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "bb038635-eb69-447b-a85b-e9c3caebfe3a",
      "name": "Send to my accountant",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1360,
        -280
      ],
      "parameters": {
        "sendTo": "test@example.com",
        "message": "Hello, here are my invoices and receipts.",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {
                "property": "={{ Object.keys($binary).join(',') }}"
              }
            ]
          }
        },
        "subject": "={{ \n  (() => {\n    const startDate = $node['Webhook'].json.body.startDate.split('T')[0];\n    const endDate = $node['Webhook'].json.body.endDate.split('T')[0];\n    return `Dokumenty kosztowe za okres od ${startDate} do ${endDate}`;\n  })() \n}}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7b2e5c6c-0a95-4347-97a9-c9ffbc0e3af2",
      "name": "Get emails with attachments",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -500,
        0
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "has:attachment",
          "sender": "",
          "receivedAfter": "={{ $('Webhook').item.json.body.startDate }}",
          "receivedBefore": "={{ $('Webhook').item.json.body.endDate }}"
        },
        "options": {
          "downloadAttachments": true,
          "dataPropertyAttachmentsPrefixName": "attachment_"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6d5b2c1b-657d-44bf-980d-fd428fd8d832",
      "name": "Read PDF email attachments",
      "type": "n8n-nodes-base.readPDF",
      "onError": "continueErrorOutput",
      "position": [
        120,
        -80
      ],
      "parameters": {},
      "notesInFlow": false,
      "typeVersion": 1
    },
    {
      "id": "3166f45c-306f-483a-b2c6-6768abc916a0",
      "name": "Is attachment a PDF?",
      "type": "n8n-nodes-base.if",
      "position": [
        -40,
        0
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $binary.data.fileExtension }}",
              "value2": "pdf"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "866b286a-7b9b-4506-aa6b-d2049b249991",
      "name": "Optional filter for emails",
      "type": "n8n-nodes-base.filter",
      "position": [
        -360,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "687c4cd0-ada5-4dc1-8707-1a9c3b551251",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.to.value[0].address }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "56133dba-bc93-4f65-be42-995164a45c03",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1600,
        -340
      ],
      "parameters": {
        "width": 440,
        "height": 880,
        "content": "## Gmail PDF Invoice/Receipt Classifier & Google Drive Uploader (via n8n & OpenAI)\n\n_**DISCLAIMER**: AI classification isn't perfect. Always double-check that the correct documents were identified and uploaded._\n\nThis n8n workflow, triggered via a webhook, scans your Gmail for emails within a specified date range, extracts PDF attachments, and uses OpenAI to determine if each PDF matches a defined category (defaulting to \"receipt or invoice\"). Matched PDFs are then uploaded to a uniquely named Google Drive folder based on the date range. You can customize the classification term (e.g., change \"receipt or invoice\" to \"contract\") and optionally have the workflow email the collected PDFs to a specified address.\n\n### How it works\n1.  Triggers via a `Webhook` receiving a start date, end date, and an optional flag to send an email.\n2.  Creates a dated folder in `Google Drive` (e.g., `invoices_YYYY-MM-DD_YYYY-MM-DD`).\n3.  Fetches emails with attachments from `Gmail` within the specified date range.\n4.  Iterates through each attachment, filtering specifically for `PDF` files.\n5.  Extracts text from each PDF (skipping if the text exceeds token limits set in the `Configure` node).\n6.  Uses the `OpenAI` node to ask if the PDF content and filename look like the item defined in the `Configure` node's \"Match on\" field (e.g., \"receipt or invoice\").\n7.  If OpenAI responds with \"true\", the original `PDF` file is uploaded to the `Google Drive` folder created in step 2.\n8.  If the initial webhook request included the flag to send an email, it aggregates all successfully matched PDFs and sends them via `Gmail` to the address specified in the `Configure` node.\n\nWorkflow written by [Tom](https://browsewiz.com)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "aa5d8126-e2ec-4476-886d-c46379f1c6e2",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -780,
        -40
      ],
      "parameters": {
        "width": 260,
        "height": 1000,
        "content": "## Parameters\n\n\n\n\n\n\n\n\n\n* **`maxTokenSize`** (Number)\n    *   **Limits PDF text length** (estimated input tokens) sent to OpenAI for classification. Prevents errors/high costs on long documents.\n    *   *Default: 8000*\n\n*   **`replyTokenSize`** (Number)\n    *   **Reserves tokens for OpenAI's reply** ('true'/'false'). Ensures total tokens stay within limits.\n    *   *Default: 50*\n\n*   **`Match on`** (String)\n    *   **The keyword/phrase OpenAI uses** to identify the desired document type (e.g., \"receipt or invoice\", \"contract\"). Defines what you're searching for.\n    *   *Default: \"receipt or invoice\"*\n\n*   **`sendInvoicesTo`** (String)\n    *   **Recipient email address** for the final collection of matched PDFs. Used only if `sendEmail` is true.\n    *   *Example: \"accounting@example.com\"*\n\n*   **`sendEmail`** (Boolean)\n    *   **Turns the final email step on (`true`) or off (`false`)**. Set via the initial webhook trigger. If false, files are only uploaded to Drive.\n    *   *Example: `true` or `false`*"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Is matched",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Create folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure": {
      "main": [
        [
          {
            "node": "Get emails with attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is matched": {
      "main": [
        [
          {
            "node": "Upload file to folder",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send email with invoices?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create folder": {
      "main": [
        [
          {
            "node": "Configure",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is attachment a PDF?": {
      "main": [
        [
          {
            "node": "Read PDF email attachments",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Not a PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate attachments": {
      "main": [
        [
          {
            "node": "Send to my accountant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to my accountant": {
      "main": [
        []
      ]
    },
    "Upload file to folder": {
      "main": [
        []
      ]
    },
    "Send email with invoices?": {
      "main": [
        [
          {
            "node": "Aggregate attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Optional filter for emails": {
      "main": [
        [
          {
            "node": "Iterate over email attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read PDF email attachments": {
      "main": [
        [
          {
            "node": "Is text within token limit?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get emails with attachments": {
      "main": [
        [
          {
            "node": "Optional filter for emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is text within token limit?": {
      "main": [
        [
          {
            "node": "OpenAI",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ],
        []
      ]
    },
    "Iterate over email attachments": {
      "main": [
        [
          {
            "node": "Is attachment a PDF?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

How this works

This workflow streamlines handling incoming webhooks by intelligently processing email attachments, extracting and analysing their content with OpenAI to identify relevant information, then automatically uploading qualifying files to Google Drive for seamless organisation. It's ideal for teams managing high volumes of emails with attachments, such as support or sales departments, saving hours on manual sorting and review. The key step involves iterating over attachments to check if they are PDFs within a token limit, merging viable text for AI evaluation before conditional upload, ensuring only pertinent files are stored without overwhelming your drive.

Use this when you receive frequent emails with mixed attachments needing AI-driven filtering, like lead documents or reports, to automate archiving and reduce inbox clutter. Avoid it for non-email webhook sources or when attachments rarely exceed token limits, as simpler flows would suffice. Common variations include swapping OpenAI for custom code checks or integrating Gmail triggers instead of generic webhooks for direct email processing.

About this workflow

Webhook Code. Uses openAi, noOp, googleDrive, respondToWebhook. Webhook trigger; 20 nodes.

Source: https://github.com/Zie619/n8n-workflows — 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

Description

Google Sheets, OpenAI, Gmail +1
Email & Gmail

✨🔪 Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, limit, stickyNote. Webhook trigger; 54 nodes.

Gmail, Gmail Trigger, HTTP Request +6
Email & Gmail

🦜✨Use OpenAI to Transcribe Audio + Summarize with AI + Save to Google Drive. Uses gmail, stickyNote, openAi, limit. Event-driven trigger; 33 nodes.

Gmail, OpenAI, Google Drive +2
Email & Gmail

Googledocs. Uses extractFromFile, outputParserItemList, splitInBatches, stickyNote. Webhook trigger; 23 nodes.

Output Parser Item List, Google Docs, OpenAI Chat +4
Email & Gmail

Code Readpdf. Uses readPDF, stickyNote, noOp, openAi. Event-driven trigger; 18 nodes.

Read Pdf, OpenAI, Google Drive +1