{
  "name": "Send overdue invoice reminders from Pennylane to Slack",
  "nodes": [
    {
      "id": "7709720d-5bd0-419b-9a73-d63f4baf97fe",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        -80
      ],
      "parameters": {
        "width": 528,
        "height": 1088,
        "content": "# Send overdue invoice reminders from Pennylane to Slack\n\n## Who is this for\n\nFinance teams and freelancers using Pennylane who want daily alerts for unpaid invoices that need follow-up.\n\n## What it does\n\nThis workflow runs daily at 9 AM, fetches all customer invoices from Pennylane, and identifies unpaid invoices past their deadline by 7 or more days. If overdue invoices are found, it sends a detailed Slack reminder with invoice number, amount, days overdue, and a direct link to the PDF. If no invoices are overdue, the workflow ends silently.\n\n## How to set up\n\n1. Import this workflow into n8n\n2. Create a Header Auth credential: name `Authorization`, value `Bearer <YOUR_PENNYLANE_TOKEN>`\n3. Select your Slack channel in the SL Send Notification node\n4. Adjust the schedule time in the Schedule Trigger node if needed\n5. Change THRESHOLD_DAYS in the Code Filter Overdue node to adjust sensitivity (default: 7 days)\n\n## Requirements\n\n- Pennylane account with API access (Essentiel plan or higher)\n- Pennylane API token with scope: customer_invoices:all\n- (Optional) Slack workspace\n\nPart of a 3-workflow billing suite: https://github.com/Gauthier-Huguenin/n8n-pennylane-auto-invoicing\n\n---\n\n### Flow\n\n`Schedule Trigger`\n  \u2192 `PL Fetch Invoices`\n  \u2192 `Code Filter Overdue`\n  \u2192 `IF Has Overdue`\n    \u2192 \u2705 `Code Build Reminder` \u2192 `SL Send Notification`\n    \u2192 \u274c `Set Done`\n\n---\n\n### Node refs for $()\n\n- Schedule Trigger\n- PL Fetch Invoices\n- Code Filter Overdue\n- IF Has Overdue\n- Code Build Reminder\n- SL Send Notification\n- Set Done"
      },
      "typeVersion": 1
    },
    {
      "id": "7c7a9506-5ec1-460d-a014-7d6db37c3a35",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        80,
        224
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "bf00125c-bc60-4c7e-8649-bf3d9faa2f42",
      "name": "PL Fetch Invoices",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        320,
        224
      ],
      "parameters": {
        "url": "https://app.pennylane.com/api/external/v2/customer_invoices",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "d25af660-3af7-4869-b149-7b9909ffbc0f",
      "name": "Code Filter Overdue",
      "type": "n8n-nodes-base.code",
      "position": [
        560,
        224
      ],
      "parameters": {
        "jsCode": "// Refs: 'PL Fetch Invoices'\nconst invoices = $input.first().json.items;\nconst today = new Date().toISOString().split('T')[0];\nconst THRESHOLD_DAYS = 7;\n\nconst overdue = [];\n\nfor (const inv of invoices) {\n  if (inv.draft || inv.paid) continue;\n  if (inv.deadline >= today) continue;\n\n  const deadlineDate = new Date(inv.deadline);\n  const todayDate = new Date(today);\n  const daysOverdue = Math.floor((todayDate - deadlineDate) / (1000 * 60 * 60 * 24));\n\n  if (daysOverdue >= THRESHOLD_DAYS) {\n    overdue.push({\n      id: inv.id,\n      invoice_number: inv.invoice_number,\n      label: inv.label,\n      currency_amount: inv.currency_amount,\n      currency: inv.currency,\n      deadline: inv.deadline,\n      days_overdue: daysOverdue,\n      customer_id: inv.customer.id,\n      public_file_url: inv.public_file_url\n    });\n  }\n}\n\nreturn [{\n  json: {\n    overdue_count: overdue.length,\n    overdue,\n    has_overdue: overdue.length > 0\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "28c82e3c-1d77-4137-8396-3d79ef392cd8",
      "name": "IF Has Overdue",
      "type": "n8n-nodes-base.if",
      "position": [
        736,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1a032dfe-0694-4d96-8c75-c891a86339b2",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.has_overdue }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "00fca357-c089-4a78-a7eb-9bbf9a1f76b9",
      "name": "Set Done",
      "type": "n8n-nodes-base.set",
      "position": [
        1552,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "7c77345e-ab79-4816-aa14-d62c9e9b61a9",
              "name": "status",
              "type": "string",
              "value": "no overdue invoices"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "73f0f390-b353-487b-ad7e-98a87ca7d4ca",
      "name": "Code Build Reminder",
      "type": "n8n-nodes-base.code",
      "position": [
        976,
        144
      ],
      "parameters": {
        "jsCode": "// Refs: 'Code Filter Overdue'\nconst data = $input.first().json;\nconst lines = ['\ud83d\udea8 Overdue Invoice Reminder', ''];\n\nlines.push(`${data.overdue_count} invoice(s) require attention:`);\nlines.push('');\n\nfor (const inv of data.overdue) {\n  lines.push(`\u2022 ${inv.invoice_number} | ${inv.currency_amount} ${inv.currency}`);\n  lines.push(`  Due: ${inv.deadline} (${inv.days_overdue} days overdue)`);\n  lines.push(`  ${inv.public_file_url}`);\n  lines.push('');\n}\n\nreturn [{\n  json: {\n    message: lines.join('\\n'),\n    overdue_count: data.overdue_count\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "04edc672-c812-4b77-8643-d68f58bbc2c1",
      "name": "SL Send Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        1168,
        144
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "4ca9d854-033b-49bf-bd18-7fac67c6e674",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        -80
      ],
      "parameters": {
        "color": 5,
        "height": 480,
        "content": "## 1. Trigger\n\nRuns once daily at 9:00 AM.\nChecks for overdue invoices that need\nfollow-up.\n\nAdjust schedule in the Schedule Trigger\nnode to fit your needs."
      },
      "typeVersion": 1
    },
    {
      "id": "000b18e0-894f-4d7a-aeaa-efd9b9106545",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        -80
      ],
      "parameters": {
        "color": 3,
        "height": 480,
        "content": "## 2. Fetch invoices\n\nRetrieves all customer invoices from Pennylane.\n\nEndpoint: GET /customer_invoices\n\nSame approach as WF2: no server-side\nstatus filtering available, all logic\nis handled in the Code node."
      },
      "typeVersion": 1
    },
    {
      "id": "ba078fad-ae3c-4ebb-b045-384adf189fa5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        -80
      ],
      "parameters": {
        "color": 3,
        "width": 432,
        "height": 480,
        "content": "## 3. Filter overdue\n\nIdentifies unpaid invoices past their\ndeadline by at least 7 days (THRESHOLD_DAYS).\n\nAdjust the threshold in Code Filter Overdue\nto change the sensitivity.\n\nSkips draft and paid invoices.\nCalculates exact days overdue for each."
      },
      "typeVersion": 1
    },
    {
      "id": "c4d123ec-9c7d-41de-b22a-e93380e7e64a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 480,
        "height": 480,
        "content": "## 4. Notify\n\nBuilds a detailed reminder with:\n- Invoice number and amount\n- Due date and days overdue\n- Direct link to the invoice PDF\n\nSends to Slack. Replace with your preferred\nnotification channel (Telegram, Email, etc.)."
      },
      "typeVersion": 1
    },
    {
      "id": "a8a9b804-edbb-4123-beec-1f937e50eee6",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1408,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 368,
        "height": 480,
        "content": "## 5. Done\n\nNo overdue invoices detected.\nWorkflow ends silently.\n\nThis node ensures a clean execution log\nin n8n. No notification is sent."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "timeSavedMode": "fixed",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "connections": {
    "IF Has Overdue": {
      "main": [
        [
          {
            "node": "Code Build Reminder",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "PL Fetch Invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PL Fetch Invoices": {
      "main": [
        [
          {
            "node": "Code Filter Overdue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Build Reminder": {
      "main": [
        [
          {
            "node": "SL Send Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Filter Overdue": {
      "main": [
        [
          {
            "node": "IF Has Overdue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}