This workflow corresponds to n8n.io template #17461 — we link there as the canonical source.
This workflow follows the Emailsend → HTTP Request 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": "Invoice Reminder & Collections Ladder",
"tags": [],
"nodes": [
{
"id": "a451d826-b857-4daa-94d2-ca509a648ba9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-288
],
"parameters": {
"width": 480,
"height": 816,
"content": "## Invoice Reminder & Collections Ladder\n\n### How it works\n\n1. The workflow triggers daily to check for overdue invoices from Clio.\n2. It normalizes and triages the fetched invoices to identify those due for reminders today.\n3. It branches to either skip processing or to handle partner escalation alerts or client reminder emails based on invoice status.\n4. Partner alerts are built and emailed for invoices overdue more than 60 days.\n5. Client reminder emails undergo a compliance check before optionally being sent, or the client opt-out is respected.\n\n### Setup steps\n\n- [ ] Configure the 'Daily Invoice Check' schedule trigger according to desired frequency.\n- [ ] Set environment variable CLIO_BASE_URL for the Clio API endpoint.\n- [ ] Provide credentials for HTTP requests to Clio API in the 'Fetch Overdue Invoices from Clio' node.\n- [ ] Ensure email credentials and settings are configured for sending emails in the 'Alert Partner' and 'Send Invoice Reminder' nodes.\n- [ ] Verify access and configuration of the sub-workflow used in 'Compliance Check \u2014 Invoice Reminder'.\n"
},
"typeVersion": 1
},
{
"id": "65bcc9c5-a6aa-4f77-a530-224d55526e70",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-128
],
"parameters": {
"color": 7,
"width": 416,
"height": 304,
"content": "## Trigger and fetch invoices\n\nStarts the workflow and obtains overdue invoices from Clio API."
},
"typeVersion": 1
},
{
"id": "43383e1f-b108-4d36-ba9c-5a7f412a99f6",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
-192
],
"parameters": {
"color": 7,
"width": 240,
"height": 352,
"content": "## Normalize and triage invoices\n\nProcesses raw invoice data to prepare for conditional checks on due invoices."
},
"typeVersion": 1
},
{
"id": "9496141d-2f78-47fa-ba4a-b0e750ac8620",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
-128
],
"parameters": {
"color": 7,
"width": 416,
"height": 496,
"content": "## Invoice due check and branching\n\nDetermines if invoices are due today and directs flow to partner escalation or skipping."
},
"typeVersion": 1
},
{
"id": "d8d3ff8d-b601-4a19-a4cc-09bc51b18a72",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
-288
],
"parameters": {
"color": 7,
"width": 416,
"height": 304,
"content": "## Partner escalation alerts\n\nBuilds and sends alerts for invoices overdue 60+ days to partners."
},
"typeVersion": 1
},
{
"id": "71b1ea33-86d3-4699-a69f-80aa1bc7b9d0",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
48
],
"parameters": {
"color": 7,
"width": 848,
"height": 464,
"content": "## Client reminder email flow\n\nPrepares client reminder emails, performs compliance checks, and sends or skips based on approval and opt-out status."
},
"typeVersion": 1
},
{
"id": "1800cc00-0003-4000-c000-000000000006",
"name": "Every Morning at 8am",
"type": "n8n-nodes-base.scheduleTrigger",
"notes": "Fires every day at 8am server time. Each run checks Clio for overdue invoices and sends whichever ladder step is due today.\n\nRunning daily (not weekday-only) ensures invoices that fall due on a Friday are not missed over the weekend.\n\nTo change the run time: edit the cron expression. Format: minute hour * * * (0 8 * * * = 8am every day).",
"position": [
0,
0
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "1800cc00-0003-4000-c000-000000000007",
"name": "Fetch Overdue Invoices from Clio API",
"type": "n8n-nodes-base.httpRequest",
"notes": "Pulls all overdue invoices from Clio in a single request. Clio automatically removes paid invoices from the overdue set \u2014 so when a client pays, their invoice disappears from this query and the reminder ladder stops with no extra logic needed.\n\nEU firms: set CLIO_BASE_URL to https://eu.app.clio.com\n\nReturns up to 200 invoices per run. If your firm regularly has more than 200 overdue invoices at once, add a loop over the `page` parameter.\n\nCredential: Clio API (Bearer token). Clio personal API tokens do not expire automatically, but rotate yours every 90 days as a security best practice.",
"position": [
220,
0
],
"parameters": {
"url": "={{ $vars.CLIO_BASE_URL }}/api/v4/bills.json",
"method": "GET",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "status",
"value": "overdue"
},
{
"name": "limit",
"value": "200"
},
{
"name": "fields",
"value": "id,number,due_at,balance,total,share_url,client{id,name,email},matter{id,display_number}"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "1800cc00-0003-4000-c000-000000000008",
"name": "Normalize and Triage Overdue Invoices",
"type": "n8n-nodes-base.code",
"notes": "Computes how many days each overdue invoice has been unpaid and assigns a ladder step. Returns one item per invoice that is due for a reminder today.\n\nLadder windows use a 2-day range (e.g. days 1\u20132, 7\u20138) so a reminder is not missed if the cron runs a day late due to a server restart or holiday.\n\nInvoices with a zero or negative balance are skipped \u2014 Clio should not return these on the overdue endpoint, but this guards against it.\n\nIf no invoices are due for a reminder today, returns a single sentinel item with is_actionable: false, which the next node routes to a quiet skip.",
"position": [
440,
0
],
"parameters": {
"jsCode": "const bills = $input.first().json.data || [];\nconst results = [];\n\nconst today = new Date();\ntoday.setHours(0, 0, 0, 0);\nconst msPerDay = 24 * 60 * 60 * 1000;\n\nfor (const bill of bills) {\n const balance = Number(bill.balance || 0);\n if (balance <= 0) continue;\n\n // Use noon UTC to avoid off-by-one errors caused by timezone offsets on the due date string\n const dueDate = new Date(bill.due_at + 'T12:00:00Z');\n dueDate.setHours(0, 0, 0, 0);\n const daysOverdue = Math.max(0, Math.floor((today - dueDate) / msPerDay));\n\n // 2-day windows per step absorb weekends and cron timing drift\n let ladder_step = 'none';\n if (daysOverdue >= 1 && daysOverdue <= 2) ladder_step = 'day1';\n else if (daysOverdue >= 7 && daysOverdue <= 8) ladder_step = 'day7';\n else if (daysOverdue >= 14 && daysOverdue <= 15) ladder_step = 'day14';\n else if (daysOverdue >= 30 && daysOverdue <= 31) ladder_step = 'day30';\n else if (daysOverdue >= 60) ladder_step = 'day60';\n\n if (ladder_step === 'none') continue;\n\n results.push({\n json: {\n invoice_id: bill.id,\n invoice_number: bill.number,\n due_date: bill.due_at,\n days_overdue: daysOverdue,\n balance: balance,\n total: Number(bill.total || 0),\n client_name: bill.client?.name || '',\n client_email: bill.client?.email || '',\n matter_id: bill.matter?.id || '',\n matter_ref: bill.matter?.display_number || '',\n share_url: bill.share_url || '',\n ladder_step,\n is_actionable: true\n }\n });\n}\n\nif (results.length === 0) {\n return [{ json: { is_actionable: false, ladder_step: 'none', reason: 'No invoices due for a reminder today' } }];\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "1800cc00-0003-4000-c000-000000000009",
"name": "If Invoice Is Due Today",
"type": "n8n-nodes-base.if",
"notes": "Routes invoices that hit a ladder step today to the reminder flow. Routes days with no matching invoices to a labelled Skip so you can see a clean record in your execution history.\n\nTrue (right output): invoice is actionable \u2014 continue to partner check or client reminder.\nFalse (bottom output): no reminders due today \u2014 stop.",
"position": [
720,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "c_is_actionable",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.is_actionable }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "1800cc00-0003-4000-c000-00000000000a",
"name": "Skip No Invoices Due Today",
"type": "n8n-nodes-base.noOp",
"notes": "No actionable invoices today. The workflow run is recorded in execution history for audit purposes. No message is sent.",
"position": [
944,
208
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1800cc00-0003-4000-c000-00000000000b",
"name": "If Escalation by Partner Needed",
"type": "n8n-nodes-base.if",
"notes": "Invoices at 60+ days overdue go directly to the managing partner for manual intervention \u2014 a personal call or referral to collections.\n\nThe partner alert bypasses the client-facing compliance guardrail because it is an internal firm email, not a client communication.\n\nTrue (right output): day60 \u2014 alert the partner.\nFalse (bottom output): day1, 7, 14, or 30 \u2014 send the standard client reminder through the compliance guardrail.",
"position": [
944,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "c_day60",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.ladder_step }}",
"rightValue": "day60"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "1800cc00-0003-4000-c000-00000000000c",
"name": "Build Escalation Alert for Partner",
"type": "n8n-nodes-base.code",
"notes": "Builds the partner escalation email for invoices 60+ days overdue. Includes client name, matter reference, balance, and a direct pay link.\n\nThe pay link uses Clio's share_url for the invoice if available (a direct client-specific payment page), falling back to the FIRM_PAY_LINK variable.\n\nThis is an internal email \u2014 no opt-out footer or compliance guardrail needed.",
"position": [
1168,
-160
],
"parameters": {
"jsCode": "const invoice = $input.first().json;\nconst FIRM_NAME = $vars.FIRM_NAME;\nconst PARTNER_EMAIL = $vars.FIRM_PARTNER_EMAIL;\nconst FROM_EMAIL = $vars.FIRM_FROM_EMAIL;\nconst PAY_LINK = invoice.share_url || $vars.FIRM_PAY_LINK || '';\n\nconst subject = `Action required: Invoice ${invoice.invoice_number} is ${invoice.days_overdue} days overdue`;\n\nconst body = `An invoice has passed the 60-day threshold and needs your attention.\n\nClient: ${invoice.client_name}\nMatter: ${invoice.matter_ref}\nInvoice: ${invoice.invoice_number}\nBalance due: $${Number(invoice.balance).toFixed(2)}\nOriginal total: $${Number(invoice.total).toFixed(2)}\nDue date: ${invoice.due_date}\nDays overdue: ${invoice.days_overdue}\n\nClient pay link: ${PAY_LINK}\n\nRecommended next steps:\n1. Call the client directly\n2. Offer a payment plan if appropriate\n3. Refer to collections if no response within 7 days\n\n\u2014 ${FIRM_NAME} Billing System`;\n\nreturn [{\n json: {\n ...invoice,\n partner_email: PARTNER_EMAIL,\n from_email: FROM_EMAIL,\n subject,\n body\n }\n}];"
},
"typeVersion": 2
},
{
"id": "1800cc00-0003-4000-c000-00000000000d",
"name": "Send Alert for 60+ Day Overdue Invoice",
"type": "n8n-nodes-base.emailSend",
"notes": "Sends the 60-day escalation alert to the managing partner. The partner can then call the client directly, offer a payment plan, or refer to collections.\n\nSet FIRM_PARTNER_EMAIL in n8n Variables (project \u2192 Variables tab) to the partner's email address.\n\nCredential: Email account (SMTP) \u2014 same SMTP account used for client reminders.",
"position": [
1392,
-160
],
"parameters": {
"text": "={{ $json.body }}",
"options": {},
"subject": "={{ $json.subject }}",
"toEmail": "={{ $json.partner_email }}",
"emailType": "text",
"fromEmail": "={{ $json.from_email }}"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "1800cc00-0003-4000-c000-00000000000e",
"name": "Build Client Invoice Reminder Email",
"type": "n8n-nodes-base.code",
"notes": "Builds the client-facing reminder email for the appropriate ladder step. Tone escalates from friendly (Day 1) to firm (Day 30).\n\nThe pay link uses Clio's per-invoice share_url when available \u2014 this gives clients a direct, pre-populated payment page. Falls back to FIRM_PAY_LINK if Clio does not return a share_url.\n\nDo not add an opt-out footer here \u2014 the compliance guardrail (next node) appends it automatically. The guardrail also logs the send attempt to your audit sheet.\n\nVariables required: FIRM_NAME, FIRM_EMAIL, FIRM_PAY_LINK.",
"position": [
1168,
176
],
"parameters": {
"jsCode": "const invoice = $input.first().json;\nconst FIRM_NAME = $vars.FIRM_NAME;\nconst FIRM_EMAIL = $vars.FIRM_EMAIL;\nconst PAY_LINK = invoice.share_url || $vars.FIRM_PAY_LINK || '';\n\nconst steps = {\n day1: {\n subject: `Invoice ${invoice.invoice_number} \u2014 payment reminder`,\n body: `Hi ${invoice.client_name},\n\nThis is a friendly reminder that invoice ${invoice.invoice_number} for $${Number(invoice.balance).toFixed(2)} was due on ${invoice.due_date}.\n\nYou can pay securely using the link below:\n${PAY_LINK}\n\nIf you have questions or believe this was sent in error, please reply to this email or contact us at ${FIRM_EMAIL}.\n\nThank you,\n${FIRM_NAME}`\n },\n day7: {\n subject: `Second reminder: Invoice ${invoice.invoice_number} \u2014 7 days past due`,\n body: `Hi ${invoice.client_name},\n\nWe wanted to follow up on invoice ${invoice.invoice_number} for $${Number(invoice.balance).toFixed(2)}, which remains unpaid 7 days past the due date of ${invoice.due_date}.\n\nPay securely here:\n${PAY_LINK}\n\nIf you are experiencing difficulties with payment, please reach out \u2014 we may be able to help.\n\n${FIRM_NAME} | ${FIRM_EMAIL}`\n },\n day14: {\n subject: `Third notice: Invoice ${invoice.invoice_number} \u2014 14 days past due`,\n body: `Hi ${invoice.client_name},\n\nInvoice ${invoice.invoice_number} for $${Number(invoice.balance).toFixed(2)} is now 14 days past due.\n\nPay here:\n${PAY_LINK}\n\nIf there is a dispute or you have not received this invoice previously, contact us immediately at ${FIRM_EMAIL}.\n\n${FIRM_NAME}`\n },\n day30: {\n subject: `Final notice: Invoice ${invoice.invoice_number} \u2014 30 days past due`,\n body: `Hi ${invoice.client_name},\n\nThis is a final notice. Invoice ${invoice.invoice_number} for $${Number(invoice.balance).toFixed(2)} is now 30 days past due. Unresolved balances may be referred for collection.\n\nPay now to avoid further action:\n${PAY_LINK}\n\nTo resolve this immediately, contact us at ${FIRM_EMAIL}.\n\n${FIRM_NAME}`\n }\n};\n\nconst step = steps[invoice.ladder_step] || steps.day1;\n\nreturn [{\n json: {\n ...invoice,\n channel: 'email',\n recipient: invoice.client_email,\n message: step.body,\n subject: step.subject,\n template_id: 'PAC-18-invoice-reminder',\n matter_id: String(invoice.matter_id || '')\n }\n}];"
},
"typeVersion": 2
},
{
"id": "1800cc00-0003-4000-c000-00000000000f",
"name": "Execute Compliance Check Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"notes": "Checks whether this client has opted out of billing email reminders. If not, appends the required unsubscribe footer and logs the send attempt to your audit sheet.\n\nThe guardrail workflow ID is read from the GUARDRAIL_WORKFLOW_ID n8n Variable (project \u2192 Variables tab). Set it to the numeric ID from the Bar-Compliance Guardrail workflow URL: .../workflow/WORKFLOW_ID.\n\nPrerequisite: Bar-Compliance Guardrail must be deployed and active.\n\nReturns: { approved, message_out, recipient, suppression_reason } plus all upstream fields.",
"position": [
1392,
176
],
"parameters": {
"options": {},
"workflowId": {
"__rl": true,
"mode": "expression",
"value": "={{ $vars.GUARDRAIL_WORKFLOW_ID }}"
},
"workflowInputs": {
"value": {
"channel": "={{ $json.channel }}",
"message": "={{ $json.message }}",
"matter_id": "={{ $json.matter_id }}",
"recipient": "={{ $json.recipient }}",
"template_id": "={{ $json.template_id }}"
},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 1.3
},
{
"id": "1800cc00-0003-4000-c000-000000000010",
"name": "If Reminder Sending Approved",
"type": "n8n-nodes-base.if",
"notes": "Checks the guardrail's decision before sending.\n\nTrue (right output): client is reachable and has not opted out \u2014 send the reminder.\nFalse (bottom output): client has opted out \u2014 route to Skip and do not send.",
"position": [
1600,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "c_approved",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.approved }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "1800cc00-0003-4000-c000-000000000011",
"name": "Send Client Invoice Reminder Email",
"type": "n8n-nodes-base.emailSend",
"notes": "Sends the compliance-approved invoice reminder to the client. Uses message_out from the guardrail, which includes the required unsubscribe footer.\n\nThe subject line is pulled from the Build Client Reminder Email node (the guardrail does not modify subjects).\n\nCredential: Email account (SMTP). Set FIRM_FROM_EMAIL in n8n Variables to the sender address clients will see.",
"position": [
1824,
176
],
"parameters": {
"text": "={{ $json.message_out }}",
"options": {},
"subject": "={{ $('Build Client Invoice Reminder Email').item.json.subject }}",
"toEmail": "={{ $json.recipient }}",
"emailType": "text",
"fromEmail": "={{ $vars.FIRM_FROM_EMAIL }}"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "1800cc00-0003-4000-c000-000000000012",
"name": "Skip Reminder Due to Client Opt-Out",
"type": "n8n-nodes-base.noOp",
"notes": "This client has opted out of billing reminders. The suppression is logged by the guardrail workflow. No email is sent.",
"position": [
1824,
336
],
"parameters": {},
"typeVersion": 1
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1",
"executionTimeout": 60,
"saveManualExecutions": true,
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all"
},
"nodeGroups": [],
"staticData": null,
"connections": {
"Every Morning at 8am": {
"main": [
[
{
"node": "Fetch Overdue Invoices from Clio API",
"type": "main",
"index": 0
}
]
]
},
"If Invoice Is Due Today": {
"main": [
[
{
"node": "If Escalation by Partner Needed",
"type": "main",
"index": 0
}
],
[
{
"node": "Skip No Invoices Due Today",
"type": "main",
"index": 0
}
]
]
},
"If Reminder Sending Approved": {
"main": [
[
{
"node": "Send Client Invoice Reminder Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Skip Reminder Due to Client Opt-Out",
"type": "main",
"index": 0
}
]
]
},
"If Escalation by Partner Needed": {
"main": [
[
{
"node": "Build Escalation Alert for Partner",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Client Invoice Reminder Email",
"type": "main",
"index": 0
}
]
]
},
"Execute Compliance Check Workflow": {
"main": [
[
{
"node": "If Reminder Sending Approved",
"type": "main",
"index": 0
}
]
]
},
"Build Escalation Alert for Partner": {
"main": [
[
{
"node": "Send Alert for 60+ Day Overdue Invoice",
"type": "main",
"index": 0
}
]
]
},
"Build Client Invoice Reminder Email": {
"main": [
[
{
"node": "Execute Compliance Check Workflow",
"type": "main",
"index": 0
}
]
]
},
"Fetch Overdue Invoices from Clio API": {
"main": [
[
{
"node": "Normalize and Triage Overdue Invoices",
"type": "main",
"index": 0
}
]
]
},
"Normalize and Triage Overdue Invoices": {
"main": [
[
{
"node": "If Invoice Is Due Today",
"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.
httpHeaderAuthsmtp
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 fetch overdue invoices from Clio, assigns each invoice a reminder step based on days past due, and sends either compliance-checked client reminder emails or a 60+ day internal escalation alert to a managing partner via SMTP email. Runs every day at…
Source: https://n8n.io/workflows/17461/ — 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.
Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.
This workflow is an improvement of this workflow by Greg Brzezinka.
N8N-Self-Updater. Uses ssh, emailSend, httpRequest. Scheduled trigger; 27 nodes.
> An automated n8n workflow originally built for DigitalOcean-based n8n deployments, but fully compatible with any VPS or cloud hosting (e.g., AWS, Google Cloud, Hetzner, Linode, etc.) where n8n ru
What if you could spot a major sales problem—or a winning campaign—the very next morning, instead of weeks later? Imagine receiving a beautiful, data-rich alert directly in your inbox the moment your