AutomationFlowsWeb Scraping › Generate and Send Freelance Invoices From Airtable via Html-to-pdf and Gmail

Generate and Send Freelance Invoices From Airtable via Html-to-pdf and Gmail

ByKanishka Shrivastava @kanishka-shrivastava on n8n.io

This workflow runs daily to find Airtable projects marked ready to invoice, validates required fields, generates a PDF invoice via an HTML-to-PDF API, emails it with Gmail, and updates the Airtable record as invoiced or flagged as needs info. Runs every day at the configured…

Cron / scheduled trigger★★★★☆ complexity16 nodesAirtableHTTP RequestGmail
Web Scraping Trigger: Cron / scheduled Nodes: 16 Complexity: ★★★★☆ Added:

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

This workflow follows the Airtable → Gmail 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": "0cfAN67Kpkgyt6Hp",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Generate and send freelance invoices from an Airtable project tracker",
  "tags": [],
  "nodes": [
    {
      "id": "e440b307-bfdb-4b63-9e93-6ca51c8d420e",
      "name": "Every day",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        976,
        352
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ff2b1e7a-453e-4ecd-b64f-bd39bd8a9baf",
      "name": "Template Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        -112
      ],
      "parameters": {
        "color": 4,
        "width": 680,
        "height": 1104,
        "content": "## \ud83e\uddfe Generate and Send Freelance Invoices from an Airtable Project Tracker\n\n## Who's it for\nFreelancers and small agencies tracking client projects and billable work in Airtable who want invoices generated and emailed automatically once a project is marked ready to invoice, instead of manually building an invoice in a separate tool every time.\n\n## How it works\nOnce a day, the workflow checks Airtable for projects marked ready to invoice that haven't been billed yet. Each one is checked for the fields an invoice actually needs, client email, project name, and either an hourly rate with hours or a fixed fee. Records missing something are flagged in Airtable instead of generating a broken invoice. Complete records get their total calculated (including tax if applicable), a PDF invoice is generated and emailed straight to the client, and the record is marked invoiced so it's never billed twice.\n\n## How to set up\n1. Connect your Airtable credentials and point the workflow at your project base and table.\n2. Connect your Gmail credentials to the send node.\n3. Get an API key from your HTML-to-PDF provider.\n4. Make sure your Airtable table has the fields listed in the sticky note near the fetch step.\n5. Open the Set Configuration node and fill in your base ID, table name, tax percentage, business name, and PDF API details.\n6. Set your preferred run time in the Schedule Trigger.\n7. Activate the workflow.\n\n## Requirements\n- Airtable account with a project tracker base\n- Gmail account\n- An HTML-to-PDF API\n\n## How to customize\nEdit the invoice HTML template in Build Invoice HTML to match your branding. Adjust the required-fields check in Are Required Fields Complete? if your invoices need additional mandatory info."
      },
      "typeVersion": 1
    },
    {
      "id": "53b9b4c3-8d75-4f1f-b911-e582e8a21d19",
      "name": "Section - Trigger, Config, Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 728,
        "height": 448,
        "content": "## 1\ufe0f\u20e3 2\ufe0f\u20e3 3\ufe0f\u20e3 Trigger + Configuration + Fetch\nRuns daily, pulls every project marked ready to invoice and not yet billed. Required fields: status, invoiced, client_email, client_name, project_name, hourly_rate, billable_hours, fixed_fee."
      },
      "typeVersion": 1
    },
    {
      "id": "2ec56a3c-1357-4a50-8965-4ddd7402c5d2",
      "name": "Section - Validate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 500,
        "height": 396,
        "content": "## 4\ufe0f\u20e3 Validate Before Generating\nOnly complete records get an invoice. Missing client_email, project_name, or a rate/hours (or a fixed_fee) sends the record to the needs-info branch instead of generating a broken invoice."
      },
      "typeVersion": 1
    },
    {
      "id": "bddff099-af2c-4a5d-b73a-96dadd4aedf1",
      "name": "Section - Needs Info Branch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2208,
        592
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 368,
        "content": "## \u274c If Incomplete\nFlags the record in Airtable so you can fix it manually, nothing gets invoiced with missing information."
      },
      "typeVersion": 1
    },
    {
      "id": "57bd8e3f-dea3-4b4b-a4ad-f9bdd2c37198",
      "name": "Section - Calculate and Build",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2192,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 420,
        "content": "## 5\ufe0f\u20e3 6\ufe0f\u20e3 Calculate + Build Invoice\nCalculates the subtotal (fixed fee or rate \u00d7 hours), adds tax, then builds the invoice HTML. Edit the template here for your branding."
      },
      "typeVersion": 1
    },
    {
      "id": "015d9f19-4e2e-407b-a415-12834e602b70",
      "name": "Section - Generate and Send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2848,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 420,
        "content": "## 7\ufe0f\u20e3 8\ufe0f\u20e3 Generate PDF + Send\nGenerates the PDF, attaches it directly to the email (no separate hosting/upload step needed since Gmail accepts binary attachments), then marks the project invoiced."
      },
      "typeVersion": 1
    },
    {
      "id": "ad1f69ea-7c53-4e8d-a03f-ab588367dde1",
      "name": "Flag Airtable - Needs Info",
      "type": "n8n-nodes-base.airtable",
      "position": [
        2320,
        768
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Configuration').item.json.airtable_base_id }}"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Configuration').item.json.airtable_table_name }}"
        },
        "columns": {
          "value": {
            "id": "={{ $json.id }}",
            "status": "needs_info"
          },
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2558e99f-7858-492c-962a-93cd6f5e6234",
      "name": "Calculate Invoice Total",
      "type": "n8n-nodes-base.code",
      "position": [
        2304,
        240
      ],
      "parameters": {
        "jsCode": "const item = $json;\nconst config = $('Set Configuration').first().json;\n\nconst hourlyRate = item.hourly_rate ? Number(item.hourly_rate) : null;\nconst hours = item.billable_hours ? Number(item.billable_hours) : null;\nconst fixedFee = item.fixed_fee ? Number(item.fixed_fee) : null;\n\nconst subtotal = fixedFee != null ? fixedFee : (hourlyRate * hours);\nconst taxPercent = Number(config.tax_percent) || 0;\nconst taxAmount = Math.round((subtotal * taxPercent / 100) * 100) / 100;\nconst totalAmount = Math.round((subtotal + taxAmount) * 100) / 100;\n\nreturn [{\n  json: {\n    ...item,\n    subtotal,\n    tax_amount: taxAmount,\n    total_amount: totalAmount,\n    invoice_date: new Date().toISOString().slice(0, 10)\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c1588d5f-3b70-4453-8b21-d169e5110605",
      "name": "Build Invoice HTML",
      "type": "n8n-nodes-base.set",
      "position": [
        2560,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "html-1",
              "name": "invoice_html",
              "type": "string",
              "value": "=<html><body style=\"font-family: Arial, sans-serif; padding: 24px;\"><h2>{{ $('Set Configuration').item.json.business_name }}</h2><p><strong>Invoice for:</strong> {{ $json.client_name }} ({{ $json.client_email }})</p><p><strong>Project:</strong> {{ $json.project_name }}</p><p><strong>Date:</strong> {{ $json.invoice_date }}</p><table border=\"1\" cellpadding=\"8\" cellspacing=\"0\" style=\"border-collapse: collapse; width: 100%; margin-top: 16px;\"><tr><th>Description</th><th>Amount</th></tr><tr><td>{{ $json.project_name }}</td><td>\u20b9{{ $json.subtotal }}</td></tr><tr><td>Tax</td><td>\u20b9{{ $json.tax_amount }}</td></tr><tr><td><strong>Total</strong></td><td><strong>\u20b9{{ $json.total_amount }}</strong></td></tr></table></body></html>"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "58a0bf48-c43f-4e02-bdc8-17efd8ee7a35",
      "name": "Generate Invoice PDF",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2912,
        336
      ],
      "parameters": {
        "url": "={{ $('Set Configuration').item.json.pdf_api_url }}",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "jsonBody": "={{ JSON.stringify({ html: $json.invoice_html }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "CLIENT-API-KEY",
              "value": "={{ $('Set Configuration').item.json.pdf_api_key }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "6fc5a471-b049-4a69-9dbc-ce17b9a1bc02",
      "name": "Send Invoice Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3120,
        336
      ],
      "parameters": {
        "sendTo": "={{ $('Calculate Invoice Total').item.json.client_email }}",
        "message": "=Hi {{ $('Calculate Invoice Total').item.json.client_name }},\n\nPlease find attached the invoice for {{ $('Calculate Invoice Total').item.json.project_name }}. Total due: \u20b9{{ $('Calculate Invoice Total').item.json.total_amount }}.\n\nThanks,\n{{ $('Set Configuration').item.json.business_name }}",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {}
            ]
          }
        },
        "subject": "=Invoice for {{ $('Calculate Invoice Total').item.json.project_name }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b4b35fa4-0005-49a8-86b2-5b09c2e9b4b9",
      "name": "Update Airtable - Mark Invoiced",
      "type": "n8n-nodes-base.airtable",
      "position": [
        3360,
        336
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Configuration').item.json.airtable_base_id }}"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Configuration').item.json.airtable_table_name }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Calculate Invoice Total').item.json.id }}",
            "invoiced": true,
            "invoice_date": "={{ $('Calculate Invoice Total').item.json.invoice_date }}",
            "invoice_amount": "={{ $('Calculate Invoice Total').item.json.total_amount }}"
          },
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "795528a6-4e2f-4c68-ae86-e90caa45ab0a",
      "name": "Set Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        1200,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "86442326-9f82-497a-88ca-ca06a3d4cc10",
              "name": "business_name",
              "type": "string",
              "value": "REPLACE_WITH_BUSINESS_NAME"
            },
            {
              "id": "c15e4953-066b-4e4a-a226-9636fcf2255e",
              "name": "airtable_base_id",
              "type": "string",
              "value": "REPLACE_WITH_AIRTABLE_BASE_ID"
            },
            {
              "id": "1c4152d8-661d-4f03-b26a-1ea4e01772c6",
              "name": "airtable_table_name",
              "type": "string",
              "value": "Projects"
            },
            {
              "id": "d992eabc-3f1f-4d67-92d3-4c63f26e6d95",
              "name": "tax_percent",
              "type": "string",
              "value": "18"
            },
            {
              "id": "020159fe-9f02-4a8e-b9dc-90774323f4da",
              "name": "pdf_api_url",
              "type": "string",
              "value": "REPLACE_WITH_PDF_API_ENDPOINT"
            },
            {
              "id": "f99a3b85-f624-449a-a7ef-c86b83dd4828",
              "name": "pdf_api_key",
              "type": "string",
              "value": "REPLACE_WITH_PDF_API_KEY"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "8217b8be-63cf-43cc-a56d-9a4f1d4559b5",
      "name": "Fetch Ready-to-Invoice Projects",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1424,
        352
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Configuration').item.json.airtable_base_id }}"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Configuration').item.json.airtable_table_name }}"
        },
        "options": {},
        "operation": "search",
        "filterByFormula": "AND({status} = 'ready_to_invoice', {invoiced} = FALSE())"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "54c97bef-ae32-477b-8a94-e41565503f07",
      "name": "Are Required Fields Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        1808,
        352
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "22f9e7c7-bf8f-4767-818b-ae546c9071d6",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.client_email }}",
              "rightValue": ""
            },
            {
              "id": "c8c14831-ae40-44a8-a3d8-c4e3fbf27c30",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.project_name }}",
              "rightValue": ""
            },
            {
              "id": "a876ce21-73e7-41ac-89a0-c545677a61e0",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ !!$json.fixed_fee || (!!$json.hourly_rate && !!$json.billable_hours) }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "ec6c8083-f676-4e1a-add9-b3bd9b2a2eb7",
  "nodeGroups": [],
  "connections": {
    "Every day": {
      "main": [
        [
          {
            "node": "Set Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Configuration": {
      "main": [
        [
          {
            "node": "Fetch Ready-to-Invoice Projects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Invoice HTML": {
      "main": [
        [
          {
            "node": "Generate Invoice PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Invoice Email": {
      "main": [
        [
          {
            "node": "Update Airtable - Mark Invoiced",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Invoice PDF": {
      "main": [
        [
          {
            "node": "Send Invoice Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Invoice Total": {
      "main": [
        [
          {
            "node": "Build Invoice HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Are Required Fields Complete?": {
      "main": [
        [
          {
            "node": "Calculate Invoice Total",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag Airtable - Needs Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Ready-to-Invoice Projects": {
      "main": [
        [
          {
            "node": "Are Required Fields Complete?",
            "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

About this workflow

This workflow runs daily to find Airtable projects marked ready to invoice, validates required fields, generates a PDF invoice via an HTML-to-PDF API, emails it with Gmail, and updates the Airtable record as invoiced or flagged as needs info. Runs every day at the configured…

Source: https://n8n.io/workflows/17625/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Httprequest Workflow. Uses httpRequest, htmlExtract, gmail. Scheduled trigger; 15 nodes.

HTTP Request, Html Extract, Gmail
Web Scraping

How it works

HTTP Request, Html Extract, Sqlite +2
Web Scraping

Boost your online visibility and stay ahead of the curve with this Automated SEO Audit Workflow Template. This solution streamlines the crucial task of monitoring your website’s health, proactively id

HTTP Request, Gmail
Web Scraping

Automatically send beautifully formatted news digests from any RSS feed (e.g., Prothom Alo) directly to your Gmail inbox on a schedule using this n8n workflow. Ideal for news curators, bloggers, media

Gmail, XML, HTTP Request
Web Scraping

This workflow runs daily, checks competitor webpages listed in Google Sheets, compares the latest page text to the previous snapshot using OpenAI, stores the updated snapshot in Google Sheets, and ema

Google Sheets, HTTP Request, Html Extract +2