AutomationFlowsGeneral › Scheduled Reminders, Invoices & Escalations

Scheduled Reminders, Invoices & Escalations

Original n8n title: Scheduled Automations (reminders, Invoices, Escalations)

Scheduled Automations (Reminders, Invoices, Escalations). Uses httpRequest. Scheduled trigger; 17 nodes.

Cron / scheduled trigger★★★★☆ complexity17 nodesHTTP Request
General Trigger: Cron / scheduled Nodes: 17 Complexity: ★★★★☆ Added:

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
{
  "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"
    }
  ]
}
Pro

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 →

More General workflows → · Browse all categories →

Related workflows

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

General

Amazon Product Price Tracker. Uses googleSheets, splitInBatches, httpRequest, emailSend. Scheduled trigger; 16 nodes.

Google Sheets, HTTP Request, Email Send
General

Code Schedule. Uses itemLists, httpRequest, googleSheets, stickyNote. Scheduled trigger; 13 nodes.

Item Lists, HTTP Request, Google Sheets
General

Generate Weekly Energy Consumption Reports with API, Email and Google Drive. Uses httpRequest, convertToFile, emailSend, googleDrive. Scheduled trigger; 12 nodes.

HTTP Request, Email Send, Google Drive
General

Weekly hiring‑manager snapshot from Breezy HR to email (pipeline, next‑week interviews, stuck). Uses httpRequest, emailSend. Scheduled trigger; 12 nodes.

HTTP Request, Email Send
General

Schedule Http. Uses httpRequest, noOp, scheduleTrigger, stickyNote. Scheduled trigger; 11 nodes.

HTTP Request, Google Sheets