This workflow corresponds to n8n.io template #12113 — we link there as the canonical source.
This workflow follows the Gmail → Google Sheets 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 →
{
"name": "Generate invoices and send payment reminders with escalation using Google Sheets",
"nodes": [
{
"id": "sticky-overview",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-280,
80
],
"parameters": {
"width": 400,
"height": 560,
"content": "## Generate invoices and send payment reminders with escalation\n\n### Who is this for?\nFreelancers, small businesses, and finance teams who need automated invoice management with intelligent payment follow-ups.\n\n### What this workflow does\n**Invoice Generation:**\n- Receives order data with line items\n- Splits and calculates each line item\n- Aggregates totals and saves to Sheets\n- Sends invoice email with payment link\n\n**Payment Reminders:**\n- Daily check for overdue invoices\n- Loops through unpaid items\n- 5-level escalation via Switch node\n- Slack notification for collections\n\n### Setup\n1. Create Google Sheet with invoice columns\n2. Connect Google Sheets and Gmail\n3. Configure Slack for escalations\n\n### Requirements\n- Google Sheets\n- Gmail account\n- Slack workspace"
},
"typeVersion": 1
},
{
"id": "sticky-step1",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
180,
180
],
"parameters": {
"color": 7,
"width": 280,
"height": 80,
"content": "**Step 1: Invoice Creation**\nReceive order data, generate invoice ID, and initialize invoice fields."
},
"typeVersion": 1
},
{
"id": "sticky-step2",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
180
],
"parameters": {
"color": 7,
"width": 300,
"height": 80,
"content": "**Step 2: Line Item Processing**\nSplit Out each line item, calculate totals, and Aggregate results."
},
"typeVersion": 1
},
{
"id": "sticky-step3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1320,
420
],
"parameters": {
"color": 7,
"width": 320,
"height": 80,
"content": "**Step 3: Daily Overdue Check**\nSchedule Trigger runs daily. Loop Over batches each invoice for processing."
},
"typeVersion": 1
},
{
"id": "sticky-step4",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1980,
180
],
"parameters": {
"color": 7,
"width": 320,
"height": 80,
"content": "**Step 4: Reminder Escalation**\nSwitch routes to 5 reminder levels based on days overdue. Collections at 60+ days."
},
"typeVersion": 1
},
{
"id": "webhook-invoice",
"name": "New Invoice Request",
"type": "n8n-nodes-base.webhook",
"position": [
180,
300
],
"parameters": {
"path": "create-invoice",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "init-invoice",
"name": "Initialize Invoice",
"type": "n8n-nodes-base.set",
"position": [
400,
300
],
"parameters": {
"mode": "manual",
"options": {},
"assignments": {
"assignments": [
{
"id": "1",
"name": "invoiceId",
"type": "string",
"value": "={{ 'INV-' + $now.format('yyyy') + '-' + String(Math.floor(Math.random() * 9000) + 1000) }}"
},
{
"id": "2",
"name": "clientName",
"type": "string",
"value": "={{ $json.body.clientName }}"
},
{
"id": "3",
"name": "clientEmail",
"type": "string",
"value": "={{ $json.body.clientEmail }}"
},
{
"id": "4",
"name": "lineItems",
"type": "object",
"value": "={{ $json.body.lineItems }}"
},
{
"id": "5",
"name": "paymentTerms",
"type": "number",
"value": "={{ $json.body.paymentTerms || 30 }}"
},
{
"id": "6",
"name": "taxRate",
"type": "number",
"value": "={{ $json.body.taxRate || 0.1 }}"
},
{
"id": "7",
"name": "createdAt",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "8",
"name": "dueDate",
"type": "string",
"value": "={{ $now.plus({ days: $json.body.paymentTerms || 30 }).toISODate() }}"
}
]
},
"duplicateItem": false
},
"typeVersion": 3.4
},
{
"id": "split-line-items",
"name": "Split Line Items",
"type": "n8n-nodes-base.splitOut",
"position": [
620,
300
],
"parameters": {
"include": "allOtherFields",
"options": {},
"fieldToSplitOut": "lineItems"
},
"typeVersion": 1
},
{
"id": "calc-line-total",
"name": "Calculate Line Total",
"type": "n8n-nodes-base.code",
"position": [
840,
300
],
"parameters": {
"jsCode": "const item = $input.first().json;\nconst lineItem = item.lineItems;\n\nconst quantity = lineItem.quantity || 1;\nconst rate = lineItem.rate || 0;\nconst lineTotal = quantity * rate;\n\nreturn [{\n json: {\n invoiceId: item.invoiceId,\n clientName: item.clientName,\n clientEmail: item.clientEmail,\n taxRate: item.taxRate,\n dueDate: item.dueDate,\n lineItem: {\n description: lineItem.description,\n quantity: quantity,\n rate: rate,\n lineTotal: lineTotal\n }\n }\n}];"
},
"typeVersion": 2
},
{
"id": "aggregate-lines",
"name": "Aggregate Line Items",
"type": "n8n-nodes-base.aggregate",
"position": [
1060,
300
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "processedLines"
},
"typeVersion": 1
},
{
"id": "build-invoice",
"name": "Build Invoice Object",
"type": "n8n-nodes-base.code",
"position": [
1280,
300
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst lines = data.processedLines || [];\n\nconst invoiceId = lines[0]?.invoiceId || 'unknown';\nconst clientName = lines[0]?.clientName || '';\nconst clientEmail = lines[0]?.clientEmail || '';\nconst taxRate = lines[0]?.taxRate || 0.1;\nconst dueDate = lines[0]?.dueDate || '';\n\nconst subtotal = lines.reduce((sum, l) => sum + (l.lineItem?.lineTotal || 0), 0);\nconst tax = Math.round(subtotal * taxRate * 100) / 100;\nconst total = subtotal + tax;\n\nconst paymentLink = `https://pay.company.com/invoice/${invoiceId}`;\n\nreturn [{\n json: {\n invoiceId: invoiceId,\n clientName: clientName,\n clientEmail: clientEmail,\n lineItems: lines.map(l => l.lineItem),\n subtotal: subtotal,\n taxRate: taxRate,\n tax: tax,\n total: total,\n dueDate: dueDate,\n paymentLink: paymentLink,\n status: 'pending',\n createdAt: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "save-invoice",
"name": "Save Invoice to Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1500,
300
],
"parameters": {
"columns": {
"value": {
"Tax": "={{ $json.tax }}",
"Email": "={{ $json.clientEmail }}",
"Total": "={{ $json.total }}",
"Client": "={{ $json.clientName }}",
"Status": "={{ $json.status }}",
"Created": "={{ $json.createdAt }}",
"Due Date": "={{ $json.dueDate }}",
"Subtotal": "={{ $json.subtotal }}",
"Invoice ID": "={{ $json.invoiceId }}",
"Last Reminder": ""
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Invoices"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.5
},
{
"id": "send-invoice-email",
"name": "Send Invoice Email",
"type": "n8n-nodes-base.gmail",
"position": [
1720,
300
],
"parameters": {
"sendTo": "={{ $json.clientEmail }}",
"message": "=Dear {{ $json.clientName }},\n\nPlease find your invoice details:\n\nInvoice: {{ $json.invoiceId }}\nAmount: ${{ $json.total.toFixed(2) }}\nDue Date: {{ $json.dueDate }}\n\nPay now: {{ $json.paymentLink }}\n\nThank you!",
"options": {},
"subject": "=Invoice {{ $json.invoiceId }} from Company"
},
"typeVersion": 2.1
},
{
"id": "invoice-response",
"name": "Invoice Created Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1940,
300
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, invoiceId: $json.invoiceId, total: $json.total, dueDate: $json.dueDate }) }}"
},
"typeVersion": 1.1
},
{
"id": "daily-check",
"name": "Daily Overdue Check",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1320,
540
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.2
},
{
"id": "get-unpaid",
"name": "Get Unpaid Invoices",
"type": "n8n-nodes-base.googleSheets",
"position": [
1540,
540
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "pending",
"lookupColumn": "Status"
}
]
},
"operation": "getMany",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Invoices"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.5
},
{
"id": "calc-overdue",
"name": "Calculate Overdue Days",
"type": "n8n-nodes-base.code",
"position": [
1760,
540
],
"parameters": {
"jsCode": "const invoices = $input.all();\nconst today = new Date();\n\nconst overdueInvoices = invoices.map(inv => {\n const dueDate = new Date(inv.json['Due Date']);\n const daysOverdue = Math.floor((today - dueDate) / (1000 * 60 * 60 * 24));\n \n let reminderLevel = 'none';\n if (daysOverdue >= 60) reminderLevel = 'collections';\n else if (daysOverdue >= 31) reminderLevel = 'final';\n else if (daysOverdue >= 15) reminderLevel = 'urgent';\n else if (daysOverdue >= 8) reminderLevel = 'second';\n else if (daysOverdue >= 1) reminderLevel = 'first';\n \n return {\n json: {\n invoiceId: inv.json['Invoice ID'],\n clientName: inv.json['Client'],\n clientEmail: inv.json['Email'],\n total: parseFloat(inv.json['Total']) || 0,\n dueDate: inv.json['Due Date'],\n daysOverdue: daysOverdue,\n reminderLevel: reminderLevel\n }\n };\n}).filter(inv => inv.json.daysOverdue > 0);\n\nreturn overdueInvoices;"
},
"typeVersion": 2
},
{
"id": "loop-invoices",
"name": "Loop Over Invoices",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1980,
540
],
"parameters": {
"options": {},
"batchSize": 1
},
"typeVersion": 3
},
{
"id": "route-reminder",
"name": "Route by Reminder Level",
"type": "n8n-nodes-base.switch",
"position": [
2200,
540
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "First",
"conditions": {
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.reminderLevel }}",
"rightValue": "first"
}
]
}
},
{
"outputKey": "Second",
"conditions": {
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.reminderLevel }}",
"rightValue": "second"
}
]
}
},
{
"outputKey": "Urgent",
"conditions": {
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.reminderLevel }}",
"rightValue": "urgent"
}
]
}
},
{
"outputKey": "Final",
"conditions": {
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.reminderLevel }}",
"rightValue": "final"
}
]
}
},
{
"outputKey": "Collections",
"conditions": {
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.reminderLevel }}",
"rightValue": "collections"
}
]
}
}
]
},
"options": {
"fallbackOutput": "none"
}
},
"typeVersion": 3.2
},
{
"id": "first-reminder",
"name": "First Reminder Email",
"type": "n8n-nodes-base.gmail",
"position": [
2440,
320
],
"parameters": {
"sendTo": "={{ $json.clientEmail }}",
"message": "=Hi {{ $json.clientName }},\n\nThis is a friendly reminder that invoice {{ $json.invoiceId }} for ${{ $json.total.toFixed(2) }} was due on {{ $json.dueDate }}.\n\nPlease process payment at your earliest convenience.\n\nThank you!",
"options": {},
"subject": "=Reminder: Invoice {{ $json.invoiceId }} is Due"
},
"typeVersion": 2.1
},
{
"id": "second-reminder",
"name": "Second Reminder Email",
"type": "n8n-nodes-base.gmail",
"position": [
2440,
460
],
"parameters": {
"sendTo": "={{ $json.clientEmail }}",
"message": "=Dear {{ $json.clientName }},\n\nYour invoice {{ $json.invoiceId }} is now {{ $json.daysOverdue }} days overdue.\n\nAmount: ${{ $json.total.toFixed(2) }}\n\nPlease arrange payment immediately.",
"options": {},
"subject": "=Second Notice: Invoice {{ $json.invoiceId }}"
},
"typeVersion": 2.1
},
{
"id": "urgent-reminder",
"name": "Urgent Reminder Email",
"type": "n8n-nodes-base.gmail",
"position": [
2440,
600
],
"parameters": {
"sendTo": "={{ $json.clientEmail }}",
"message": "=Dear {{ $json.clientName }},\n\nURGENT: Your account has an outstanding balance of ${{ $json.total.toFixed(2) }} that is {{ $json.daysOverdue }} days past due.\n\nPlease contact us immediately.",
"options": {},
"subject": "=URGENT: Invoice {{ $json.invoiceId }}"
},
"typeVersion": 2.1
},
{
"id": "final-reminder",
"name": "Final Notice Email",
"type": "n8n-nodes-base.gmail",
"position": [
2440,
740
],
"parameters": {
"sendTo": "={{ $json.clientEmail }}",
"message": "=Dear {{ $json.clientName }},\n\nFINAL NOTICE: Invoice {{ $json.invoiceId }} is now {{ $json.daysOverdue }} days overdue.\n\nAmount: ${{ $json.total.toFixed(2) }}\n\nThis is your final notice before collections.",
"options": {},
"subject": "=FINAL NOTICE: Invoice {{ $json.invoiceId }}"
},
"typeVersion": 2.1
},
{
"id": "collections-slack",
"name": "Escalate to Collections",
"type": "n8n-nodes-base.slack",
"position": [
2440,
880
],
"parameters": {
"text": "=:warning: *Collections Escalation*\n\nInvoice: {{ $json.invoiceId }}\nClient: {{ $json.clientName }}\nAmount: ${{ $json.total.toFixed(2) }}\nDays Overdue: {{ $json.daysOverdue }}",
"select": "channel",
"resource": "message",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#collections"
},
"operation": "post",
"otherOptions": {}
},
"typeVersion": 2.2
},
{
"id": "merge-reminders",
"name": "Merge Reminder Paths",
"type": "n8n-nodes-base.merge",
"position": [
2680,
540
],
"parameters": {
"mode": "chooseBranch",
"options": {}
},
"typeVersion": 3
},
{
"id": "update-reminder-date",
"name": "Update Reminder Date",
"type": "n8n-nodes-base.googleSheets",
"position": [
2900,
540
],
"parameters": {
"columns": {
"value": {
"Invoice ID": "={{ $json.invoiceId }}",
"Last Reminder": "={{ $now.toISO() }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "Invoices"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.5
},
{
"id": "continue-loop",
"name": "Continue Loop",
"type": "n8n-nodes-base.code",
"position": [
3120,
540
],
"parameters": {
"jsCode": "return $input.all();"
},
"typeVersion": 2
}
],
"settings": {
"executionOrder": "v1"
},
"connections": {
"Continue Loop": {
"main": [
[
{
"node": "Loop Over Invoices",
"type": "main",
"index": 0
}
]
]
},
"Split Line Items": {
"main": [
[
{
"node": "Calculate Line Total",
"type": "main",
"index": 0
}
]
]
},
"Final Notice Email": {
"main": [
[
{
"node": "Merge Reminder Paths",
"type": "main",
"index": 0
}
]
]
},
"Initialize Invoice": {
"main": [
[
{
"node": "Split Line Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Invoices": {
"main": [
[
{
"node": "Route by Reminder Level",
"type": "main",
"index": 0
}
],
[]
]
},
"Send Invoice Email": {
"main": [
[
{
"node": "Invoice Created Response",
"type": "main",
"index": 0
}
]
]
},
"Daily Overdue Check": {
"main": [
[
{
"node": "Get Unpaid Invoices",
"type": "main",
"index": 0
}
]
]
},
"Get Unpaid Invoices": {
"main": [
[
{
"node": "Calculate Overdue Days",
"type": "main",
"index": 0
}
]
]
},
"New Invoice Request": {
"main": [
[
{
"node": "Initialize Invoice",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Line Items": {
"main": [
[
{
"node": "Build Invoice Object",
"type": "main",
"index": 0
}
]
]
},
"Build Invoice Object": {
"main": [
[
{
"node": "Save Invoice to Sheet",
"type": "main",
"index": 0
}
]
]
},
"Calculate Line Total": {
"main": [
[
{
"node": "Aggregate Line Items",
"type": "main",
"index": 0
}
]
]
},
"First Reminder Email": {
"main": [
[
{
"node": "Merge Reminder Paths",
"type": "main",
"index": 0
}
]
]
},
"Merge Reminder Paths": {
"main": [
[
{
"node": "Update Reminder Date",
"type": "main",
"index": 0
}
]
]
},
"Update Reminder Date": {
"main": [
[
{
"node": "Continue Loop",
"type": "main",
"index": 0
}
]
]
},
"Save Invoice to Sheet": {
"main": [
[
{
"node": "Send Invoice Email",
"type": "main",
"index": 0
}
]
]
},
"Second Reminder Email": {
"main": [
[
{
"node": "Merge Reminder Paths",
"type": "main",
"index": 0
}
]
]
},
"Urgent Reminder Email": {
"main": [
[
{
"node": "Merge Reminder Paths",
"type": "main",
"index": 0
}
]
]
},
"Calculate Overdue Days": {
"main": [
[
{
"node": "Loop Over Invoices",
"type": "main",
"index": 0
}
]
]
},
"Escalate to Collections": {
"main": [
[
{
"node": "Merge Reminder Paths",
"type": "main",
"index": 0
}
]
]
},
"Route by Reminder Level": {
"main": [
[
{
"node": "First Reminder Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Second Reminder Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Urgent Reminder Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Final Notice Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Escalate to Collections",
"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 template is designed for freelancers, small businesses, and finance teams who need automated invoice management with intelligent payment follow-ups. Perfect for service providers, agencies, or any business that needs to track receivables and reduce late payments.
Source: https://n8n.io/workflows/12113/ — 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.
Automate short-term trading research by generating high-quality trade ideas using MCP (Market Context Protocol) signals and AI-powered analysis. 📈🤖 This workflow evaluates market context, catalysts, m
This n8n workflow is designed to centralize the management and tracking of customer inquiries received through multiple channels (email and web forms).
Receive booking requests via webhook with automatic validation, duplicate detection, availability checking, confirmation emails, Google Calendar sync, and Slack notifications.
This workflow automatically detects bounced or invalid email addresses from your Gmail inbox and updates their status in Google Sheets. It fetches bounce notifications, extracts failed email addresses
A complete, production-ready newsletter automation workflow that validates email addresses, sends personalized welcome emails, and maintains comprehensive logs in Google Sheets. Perfect for marketing