I used to spend 20 minutes on every client invoice: open Google Docs, copy the template, fill in the details, export to PDF, email it, update my spreadsheet. For 10 clients a month, that's over 3 hours of busywork.
Here's the n8n workflow I built that does the same thing in under 10 seconds.
What it does
- Webhook trigger — receives invoice data (client name, email, items, amounts)
- Build invoice HTML — generates a clean professional invoice
- Email to client — sends the invoice automatically
- Log to Google Sheets — records invoice number, client, amount, date
- Return confirmation — sends back a success response
The workflow JSON
{"name":"Invoice Generator","nodes":[{"parameters":{"httpMethod":"POST","path":"invoice","responseMode":"responseNode","options":{}},"id":"i1","name":"Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[240,300]},{"parameters":{"jsCode":"const d=$json.body||$json;const invoiceNum='INV-'+Date.now().toString().slice(-6);const date=new Date().toLocaleDateString('en-US',{year:'numeric',month:'long',day:'numeric'});const items=d.items||[{description:'Service',quantity:1,rate:100}];const subtotal=items.reduce((s,i)=>s+(i.quantity*i.rate),0);const tax=Math.round(subtotal*0.1*100)/100;const total=subtotal+tax;const rows=items.map(i=>`<tr><td>${i.description}</td><td>${i.quantity}</td><td>$${i.rate}</td><td>$${i.quantity*i.rate}</td></tr>`).join('');const html=`<!DOCTYPE html><html><head><style>body{font-family:Arial,sans-serif;padding:40px;color:#333}h1{color:#2c3e50}table{width:100%;border-collapse:collapse;margin-top:20px}th{background:#2c3e50;color:white;padding:12px;text-align:left}td{padding:10px;border-bottom:1px solid #eee}.total{font-size:1.2em;font-weight:bold}</style></head><body><h1>INVOICE #${invoiceNum}</h1><p>Date: ${date}</p><h3>Bill To: ${d.clientName||'Client'}</h3><table><thead><tr><th>Description</th><th>Qty</th><th>Rate</th><th>Amount</th></tr></thead><tbody>${rows}</tbody></table><p>Subtotal: $${subtotal} | Tax (10%): $${tax} | <span class='total'>Total: $${total}</span></p><p>Payment due within 30 days. Thank you!</p></body></html>`;return [{json:{...d,invoiceNum,date,html,subtotal,tax,total}}];"},"id":"i2","name":"Build Invoice","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300]},{"parameters":{"sendTo":"={{ $json.clientEmail }}","subject":"Invoice {{ $json.invoiceNum }}","message":"Hi {{ $json.clientName }},\n\nYour invoice is attached.\n\nInvoice: {{ $json.invoiceNum }}\nAmount: ${{ $json.total }}\nDue: 30 days\n\nThank you!","options":{}},"id":"i3","name":"Email Invoice","type":"n8n-nodes-base.gmail","typeVersion":2,"position":[680,300]},{"parameters":{"operation":"append","documentId":{"value":"YOUR_SHEET_ID"},"sheetName":"Invoices","columns":{"mappingMode":"defineBelow","value":{"Invoice":"={{ $json.invoiceNum }}","Client":"={{ $json.clientName }}","Amount":"={{ $json.total }}","Date":"={{ $json.date }}","Status":"Sent"}},"options":{}},"id":"i4","name":"Log to Sheets","type":"n8n-nodes-base.googleSheets","typeVersion":4,"position":[900,300]},{"parameters":{"respondWith":"json","responseBody":"={\"success\":true,\"invoice\":\"{{ $json.invoiceNum }}\",\"total\":{{ $json.total }}}","options":{}},"id":"i5","name":"Respond","type":"n8n-nodes-base.respondToWebhook","typeVersion":1,"position":[1120,300]}],"connections":{"Webhook":{"main":[[{"node":"Build Invoice","type":"main","index":0}]]},"Build Invoice":{"main":[[{"node":"Email Invoice","type":"main","index":0}]]},"Email Invoice":{"main":[[{"node":"Log to Sheets","type":"main","index":0}]]},"Log to Sheets":{"main":[[{"node":"Respond","type":"main","index":0}]]}}}
How to use it
Import: Workflows → New → ⋯ menu → Import from JSON → paste above.
Send an invoice with one POST request:
curl -X POST https://your-n8n.com/webhook/invoice \
-H "Content-Type: application/json" \
-d '{
"clientName": "Acme Corp",
"clientEmail": "billing@acme.com",
"items": [
{"description": "Web Development", "quantity": 10, "rate": 150},
{"description": "Hosting Setup", "quantity": 1, "rate": 200}
]
}'
Client gets a professional invoice email. Spreadsheet is updated. You get a confirmation. Under 10 seconds.
Customize it
- Replace
YOUR_SHEET_IDwith your Google Sheets ID - Change the tax rate in the Code node (currently 10%)
- Add your company name/logo to the HTML template
- Trigger via form: swap the Webhook node for a Typeform or Google Forms trigger
4 tips to make it production-ready
1. Sequential invoice numbers
Replace Date.now() with a Sheets counter to get INV-001, INV-002, etc. Add a "read last invoice number" step before the Code node, increment by 1, write it back.
2. Mark paid automatically
When Stripe/PayPal sends a payment webhook, add a second workflow that finds the matching row in Sheets and updates Status to "Paid" — then sends a receipt.
3. Overdue reminders
Schedule a daily n8n run that checks Sheets for invoices older than 30 days with Status "Sent" and fires a follow-up email automatically.
4. Multi-currency
Add a currency field to the webhook payload and drop it into the HTML template. One field, zero extra nodes.
The JSON above is the complete working core. If you want sequential invoice numbers, a polished PDF layout, and the auto-reminder flow wired up, I packaged everything with a setup guide at stripeai.gumroad.com. Happy to answer setup questions in the comments!
Top comments (0)