{
  "name": "Overdue Invoice Chaser (Helm Systems)",
  "nodes": [
    {
      "parameters": {
        "content": "## Overdue Invoice Chaser\n1. Make a Google Sheet with: Invoice #, Customer, Email, Amount, Due Date, Paid Date, Reminders Sent, Last Reminder.\n2. Pick that sheet in **Read invoices** and **Mark reminded**. Fill in **Settings**, including how customers pay you.\n3. Connect Gmail and the AI model credentials.\n4. Reminders get firmer with age. After 30 days it stops writing and emails YOU that the invoice needs a phone call.\n5. With autoSend off you get Gmail drafts to review first.\nMade by Helm Systems: helmsystems.biz/field-kit",
        "width": 420,
        "height": 290,
        "color": 4
      },
      "id": "d34306fa-79ff-4b9b-ae2b-d2f5fa97f50c",
      "name": "Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -323
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "id": "e1f74eb4-a4af-4ab2-a09a-3f2395f05452",
      "name": "Every weekday at 9am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e795899d-d20d-400d-adfd-3c90af226edd",
              "name": "business",
              "value": "Your Business Name",
              "type": "string"
            },
            {
              "id": "68329872-cb3d-4057-ae16-76b7b5e5234a",
              "name": "owner",
              "value": "Your Name",
              "type": "string"
            },
            {
              "id": "d2f9b0f0-c5b2-489c-a8ae-d5d788168d8a",
              "name": "ownerEmail",
              "value": "you@yourbusiness.com",
              "type": "string"
            },
            {
              "id": "b2111b37-ca56-4c8e-adb4-1d6ca7069e4f",
              "name": "autoSend",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "66903037-2942-4b73-a645-53a7f4da911d",
              "name": "waitDays",
              "value": 5,
              "type": "number"
            },
            {
              "id": "2eecaba5-e815-477d-a33f-91fd3be96865",
              "name": "maxReminders",
              "value": 4,
              "type": "number"
            },
            {
              "id": "9ca37d3b-00bb-4f13-a6d4-4e7dabad672d",
              "name": "payInstructions",
              "value": "Reply to this email and we will send a payment link.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ec11f133-720f-40bb-a8da-047c1a114a0c",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "074e952e-940e-461c-aef7-c600c0ae8d56",
      "name": "Read invoices",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        520,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const asDate = (v) => { if (!v) return null; const d = new Date(v); return isNaN(d) ? null : d; };\nconst startOfDay = (d) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; };\nconst today = startOfDay(new Date());\nconst daysBetween = (a, b) => Math.round((b - a) / 86400000);\nconst s = $('Settings').first().json;\nconst out = [];\nfor (const item of $input.all()) {\n  const r = item.json;\n  if (String(r['Paid Date'] || '').trim()) continue;\n  const due = asDate(r['Due Date']);\n  if (!due) continue;\n  const late = daysBetween(startOfDay(due), today);\n  if (late < 1) continue;\n  const done = Number(r['Reminders Sent'] || 0);\n  if (done >= Number(s.maxReminders)) continue;\n  const last = asDate(r['Last Reminder']);\n  if (last && daysBetween(startOfDay(last), today) < Number(s.waitDays)) continue;\n  const email = String(r['Email'] || '').trim();\n  const level = late >= 30 ? 'call' : late >= 21 ? 'final' : late >= 8 ? 'firm' : 'friendly';\n  out.push({ json: {\n    row_number: r.row_number,\n    action: level === 'call' || !email ? 'call' : 'email',\n    name: String(r['Customer'] || '').split(' ')[0] || 'there',\n    fullName: r['Customer'] || '',\n    email,\n    invoice: r['Invoice #'] || '',\n    amount: r['Amount'] || '',\n    dueDate: r['Due Date'] || '',\n    daysLate: late,\n    level,\n    done,\n    payInstructions: s.payInstructions,\n    business: s.business, owner: s.owner,\n  } });\n}\nreturn out;"
      },
      "id": "c94a68db-d1bf-47e0-aff1-3307118f6581",
      "name": "Pick overdue invoices",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "9069346c-cbbc-4c77-a2a6-251a259edf14",
              "leftValue": "={{ $json.action }}",
              "rightValue": "email",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f18e5ff7-678c-4fbd-a215-b46a929ae49c",
      "name": "Write an email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1040,
        0
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Customer: {{ $json.name }}\nInvoice number: {{ $json.invoice }}\nAmount: {{ $json.amount }}\nDue date: {{ $json.dueDate }}\nDays late: {{ $json.daysLate }}\nLevel: {{ $json.level }}\nHow to pay: {{ $json.payInstructions }}\nSign off as {{ $json.owner }}, {{ $json.business }}.",
        "hasOutputParser": false,
        "messages": {
          "messageValues": [
            {
              "message": "=You write payment reminder emails for {{ $json.business }}.\n\nTone by level:\n- friendly: a few days late. Assume it slipped through.\n- firm: a week or more late. Polite and clear.\n- final: three weeks or more late. Clear that you would like to settle it this week or talk about it.\n\nRules:\n- Under 90 words. Include the invoice number and the amount.\n- Never threaten. Never mention collections, fees, interest or legal action. Never invent a late fee.\nReturn only JSON with the keys \"subject\" and \"body\"."
            }
          ]
        }
      },
      "id": "b11865f3-b94e-4fe2-a023-749f6202dfb0",
      "name": "Write the reminder",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1300,
        -38
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5-mini",
          "mode": "list",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "id": "6ccb1a1a-238e-4fe7-a3dd-cf5a5dba747e",
      "name": "Write the reminder model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        1300,
        228
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const raw = String($json.text ?? '').trim().replace(/^```(?:json)?/i, '').replace(/```$/, '').trim();\nlet out = {};\nlet ok = true;\ntry { out = JSON.parse(raw); } catch (e) { ok = false; }\nconst needs = [\"subject\",\"body\"];\nif (ok && !needs.every((k) => out[k] !== undefined && out[k] !== null)) ok = false;\nconst base = $('Pick overdue invoices').item.json;\n\nif (!ok) {\n  out = { ...out, subject: out.subject || 'Check this one: the AI reply could not be read', body: out.body || ('Original AI output:\\n' + raw) };\n}\nreturn { json: { ...base, ...out, canSend: ok } };"
      },
      "id": "e8355615-55fe-4c08-a2e6-661212ec9a70",
      "name": "Read the reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        -38
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "110307a7-0908-47bf-aa19-345c2861b26d",
              "leftValue": "={{ $json.canSend && $('Settings').first().json.autoSend }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "78aa2463-c557-4346-abd2-b5d96f8640db",
      "name": "Auto-send on?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1820,
        -38
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $json.email }}",
        "subject": "={{ $json.subject }}",
        "emailType": "text",
        "message": "={{ $json.body }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "71a49ada-2e2f-4c88-a29c-3d300d71023b",
      "name": "Send email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2080,
        -123.5
      ]
    },
    {
      "parameters": {
        "resource": "draft",
        "operation": "create",
        "subject": "={{ $json.subject }}",
        "emailType": "text",
        "message": "={{ $json.body }}",
        "options": {
          "sendTo": "={{ $json.email }}"
        }
      },
      "id": "4feb959c-3097-420e-a740-0b82d8c951fa",
      "name": "Save as Gmail draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2080,
        47.5
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $('Settings').first().json.ownerEmail }}",
        "subject": "={{ 'Needs a call: ' + $json.fullName + ' owes $' + $json.amount + ' (' + $json.daysLate + ' days late)' }}",
        "emailType": "text",
        "message": "={{ 'Invoice ' + $json.invoice + ' for $' + $json.amount + ' was due ' + $json.dueDate + '.\\n\\nIt is ' + $json.daysLate + ' days late' + ($json.email ? '' : ' and there is no email on file') + '. This one is better handled with a phone call than another email.' }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "6458deed-5bfe-41fd-ae51-49c1cf211e93",
      "name": "Tell the owner to call",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1300,
        304
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $('Pick overdue invoices').item.json.row_number }}",
            "Reminders Sent": "={{ Number($('Pick overdue invoices').item.json.done) + 1 }}",
            "Last Reminder": "={{ $now.toFormat('yyyy-LL-dd') }}"
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": []
        },
        "options": {}
      },
      "id": "2d4489fe-6224-426c-a765-0844378a7ea3",
      "name": "Mark reminded",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2340,
        114
      ]
    }
  ],
  "connections": {
    "Write the reminder model": {
      "ai_languageModel": [
        [
          {
            "node": "Write the reminder",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Read the reply": {
      "main": [
        [
          {
            "node": "Auto-send on?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Auto-send on?": {
      "main": [
        [
          {
            "node": "Send email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save as Gmail draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every weekday at 9am": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Read invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read invoices": {
      "main": [
        [
          {
            "node": "Pick overdue invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick overdue invoices": {
      "main": [
        [
          {
            "node": "Write an email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write an email?": {
      "main": [
        [
          {
            "node": "Write the reminder",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Tell the owner to call",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the reminder": {
      "main": [
        [
          {
            "node": "Read the reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send email": {
      "main": [
        [
          {
            "node": "Mark reminded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save as Gmail draft": {
      "main": [
        [
          {
            "node": "Mark reminded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tell the owner to call": {
      "main": [
        [
          {
            "node": "Mark reminded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "tags": []
}
