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