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 →
{
"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
}
]
]
}
}
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, httpRequest, googleSheets. Webhook trigger; 54 nodes.
Fireflies to Drive and Email (Unified). Uses httpRequest, googleDrive, gmail, moveBinaryData. Webhook trigger; 44 nodes.
Purchase Approval system. Uses gmail, googleSheets, httpRequest, googleDrive. Webhook trigger; 26 nodes.
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
Quotation-Workflow. Uses httpRequest, googleDrive, gmail. Webhook trigger; 23 nodes.