20 KiB
Generate PDF documents from HTML with PDF Generator API, Gmail and Supabase
Generate PDF documents from HTML with PDF Generator API, Gmail and Supabase
1. Workflow Overview
Purpose: Automatically generate a PDF from an HTML template using incoming webhook data, validate the recipient email, deliver the PDF via Gmail, store it in Supabase Storage, and record the transaction in Postgres.
Typical use cases: invoices, confirmations, reports, certificates, and any dynamic HTML-to-PDF document pipeline with email delivery + storage + audit logging.
1.1 Input Reception
Receives a structured JSON payload via an HTTP POST webhook and acts as the workflow entry point.
1.2 Validation (Email Verification)
Verifies the client email via Hunter; rejects the request with an HTTP 400 response if invalid.
1.3 Template Retrieval + HTML Population
Fetches the correct HTML template from Postgres based on documentType, then fills {{ ... }} placeholders using the webhook payload.
1.4 PDF Generation
Converts the filled HTML to a PDF file using PDF Generator API.
1.5 Delivery + Storage
Sends the generated PDF to the client via Gmail and uploads it to Supabase Storage.
1.6 Transaction Recording
Inserts an audit/trace record into Postgres including metadata and the Supabase file URL.
2. Block-by-Block Analysis
Block 1.1 — Input Reception
Overview: Accepts a POST request with JSON describing the document, client, and data to inject into the template.
Nodes involved: Receive Document Request (Webhook)
Node: Receive Document Request (Webhook)
- Type / role: Webhook trigger (HTTP entry point).
- Key configuration:
- Method: POST
- Path:
422d2c5b-d1b2-47a2-a1a6-5785885b6374(also used as webhookId)
- Key data expectations (from pinned example):
documentType(e.g.,"invoice")client.name,client.email,client.addressrequest.id,request.createdAtdataobject containing template fields (e.g.,date,price, etc.)
- Connections:
- Output →
Verify Client Email
- Output →
- Potential failures / edge cases:
- Missing required fields (later nodes will fail on expressions like
$json.client.email). - Non-JSON body or wrong content-type if caller misconfigures request.
- Missing required fields (later nodes will fail on expressions like
- Version notes: Node typeVersion
1(standard webhook behavior).
Block 2 — Validation (Email Verification)
Overview: Ensures the provided client email is valid before doing any PDF generation or sending.
Nodes involved: Verify Client Email, Email Is Valid?, Respond – Invalid Email
Node: Verify Client Email
- Type / role: Hunter node (email verifier).
- Key configuration:
- Operation: Email Verifier
- Email:
={{ $json.client.email }} - Credentials:
Hunter account 2
- Connections:
- Input ←
Receive Document Request (Webhook) - Output →
Email Is Valid?
- Input ←
- Potential failures / edge cases:
- Hunter auth/plan limits; API rate limiting.
- Hunter response may not contain the expected
status(would break the IF logic). - Email field missing/null.
- Version notes: typeVersion
1.
Node: Email Is Valid?
- Type / role: IF node to branch based on Hunter result.
- Key configuration:
- Condition:
={{ $json.status }}equals"valid"
- Condition:
- Connections:
- Input ←
Verify Client Email - True output →
Load HTML Template - False output →
Respond – Invalid Email
- Input ←
- Potential failures / edge cases:
- If Hunter returns statuses like
accept_all,unknown, etc., they will be treated as invalid (false branch). - If
$json.statusis undefined, strict validation may force false path.
- If Hunter returns statuses like
- Version notes: IF node typeVersion
2.3with conditions version3.
Node: Respond – Invalid Email
- Type / role: Respond to Webhook (early exit with error).
- Key configuration:
- Response code: 400
- JSON body:
{ "error": { "code": "400 Bad Request", "message": "Email validation failed: invalid email format." } }
- Connections:
- Input ←
Email Is Valid?(false branch) - Output: none (ends request)
- Input ←
- Potential failures / edge cases:
- If the workflow continues in parallel elsewhere (not the case here), multiple responses could occur; here it’s a terminal branch.
- Version notes: Respond node typeVersion
1.5.
Block 3 — Template Retrieval + HTML Population
Overview: Loads the HTML template for the requested document type from Postgres, then replaces {{path}} placeholders with values from webhook JSON.
Nodes involved: Load HTML Template, Populate HTML Template
Node: Load HTML Template
- Type / role: Postgres select from
document_templates. - Key configuration:
- Schema:
public - Table:
document_templates - Filter:
document_typeequals={{ $('Receive Document Request (Webhook)').item.json.documentType }} - Limit:
1 - Output columns:
html_template
- Schema:
- Connections:
- Input ←
Email Is Valid?(true branch) - Output →
Populate HTML Template
- Input ←
- Potential failures / edge cases:
- No matching template: result set empty; downstream code expects
.json.html_templateand may becomeundefined, producing failures or empty PDF. - DB auth/connectivity issues.
- No matching template: result set empty; downstream code expects
- Version notes: Postgres node typeVersion
2.6.
Node: Populate HTML Template
- Type / role: Code node (template filling + filename generation).
- Key configuration / logic (interpreted):
- Reads webhook payload:
const data = $node["Receive Document Request (Webhook)"].json;
- Reads template string:
const html = $node["Load HTML Template"].json.html_template;
- Fills placeholders of the form
{{ some.path }}including array indexes likeitems[0].field. - If a placeholder cannot be resolved, it leaves the original
{{...}}text in place. - Outputs:
json.html= filled HTMLjson.filename=${data.documentType}_${data.request.id}
- Reads webhook payload:
- Connections:
- Input ←
Load HTML Template - Output →
Convert HTML to PDF
- Input ←
- Potential failures / edge cases:
- If
Load HTML Templatereturns no row,htmlbecomes undefined and.replace(...)will throw. - If
data.request.idmissing, filename becomes e.g.invoice_undefined(later used in storage paths and subjects). - Placeholders not matching payload paths remain unreplaced (might be desired, but can also leak template tokens).
- If
- Version notes: Code node typeVersion
2.
Block 4 — PDF Generation
Overview: Converts filled HTML into a PDF binary file using PDF Generator API and returns it as a file output.
Nodes involved: Convert HTML to PDF
Node: Convert HTML to PDF
- Type / role: PDF Generator API node (HTML → PDF conversion).
- Key configuration:
- Resource:
conversion - HTML content:
={{ $json.html }} - Filename:
={{ $json.filename }} - Options:
paper_size:a4orientation:portrait- Output:
file(binary)
- Credentials:
PDF Generator API
- Resource:
- Connections:
- Input ←
Populate HTML Template - Output →
Upload PDF to Supabase StorageandSend PDF to Client Email(fan-out)
- Input ←
- Potential failures / edge cases:
- API auth errors, quota, invalid HTML, conversion timeouts.
- Output binary property naming matters downstream; if the node returns a binary key different from what downstream expects, attachments/upload will fail.
- Version notes: Custom node
@pdfgeneratorapi/n8n-nodes-pdf-generator-apitypeVersion1.
Block 5 — Delivery + Storage
Overview: Delivers the PDF to the client email and uploads the PDF to Supabase Storage.
Nodes involved: Send PDF to Client Email, Upload PDF to Supabase Storage
Node: Send PDF to Client Email
- Type / role: Gmail send (outbound email with attachment).
- Key configuration:
- To:
={{ $('Receive Document Request (Webhook)').item.json.client.email }} - Subject: derived from
$json.filename:- removes trailing
.pdfif present - capitalizes first character
- replaces first
_with a space
- removes trailing
- Body: templated text including client name from webhook
- Attachment configuration:
- Uses UI attachment setting referencing a binary property named:
invoice_2025-0001.pdf
- Uses UI attachment setting referencing a binary property named:
- To:
- Connections:
- Input ←
Convert HTML to PDF - Output: none
- Input ←
- Potential failures / edge cases (important):
- Attachment binary key mismatch: The workflow hard-codes
invoice_2025-0001.pdf. If the PDF node outputs binary under a different property (or if filename changes), Gmail node will send without attachment or error. - Gmail OAuth scopes/refresh token expiration, “From” restrictions, sending limits.
- If
$json.filenamedoes not include.pdf, subject formatting still works but may look odd.
- Attachment binary key mismatch: The workflow hard-codes
- Version notes: Gmail node typeVersion
2.1.
Node: Upload PDF to Supabase Storage
- Type / role: HTTP Request node to Supabase Storage object upload endpoint.
- Key configuration:
- Method: POST
- URL:
https://nwnahpmsaiaekfwuxpft.supabase.co/storage/v1/object/documents/{{documentType}}/{{ $json.filename }}
- Body: binaryData
- Input binary field name:
={{ $json.filename }} - Headers:
Authorization: Bearer YOUR_TOKEN_HEREContent-Type: application/pdfx-upsert: false
- Connections:
- Input ←
Convert HTML to PDF - Output →
Record Document Transaction
- Input ←
- Potential failures / edge cases (important):
- Authorization is placeholder (
YOUR_TOKEN_HERE): must be replaced with a valid Supabase service role key or a properly scoped JWT. - Binary property mismatch:
inputDataFieldNameexpects a binary key equal to$json.filename(e.g.,invoice_2025-0001). If the actual binary key differs (often something likedata), upload will fail. x-upsert: falsewill fail if the object already exists (409 conflict).- Supabase bucket/path assumptions: the URL implies bucket
documentsand folder perdocumentType.
- Authorization is placeholder (
- Version notes: HTTP Request node typeVersion
4.3.
Block 6 — Transaction Recording
Overview: Writes a row into a documents table to store metadata and the storage location of the generated PDF.
Nodes involved: Record Document Transaction
Node: Record Document Transaction
- Type / role: Postgres execute query (INSERT + RETURNING).
- Key configuration:
- SQL inserts:
document_type, document_name, request_id, client_name, client_email, file_path, file_url - Uses
$1..$7placeholders with “Query Replacement” values built from expressions referencing:Receive Document Request (Webhook)(documentType, request.id, client.name, client.email)Populate HTML Template(filename)Upload PDF to Supabase Storage(Key)- Builds file_url as:
https://nwnahpmsaiaekfwuxpft.supabase.co/storage/v1/object/{{Key}}
- Returns inserted
id
- SQL inserts:
- Connections:
- Input ←
Upload PDF to Supabase Storage - Output: none
- Input ←
- Potential failures / edge cases:
- If Supabase response does not include
.Key, expressions will fail or store nulls. - SQL schema mismatch (table/columns not present).
- “Query replacement” formatting issues (quoting/escaping) depending on how n8n applies replacements; ensure values are passed as parameters, not string-concatenated SQL.
- If Supabase response does not include
- Version notes: Postgres node typeVersion
2.6.
3. Summary Table
| Node Name | Node Type | Functional Role | Input Node(s) | Output Node(s) | Sticky Note |
|---|---|---|---|---|---|
| Receive Document Request (Webhook) | n8n-nodes-base.webhook | Entry point: receive POST JSON payload | — | Verify Client Email | ## 1. Webhook input This pinned data simulates a test JSON payload from the webhook. It includes sample information such as the document type, client details, request metadata, and dynamic data used to populate the document template. |
| Verify Client Email | n8n-nodes-base.hunter | Verify client email with Hunter | Receive Document Request (Webhook) | Email Is Valid? | ## 2. Validation – Email verification |
| Email Is Valid? | n8n-nodes-base.if | Branch based on Hunter status == valid | Verify Client Email | Load HTML Template; Respond – Invalid Email | ## 2. Validation – Email verification |
| Respond – Invalid Email | n8n-nodes-base.respondToWebhook | Return HTTP 400 if email invalid | Email Is Valid? (false) | — | ## 2.1. Invalid email |
| Load HTML Template | n8n-nodes-base.postgres | Fetch HTML template by document_type | Email Is Valid? (true) | Populate HTML Template | ## 3. Generate PDF document |
| Populate HTML Template | n8n-nodes-base.code | Replace {{placeholders}} and create filename | Load HTML Template | Convert HTML to PDF | ## 3. Generate PDF document |
| Convert HTML to PDF | @pdfgeneratorapi/n8n-nodes-pdf-generator-api.pdfGeneratorApi | Convert HTML to PDF binary | Populate HTML Template | Upload PDF to Supabase Storage; Send PDF to Client Email | ## 3. Generate PDF document |
| Upload PDF to Supabase Storage | n8n-nodes-base.httpRequest | Upload PDF binary to Supabase Storage | Convert HTML to PDF | Record Document Transaction | ## 4. Email & storage |
| Send PDF to Client Email | n8n-nodes-base.gmail | Email PDF attachment via Gmail | Convert HTML to PDF | — | ## 4. Email & storage |
| Record Document Transaction | n8n-nodes-base.postgres | Insert transaction record into Postgres | Upload PDF to Supabase Storage | — | ## 5. Record transaction |
| Sticky Note | n8n-nodes-base.stickyNote | Workspace documentation | — | — | ## Automate document creation from HTML using PDF Generator API, Gmail & Supabase This workflow automates the creation, validation, and delivery of PDF documents generated from HTML templates using PDF Generator API. ### How it works 1. Receives a document generation request via an HTTP POST webhook. 2. Validates the client’s email address using Hunter Email Verification. 2.1 If the email is invalid, the workflow stops and returns an error response. 3. Generates the document by loading the HTML template, populating it with request data, and converting it into a PDF using PDF Generator API. 4. Sends the generated PDF to the client via Gmail and uploads it to Supabase Storage. 5. Records the document generation transaction in the database. ### Setup Before using this workflow, make sure the following components are set up: * A POST webhook endpoint that accepts structured JSON input. * Hunter API credentials for validating client email addresses. * PDF Generator API credentials for converting HTML documents to PDF. * A Postgres database with tables for: * HTML document templates, * document generation records. * Gmail or SMTP credentials for sending generated documents via email. * Supabase Storage for storing generated PDF files. |
| Sticky Note1 | n8n-nodes-base.stickyNote | Workspace documentation | — | — | (see node content in workflow; applies to webhook block) |
| Sticky Note2 | n8n-nodes-base.stickyNote | Workspace documentation | — | — | ## 2. Validation – Email verification |
| Sticky Note4 | n8n-nodes-base.stickyNote | Workspace documentation | — | — | ## 2.1. Invalid email |
| Sticky Note6 | n8n-nodes-base.stickyNote | Workspace documentation | — | — | ## 3. Generate PDF document |
| Sticky Note7 | n8n-nodes-base.stickyNote | Workspace documentation | — | — | ## 5. Record transaction |
| Sticky Note8 | n8n-nodes-base.stickyNote | Workspace documentation | — | — | ## 4. Email & storage |
Note: Sticky notes are included as nodes in the workflow JSON; they don’t connect to the execution graph.
4. Reproducing the Workflow from Scratch
-
Create the Webhook trigger
- Add node: Webhook
- Method: POST
- Path: generate a unique path (store it; this is your endpoint)
- (Optional) Add pinned sample payload similar to:
documentType,client {name,email},request {id,createdAt},data {…}
-
Add Hunter email verification
- Add node: Hunter
- Operation: Email Verifier
- Email field: expression
{{$json.client.email}} - Create/configure Hunter API credentials in n8n and select them.
- Connect: Webhook → Hunter
-
Branch on validity
- Add node: IF
- Condition:
{{$json.status}}equalsvalid - Connect: Hunter → IF
-
Invalid branch response
- Add node: Respond to Webhook
- Response code: 400
- Respond with: JSON
- Body: the error object used in the workflow (or your own)
- Connect: IF (false) → Respond to Webhook
-
Load the HTML template from Postgres (valid branch)
- Add node: Postgres
- Operation: Select
- Schema:
public - Table:
document_templates - Where:
document_type={{$('Webhook').item.json.documentType}} - Limit:
1 - Output columns:
html_template - Create/configure Postgres credentials in n8n.
- Connect: IF (true) → Postgres (Load HTML Template)
-
Populate the HTML
- Add node: Code
- Paste logic that:
- reads webhook JSON
- reads
html_template - replaces
{{path}}placeholders - outputs
{ html: filledHtml, filename: documentType_requestId }
- Connect: Load HTML Template → Populate HTML Template
-
Convert HTML to PDF (PDF Generator API)
- Add node: PDF Generator API (community/custom node)
- Resource: Conversion
- HTML content:
{{$json.html}} - Filename:
{{$json.filename}} - Options: A4, portrait, output as file
- Create/configure PDF Generator API credentials in n8n.
- Connect: Populate HTML Template → Convert HTML to PDF
-
Upload to Supabase Storage
- Add node: HTTP Request
- Method: POST
- URL:
https://<your-project>.supabase.co/storage/v1/object/documents/{{$('Webhook').item.json.documentType}}/{{$json.filename}}
- Send Body: true
- Body Content Type: Binary Data
- Input Data Field Name: set to the binary property that actually contains the PDF (align with the PDF node output)
- Headers:
Authorization: Bearer <SUPABASE_TOKEN>Content-Type: application/pdfx-upsert: false(or true if you want overwrites)
- Connect: Convert HTML to PDF → Upload to Supabase Storage
-
Send email via Gmail
- Add node: Gmail
- Operation: Send
- To:
{{$('Webhook').item.json.client.email}} - Subject: derive from filename (optional)
- Message: include
client.name(optional) - Attachments: select the PDF binary property from the PDF conversion output
- Create/configure Gmail OAuth2 credentials in n8n.
- Connect: Convert HTML to PDF → Gmail
-
Record transaction in Postgres
- Add node: Postgres
- Operation: Execute Query
- SQL INSERT into
documentswith fields:document_type, document_name, request_id, client_name, client_email, file_path, file_url
- Map parameters from:
- Webhook (
documentType,request.id,client.name,client.email) - Populate HTML Template (
filename) - Supabase upload response (key/path)
- Webhook (
- Connect: Upload to Supabase Storage → Record Document Transaction
5. General Notes & Resources
| Note Content | Context or Link |
|---|---|
| PDF Generator API main site | https://pdfgeneratorapi.com/ |
| PDF Generator API credentials setup in n8n | https://support.pdfgeneratorapi.com/en/article/how-to-integrate-pdf-generator-api-with-n8n-zulhli/#4-step-2-set-your-credentials |
Supabase upload auth header is a placeholder in this workflow (Bearer YOUR_TOKEN_HERE) and must be replaced |
Supabase Storage integration |
Attachment configuration in Gmail node appears hard-coded to invoice_2025-0001.pdf and should be aligned to the actual PDF binary property |
Gmail delivery step |