{
 "id": "r2unpaidinvoicer",
 "name": "AutomateRecipes \u2013 unpaid-invoice-reminders",
 "nodes": [
  {
   "parameters": {
    "rule": {
     "interval": [
      {
       "field": "days",
       "triggerAtHour": 9
      }
     ]
    }
   },
   "name": "Schedule Trigger",
   "type": "n8n-nodes-base.scheduleTrigger",
   "typeVersion": 1.2,
   "position": [
    0,
    0
   ],
   "id": "5dc0a3e5-acab-437e-b643-15ae2fef529b"
  },
  {
   "parameters": {
    "jsCode": "// EDIT THESE SETTINGS\nreturn [{json:{\n \"ntfyTopic\": \"CHANGE-ME-to-a-long-random-topic\",\n \"cfAccountId\": \"YOUR_CLOUDFLARE_ACCOUNT_ID\",\n \"kvNamespaceId\": \"YOUR_KV_NAMESPACE_ID\",\n \"invoicesKey\": \"unpaid-invoices.json\",\n \"stageDays\": [\n  1,\n  15,\n  30\n ],\n \"yourName\": \"Your Business\"\n}}];"
   },
   "name": "Config",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    240,
    0
   ],
   "id": "4740771e-5adf-4b49-a709-39452eff2617"
  },
  {
   "parameters": {
    "url": "=https://api.cloudflare.com/client/v4/accounts/{{ $('Config').first().json.cfAccountId }}/storage/kv/namespaces/{{ $('Config').first().json.kvNamespaceId }}/values/{{ $('Config').first().json.invoicesKey }}",
    "authentication": "genericCredentialType",
    "genericAuthType": "httpHeaderAuth",
    "options": {
     "response": {
      "response": {
       "responseFormat": "text",
       "outputPropertyName": "raw"
      }
     }
    }
   },
   "name": "Read invoices from KV",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    480,
    0
   ],
   "id": "a2d82dd8-1180-464f-970d-11acf1d66505",
   "credentials": {
    "httpHeaderAuth": {
     "id": "REPLACE_WITH_YOUR_CREDENTIAL",
     "name": "Cloudflare API token"
    }
   }
  },
  {
   "parameters": {
    "jsCode": "const c=$('Config').first().json;let a;try{a=JSON.parse($input.first().json.raw)}catch(e){throw new Error('invoices key missing or not JSON')}\nconst today=new Date();today.setHours(0,0,0,0);const iso=new Date(Date.now()-new Date().getTimezoneOffset()*6e4).toISOString().slice(0,10);const out=[],paid=[];const tone=['Friendly reminder','Second notice','Final notice'];\nfor(const x of a){x.stage=x.stage||0;if(String(x.status).toLowerCase()==='paid'){paid.push(x.number);continue}\n const od=Math.floor((today-new Date(x.dueDate+'T00:00:00'))/864e5);if(od<1||x.stage>=c.stageDays.length)continue;\n let s=x.stage;while(s<c.stageDays.length&&od>=c.stageDays[s])s++;\n if(s>x.stage&&x.lastReminder!==iso){x.stage=s;x.lastReminder=iso;const t=tone[s-1];\n  out.push(`\u2022 ${t} \u2192 ${x.client} <${x.email}> \u00b7 ${x.number} \u00b7 ${x.amount} \u00b7 ${od} days overdue\\n  Draft subject: \"${t}: invoice ${x.number} (${x.amount})\"`)}}\nreturn [{json:{body:JSON.stringify(a,null,1),out,paid}}];"
   },
   "name": "Decide reminders",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    720,
    0
   ],
   "id": "1ee6af43-0e0e-4415-a54c-900bbf057213"
  },
  {
   "parameters": {
    "method": "PUT",
    "url": "=https://api.cloudflare.com/client/v4/accounts/{{ $('Config').first().json.cfAccountId }}/storage/kv/namespaces/{{ $('Config').first().json.kvNamespaceId }}/values/{{ $('Config').first().json.invoicesKey }}",
    "authentication": "genericCredentialType",
    "genericAuthType": "httpHeaderAuth",
    "sendBody": true,
    "contentType": "raw",
    "rawContentType": "text/plain",
    "body": "={{ $json.body }}",
    "options": {}
   },
   "name": "Save updated invoices",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    960,
    0
   ],
   "id": "e76da66e-b9cd-46d5-87b4-d4fd988da70d",
   "credentials": {
    "httpHeaderAuth": {
     "id": "REPLACE_WITH_YOUR_CREDENTIAL",
     "name": "Cloudflare API token"
    }
   }
  },
  {
   "parameters": {
    "jsCode": "if($input.first().json.success!==true)throw new Error('KV write failed');const d=$('Decide reminders').first().json;if(!d.out.length)return [];\nreturn [{json:{title:`Send ${d.out.length} invoice reminder(s)`,tags:'money_with_wings',message:d.out.join('\\n')+(d.paid.length?`\\nPaid, stopped: ${d.paid.join(', ')}`:'')}}];"
   },
   "name": "Build alert",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    1200,
    0
   ],
   "id": "bc4dee6b-f348-424c-9c79-17afd5649ffd"
  },
  {
   "parameters": {
    "method": "POST",
    "url": "=https://ntfy.sh/{{ $('Config').first().json.ntfyTopic }}",
    "sendHeaders": true,
    "headerParameters": {
     "parameters": [
      {
       "name": "Title",
       "value": "={{ $json.title }}"
      },
      {
       "name": "Tags",
       "value": "={{ $json.tags || 'bell' }}"
      }
     ]
    },
    "sendBody": true,
    "contentType": "raw",
    "rawContentType": "text/plain",
    "body": "={{ $json.message }}",
    "options": {}
   },
   "name": "Send ntfy alert",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    1440,
    0
   ],
   "id": "d2f71b7f-6ebd-4be0-94f2-76e41f327c91"
  },
  {
   "parameters": {},
   "name": "Run manually (test)",
   "type": "n8n-nodes-base.manualTrigger",
   "typeVersion": 1,
   "position": [
    0,
    200
   ],
   "id": "600b899a-11e4-45e4-95a2-c23f3703d3c3"
  }
 ],
 "connections": {
  "Schedule Trigger": {
   "main": [
    [
     {
      "node": "Config",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Config": {
   "main": [
    [
     {
      "node": "Read invoices from KV",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Read invoices from KV": {
   "main": [
    [
     {
      "node": "Decide reminders",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Decide reminders": {
   "main": [
    [
     {
      "node": "Save updated invoices",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Save updated invoices": {
   "main": [
    [
     {
      "node": "Build alert",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Build alert": {
   "main": [
    [
     {
      "node": "Send ntfy alert",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Run manually (test)": {
   "main": [
    [
     {
      "node": "Config",
      "type": "main",
      "index": 0
     }
    ]
   ]
  }
 },
 "settings": {
  "executionOrder": "v1"
 },
 "active": false,
 "pinData": {}
}