AutomationFlowsEmail & Gmail › Fill, Validate, and Email PDF Forms From Webhook Data with Pdfops, Gmail,…

Fill, Validate, and Email PDF Forms From Webhook Data with Pdfops, Gmail,…

Original n8n title: Fill, Validate, and Email PDF Forms From Webhook Data with Pdfops, Gmail, and Drive

ByPDFops @pdfops on n8n.io

This workflow receives a webhook payload with a PDF URL, form field values, and an email address, then downloads the fillable PDF, fills it using PDFops, and emails the completed PDF via Gmail. Receives a POST webhook request containing , a object, and the recipient address.…

Webhook trigger★★★★☆ complexity17 nodesHTTP RequestN8N Nodes PdfopsGmailGoogle Drive
Email & Gmail Trigger: Webhook Nodes: 17 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #17884 — we link there as the canonical source.

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
{
  "id": "pdfops-fill-form-webhook-email",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Fill and flatten PDF forms from webhook data with PDFops, Gmail and Drive",
  "nodes": [
    {
      "id": "note-overview",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -840,
        -120
      ],
      "parameters": {
        "width": 560,
        "height": 1120,
        "content": "## Fill and flatten PDF forms from webhook data with PDFops, Gmail and Drive\n\nTurn any app that can POST a webhook (Tally, Typeform, your own backend) into a PDF-filling pipeline: JSON in, a locked PDF emailed out and archived.\n\nThe part most fill-a-PDF workflows skip: **a typo in a field name silently produces a blank form**. This one inspects the PDF first and returns the exact bad names instead of emailing a broken document.\n\n### How it works\n- **Payload Valid?** rejects requests missing a PDF URL, recipient or fields, answering 400 immediately.\n- **PDFops Inspect Fields** reads the blank PDF's real AcroForm names; **Compare Requested Fields** diffs them against the payload.\n- **PDFops Fill & Flatten** fills the form and bakes the values into the page, so the result can't be edited after sending.\n- The filled PDF fans out: **Gmail** emails it, **Google Drive** archives it, and the webhook gets a JSON receipt.\n- A failed download or unknown field name routes to **Respond With Error**.\n\n### Setup\n1. Install the **PDFops** community node (`n8n-nodes-pdfops`) \u2014 verified: one-click install on n8n Cloud (search \"PDFops\" in the nodes panel), or Settings \u2192 Community Nodes on self-hosted.\n2. *(Optional)* Add a **PDFops API** credential for a higher quota \u2014 keyless works at 100 req/IP/month.\n3. Connect **Gmail** and **Google Drive** credentials.\n4. Open **Set Delivery Options** to change the subject, file name or archive folder.\n5. Activate, then POST `{ \"pdfUrl\": \"\u2026\", \"fields\": { \u2026 }, \"email\": \"\u2026\" }` to the Production URL.\n\n### Customization tips\n- Set `flattenOutput` to false to return an editable AcroForm.\n- Swap Gmail for SMTP or Slack, and Drive for S3 \u2014 neither touches the PDF steps.\n- Chain the PDFops **Merge** operation to bundle several forms into one packet."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-1",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        -40
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 560,
        "content": "## 1. Receive and normalise the request\nThe webhook takes JSON; **Set Delivery Options** is the one place to edit subject, file name, archive folder and flattening. Invalid payloads are answered 400 here."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-2",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        -40
      ],
      "parameters": {
        "color": 7,
        "width": 860,
        "height": 560,
        "content": "## 2. Pre-flight: does the PDF really have these fields?\nInspect the blank PDF and diff its real AcroForm names against the ones requested. A typo fails loudly here instead of emailing a blank form."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-3",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        -40
      ],
      "parameters": {
        "color": 7,
        "width": 660,
        "height": 560,
        "content": "## 3. Fill, deliver, archive, answer\nFill and flatten so the values can't be edited, then fan out: email the PDF, archive a copy to Drive, and return a JSON receipt to the caller."
      },
      "typeVersion": 1
    },
    {
      "id": "note-section-4",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        940,
        560
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 240,
        "content": "## Error path\nBad download, missing payload or unknown field names all end here, so the caller never waits on a silent failure."
      },
      "typeVersion": 1
    },
    {
      "id": "trigger-webhook",
      "name": "Form Submission",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -80,
        180
      ],
      "parameters": {
        "path": "fill-form-webhook-email",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "set-delivery-options",
      "name": "Set Delivery Options",
      "type": "n8n-nodes-base.set",
      "position": [
        140,
        180
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-pdf-url",
              "name": "pdfUrl",
              "type": "string",
              "value": "={{ $json.body.pdfUrl }}"
            },
            {
              "id": "cfg-recipient",
              "name": "recipient",
              "type": "string",
              "value": "={{ $json.body.email }}"
            },
            {
              "id": "cfg-fields",
              "name": "requestedFields",
              "type": "object",
              "value": "={{ $json.body.fields }}"
            },
            {
              "id": "cfg-subject",
              "name": "emailSubject",
              "type": "string",
              "value": "Your completed form"
            },
            {
              "id": "cfg-filename",
              "name": "outputFileName",
              "type": "string",
              "value": "={{ $json.body.fileName || 'filled-form.pdf' }}"
            },
            {
              "id": "cfg-flatten",
              "name": "flattenOutput",
              "type": "boolean",
              "value": true
            }
          ]
        },
        "includeOtherFields": false
      },
      "typeVersion": 3.4
    },
    {
      "id": "if-payload-valid",
      "name": "Payload Valid?",
      "type": "n8n-nodes-base.if",
      "position": [
        360,
        180
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-payload-complete",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ !!$json.pdfUrl && !!$json.recipient && Object.keys($json.requestedFields || {}).length > 0 }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "http-fetch-blank",
      "name": "Fetch Blank PDF",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        580,
        180
      ],
      "parameters": {
        "url": "={{ $json.pdfUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "pdfops-inspect",
      "name": "PDFops Inspect Fields",
      "type": "n8n-nodes-pdfops.pdfops",
      "position": [
        800,
        180
      ],
      "parameters": {
        "operation": "inspect",
        "binaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "code-compare-fields",
      "name": "Compare Requested Fields",
      "type": "n8n-nodes-base.code",
      "position": [
        1020,
        180
      ],
      "parameters": {
        "jsCode": "// Pre-flight check. PDF form filling fails silently: ask for a field name\n// that does not exist and you get back a perfectly valid, completely blank\n// form. So diff what the caller asked for against what the PDF actually has.\nconst config = $('Set Delivery Options').first().json;\nconst inspected = $input.first().json;\n\nconst requested = Object.keys(config.requestedFields || {});\nconst available = (inspected.fields || []).map((f) => f.name);\nconst unknownFields = requested.filter((name) => !available.includes(name));\n\nreturn [\n  {\n    json: {\n      ...config,\n      unknownFields,\n      unknownCount: unknownFields.length,\n      availableFields: available,\n      pdfFieldCount: inspected.count,\n      hasXFA: inspected.hasXFA === true,\n    },\n    // Carry the blank PDF forward \u2014 Inspect returns JSON only, and the fill\n    // step still needs the original binary.\n    binary: $('Fetch Blank PDF').first().binary,\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "if-fields-exist",
      "name": "All Fields Exist?",
      "type": "n8n-nodes-base.if",
      "position": [
        1240,
        180
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-fields-known",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.unknownCount === 0 }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "pdfops-fill",
      "name": "PDFops Fill & Flatten",
      "type": "n8n-nodes-pdfops.pdfops",
      "position": [
        1460,
        180
      ],
      "parameters": {
        "fields": "={{ $json.requestedFields }}",
        "flatten": "={{ $json.flattenOutput }}",
        "fileName": "={{ $json.outputFileName }}",
        "operation": "fill",
        "binaryPropertyName": "data",
        "outputBinaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "gmail-send",
      "name": "Email Filled PDF",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1680,
        180
      ],
      "parameters": {
        "sendTo": "={{ $('Set Delivery Options').item.json.recipient }}",
        "message": "Attached is your completed form. The values are flattened into the page, so the document cannot be edited.",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {
                "property": "data"
              }
            ]
          }
        },
        "subject": "={{ $('Set Delivery Options').item.json.emailSubject }}",
        "resource": "message",
        "emailType": "text",
        "operation": "send"
      },
      "typeVersion": 2.1
    },
    {
      "id": "respond-success",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1900,
        180
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'sent', file: $('Set Delivery Options').item.json.outputFileName, to: $('Set Delivery Options').item.json.recipient, fieldsFilled: Object.keys($('Set Delivery Options').item.json.requestedFields || {}).length }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "drive-archive",
      "name": "Archive to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1680,
        380
      ],
      "parameters": {
        "name": "={{ $('Set Delivery Options').item.json.outputFileName }}",
        "driveId": {
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "mode": "list",
          "value": "root",
          "cachedResultName": "/ (Root folder)"
        },
        "resource": "file",
        "operation": "upload",
        "inputDataFieldName": "data"
      },
      "typeVersion": 3
    },
    {
      "id": "respond-error",
      "name": "Respond With Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1020,
        680
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'error', reason: $json.unknownCount > 0 ? 'unknown_field_names' : ($json.error ? 'pdf_download_failed' : 'invalid_payload'), unknownFields: $json.unknownFields || [], availableFields: $json.availableFields || [] }) }}",
        "responseCode": 400
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Payload Valid?": {
      "main": [
        [
          {
            "node": "Fetch Blank PDF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Blank PDF": {
      "main": [
        [
          {
            "node": "PDFops Inspect Fields",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Form Submission": {
      "main": [
        [
          {
            "node": "Set Delivery Options",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Filled PDF": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "All Fields Exist?": {
      "main": [
        [
          {
            "node": "PDFops Fill & Flatten",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond With Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Delivery Options": {
      "main": [
        [
          {
            "node": "Payload Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDFops Fill & Flatten": {
      "main": [
        [
          {
            "node": "Email Filled PDF",
            "type": "main",
            "index": 0
          },
          {
            "node": "Archive to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDFops Inspect Fields": {
      "main": [
        [
          {
            "node": "Compare Requested Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Requested Fields": {
      "main": [
        [
          {
            "node": "All Fields Exist?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow receives a webhook payload with a PDF URL, form field values, and an email address, then downloads the fillable PDF, fills it using PDFops, and emails the completed PDF via Gmail. Receives a POST webhook request containing , a object, and the recipient address.…

Source: https://n8n.io/workflows/17884/ — 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

Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, httpRequest, googleSheets. Webhook trigger; 54 nodes.

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

Fireflies to Drive and Email (Unified). Uses httpRequest, googleDrive, gmail, moveBinaryData. Webhook trigger; 44 nodes.

HTTP Request, Google Drive, Gmail +1
Email & Gmail

Purchase Approval system. Uses gmail, googleSheets, httpRequest, googleDrive. Webhook trigger; 26 nodes.

Gmail, Google Sheets, HTTP Request +1
Email & Gmail

Streamline and standardize your entire client onboarding process with a single end-to-end automation. 🚀📋 This workflow captures detailed client intake data via webhook, automatically creates a fully s

Slack, Asana, HTTP Request +4
Email & Gmail

Quotation-Workflow. Uses httpRequest, googleDrive, gmail. Webhook trigger; 23 nodes.

HTTP Request, Google Drive, Gmail