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": "Scheduled Automations (Reminders, Invoices, Escalations)",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"id": "node-every5min",
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1328,
64
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 1 * *"
}
]
}
},
"id": "node-monthly",
"name": "1st of Month 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1328,
368
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * *"
}
]
}
},
"id": "node-daily",
"name": "Daily 9AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1328,
672
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "cfg-app-url",
"name": "app_url",
"value": "https://YOUR_APP_DOMAIN",
"type": "string"
},
{
"id": "cfg-secret",
"name": "webhook_secret",
"value": "YOUR_WEBHOOK_SECRET",
"type": "string"
}
]
},
"options": {}
},
"id": "node-config",
"name": "Config",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-1104,
368
]
},
{
"parameters": {
"jsCode": "// Find sessions starting in the next 15\u201320 minutes\nconst now = new Date();\nconst in15min = new Date(now.getTime() + 15 * 60 * 1000);\nconst in20min = new Date(now.getTime() + 20 * 60 * 1000);\n\nconst workflowStaticData = $getWorkflowStaticData('global');\nif (!workflowStaticData.sentReminders) {\n workflowStaticData.sentReminders = {};\n}\n\n// Clean reminders older than 2 hours\nconst twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000;\nfor (const key of Object.keys(workflowStaticData.sentReminders)) {\n if (workflowStaticData.sentReminders[key] < twoHoursAgo) {\n delete workflowStaticData.sentReminders[key];\n }\n}\n\nreturn {\n json: {\n checkTime: now.toISOString(),\n windowStart: in15min.toISOString(),\n windowEnd: in20min.toISOString()\n }\n};"
},
"id": "node-check-window",
"name": "Check Session Window",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-880,
64
]
},
{
"parameters": {
"url": "={{ $('Config').item.json.app_url }}/api/sessions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node-fetch-sessions",
"name": "Fetch Today Sessions",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-656,
64
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const sessions = $input.item.json;\nconst config = $('Config').item.json;\nconst workflowStaticData = $getWorkflowStaticData('global');\n\nconst sessionList = Array.isArray(sessions) ? sessions : (sessions.data || []);\n\nconst now = new Date();\nconst today = now.toISOString().split('T')[0];\nconst currentMinutes = now.getHours() * 60 + now.getMinutes();\n\nconst sessionsToRemind = [];\n\nfor (const session of sessionList) {\n if (session.status !== 'SCHEDULED') continue;\n \n const sessionDate = new Date(session.date).toISOString().split('T')[0];\n if (sessionDate !== today) continue;\n \n const [hours, mins] = (session.startTime || '00:00').split(':').map(Number);\n const sessionMinutes = hours * 60 + mins;\n \n const diff = sessionMinutes - currentMinutes;\n if (diff >= 15 && diff <= 20) {\n const key = `session_${session.id}_${today}`;\n if (!workflowStaticData.sentReminders[key]) {\n sessionsToRemind.push(session);\n workflowStaticData.sentReminders[key] = Date.now();\n }\n }\n}\n\nif (sessionsToRemind.length === 0) {\n return { json: { skip: true, reason: 'No sessions starting in 15\u201320 minutes' } };\n}\n\nreturn sessionsToRemind.map(s => ({ json: { sessionId: s.id, sessionType: s.type, startTime: s.startTime } }));"
},
"id": "node-filter-sessions",
"name": "Filter Upcoming Sessions",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-448,
64
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "check-skip",
"leftValue": "={{ $json.skip }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "node-has-sessions",
"name": "Has Sessions?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-224,
64
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Config').item.json.app_url }}/api/webhooks/n8n",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-n8n-webhook-secret",
"value": "={{ $('Config').item.json.webhook_secret }}"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({ event: 'session.reminder', sessionId: $json.sessionId }) }}",
"options": {}
},
"id": "node-trigger-reminder",
"name": "Trigger Session Reminder",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
0,
0
],
"continueOnFail": true
},
{
"parameters": {
"url": "={{ $('Config').item.json.app_url }}/api/invoices?status=SENT",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node-fetch-invoices",
"name": "Fetch Unpaid Invoices",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-880,
368
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const data = $input.item.json;\nconst invoices = Array.isArray(data) ? data : (data.data || []);\n\nconst unpaid = invoices.filter(inv => \n inv.status === 'SENT' || inv.status === 'OVERDUE'\n);\n\nif (unpaid.length === 0) {\n return { json: { skip: true, count: 0 } };\n}\n\nreturn unpaid.map(inv => ({ json: { invoiceId: inv.id, invoiceNumber: inv.invoiceNumber, total: inv.total, status: inv.status } }));"
},
"id": "node-filter-unpaid",
"name": "Filter Unpaid",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-656,
368
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "check-skip",
"leftValue": "={{ $json.skip }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "node-has-invoices",
"name": "Has Invoices?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-448,
368
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Config').item.json.app_url }}/api/webhooks/n8n",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-n8n-webhook-secret",
"value": "={{ $('Config').item.json.webhook_secret }}"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({ event: 'invoice.overdue', invoiceId: $json.invoiceId }) }}",
"options": {}
},
"id": "node-trigger-overdue",
"name": "Trigger Overdue Alert",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-224,
304
],
"continueOnFail": true
},
{
"parameters": {
"url": "={{ $('Config').item.json.app_url }}/api/invoices?status=OVERDUE",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "node-fetch-overdue",
"name": "Fetch Overdue Invoices",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-880,
672
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const data = $input.item.json;\nconst invoices = Array.isArray(data) ? data : (data.data || []);\n\nconst now = new Date();\nconst sevenDaysMs = 7 * 24 * 60 * 60 * 1000;\n\nconst escalate = invoices.filter(inv => {\n if (inv.status !== 'OVERDUE') return false;\n const dueDate = new Date(inv.dueDate);\n return (now.getTime() - dueDate.getTime()) > sevenDaysMs;\n});\n\nif (escalate.length === 0) {\n return { json: { skip: true, count: 0 } };\n}\n\nreturn escalate.map(inv => ({ json: { invoiceId: inv.id, invoiceNumber: inv.invoiceNumber, total: inv.total, dueDate: inv.dueDate } }));"
},
"id": "node-filter-overdue",
"name": "Filter 7+ Days Overdue",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-656,
672
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "check-skip",
"leftValue": "={{ $json.skip }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "node-has-overdue",
"name": "Has Overdue?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-448,
672
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Config').item.json.app_url }}/api/webhooks/n8n",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-n8n-webhook-secret",
"value": "={{ $('Config').item.json.webhook_secret }}"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({ event: 'invoice.overdue', invoiceId: $json.invoiceId }) }}",
"options": {}
},
"id": "node-trigger-escalation",
"name": "Trigger Escalation",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-224,
608
],
"continueOnFail": true
}
],
"connections": {
"Every 5 Minutes": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"1st of Month 8AM": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Daily 9AM": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Check Session Window",
"type": "main",
"index": 0
},
{
"node": "Fetch Unpaid Invoices",
"type": "main",
"index": 0
},
{
"node": "Fetch Overdue Invoices",
"type": "main",
"index": 0
}
]
]
},
"Check Session Window": {
"main": [
[
{
"node": "Fetch Today Sessions",
"type": "main",
"index": 0
}
]
]
},
"Fetch Today Sessions": {
"main": [
[
{
"node": "Filter Upcoming Sessions",
"type": "main",
"index": 0
}
]
]
},
"Filter Upcoming Sessions": {
"main": [
[
{
"node": "Has Sessions?",
"type": "main",
"index": 0
}
]
]
},
"Has Sessions?": {
"main": [
[
{
"node": "Trigger Session Reminder",
"type": "main",
"index": 0
}
]
]
},
"Fetch Unpaid Invoices": {
"main": [
[
{
"node": "Filter Unpaid",
"type": "main",
"index": 0
}
]
]
},
"Filter Unpaid": {
"main": [
[
{
"node": "Has Invoices?",
"type": "main",
"index": 0
}
]
]
},
"Has Invoices?": {
"main": [
[
{
"node": "Trigger Overdue Alert",
"type": "main",
"index": 0
}
]
]
},
"Fetch Overdue Invoices": {
"main": [
[
{
"node": "Filter 7+ Days Overdue",
"type": "main",
"index": 0
}
]
]
},
"Filter 7+ Days Overdue": {
"main": [
[
{
"node": "Has Overdue?",
"type": "main",
"index": 0
}
]
]
},
"Has Overdue?": {
"main": [
[
{
"node": "Trigger Escalation",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "Automation"
},
{
"name": "Invoicing"
},
{
"name": "Reminders"
}
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
Stay on top of your business operations effortlessly with this workflow that automates reminders for upcoming sessions, generates monthly invoices, and handles escalations based on predefined schedules. It's ideal for freelancers, consultants, or small teams managing client bookings who want to reduce manual follow-ups and ensure timely communications without constant oversight. The key step involves the cron triggers firing at set intervals—like every five minutes for real-time checks or daily at 9am for routine tasks—followed by an httpRequest node to fetch current session data, enabling conditional logic to process only relevant items.
Use this workflow when you need reliable, time-based automations for client management, such as sending personalised reminders via email or updating records in tools like Google Sheets. Avoid it for one-off tasks or highly customised AI-driven decisions, as it relies on straightforward scheduling and httpRequest calls without advanced intelligence. Common variations include adjusting cron intervals for weekly reports or integrating with Slack for instant escalations instead of emails.
About this workflow
Scheduled Automations (Reminders, Invoices, Escalations). Uses httpRequest. Scheduled trigger; 17 nodes.
Source: https://github.com/hasancoded/n8n-workflow-templates/blob/main/scheduled-automations.json — 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.
Amazon Product Price Tracker. Uses googleSheets, splitInBatches, httpRequest, emailSend. Scheduled trigger; 16 nodes.
Code Schedule. Uses itemLists, httpRequest, googleSheets, stickyNote. Scheduled trigger; 13 nodes.
Generate Weekly Energy Consumption Reports with API, Email and Google Drive. Uses httpRequest, convertToFile, emailSend, googleDrive. Scheduled trigger; 12 nodes.
Weekly hiring‑manager snapshot from Breezy HR to email (pipeline, next‑week interviews, stuck). Uses httpRequest, emailSend. Scheduled trigger; 12 nodes.
Schedule Http. Uses httpRequest, noOp, scheduleTrigger, stickyNote. Scheduled trigger; 11 nodes.