{ "name": "SMB · Renewal Reminder", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "cronExpression", "expression": "0 8 * * *" } ] } }, "id": "41b1500b-ece3-44ea-b192-363a998c1247", "name": "Daily 08:00", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [ 240, 300 ] }, { "parameters": { "url": "http://smb-crm:8080/api/clients", "options": {} }, "id": "f06a60af-dff1-415d-aca6-fef0367f3553", "name": "Read Clients (DB)", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 460, 300 ], "retryOnFail": true, "maxTries": 4, "waitBetweenTries": 3000 }, { "parameters": { "jsCode": "\nconst rows = $input.first().json.rows || [];\nconst today=new Date();\nconst t0=Date.UTC(today.getUTCFullYear(),today.getUTCMonth(),today.getUTCDate());\nfunction parseDate(v){\n if(v==null||v==='') return null;\n const s=String(v).trim();\n const m=s.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if(m) return Date.UTC(+m[1],+m[2]-1,+m[3]);\n const d=Date.parse(s); return isNaN(d)?null:d;\n}\nconst offsets=[14,7,3,1,0];\nconst due=[];\nfor(const r of rows){\n const status=String(r.status||'').toLowerCase();\n if(status!=='active') continue;\n const rd=parseDate(r.renewal_date);\n if(rd==null) continue;\n const days=Math.round((rd-t0)/86400000);\n if(days<0 || offsets.includes(days)){\n due.push({client_id:r.client_id, business:r.business_name, renewal_date:r.renewal_date,\n days, fee:r.monthly_fee_eur, cycle:r.billing_cycle});\n }\n}\nif(due.length===0) return [];\ndue.sort((a,b)=>a.days-b.days);\nconst line=d=>`• ${d.business||d.client_id} — ${d.days<0?('ÜBERFÄLLIG seit '+(-d.days)+'T'):(d.days===0?'heute':('in '+d.days+'T'))} (${d.renewal_date}${d.fee?(', '+d.fee+'€/'+(d.cycle||'')):''})`;\nconst summary='🔔 Anstehende Verlängerungen ('+due.length+')\\n'+due.map(line).join('\\n');\nreturn [{json:{count:due.length, summary, due}}];\n" }, "id": "9f3e4ca9-0d87-4d1c-9f5e-abdf5f9f77dd", "name": "Find due renewals", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 680, 300 ] }, { "parameters": { "resource": "message", "operation": "sendMessage", "chatId": "5499280257", "text": "={{ $json.summary }}", "additionalFields": { "appendAttribution": false } }, "id": "807748ee-311e-4b18-bae1-ae339068a04f", "name": "Notify Telegram", "type": "n8n-nodes-base.telegram", "typeVersion": 1.2, "position": [ 900, 300 ], "credentials": { "telegramApi": { "id": "d9s4Ec5ocEH8FAxh", "name": "SMB Telegram (leads bot)" } } }, { "parameters": { "jsCode": "const r=$('Find due renewals').first().json;\nconst now=new Date().toISOString().replace('T',' ').slice(0,16);\nreturn [{json:{ts:now, workflow:'renewal-reminder', client_id:'',\n action:'renewals due: '+r.count,\n detail:r.due.map(d=>d.client_id+'('+d.days+'T)').join(', '), result:'ok'}}];" }, "id": "c6a1792f-1866-4b1a-aa74-e1f346d80a97", "name": "Build activity", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1120, 300 ] }, { "parameters": { "method": "POST", "url": "http://smb-crm:8080/api/activity_log", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "X-CRM-Token", "value": "__CRM_TOKEN__" } ] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{ JSON.stringify($json) }}", "options": {} }, "id": "55978962-489b-4740-a242-7470f08087b6", "name": "Append to Activity Log (DB)", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1340, 300 ], "retryOnFail": true, "maxTries": 4, "waitBetweenTries": 3000 } ], "connections": { "Daily 08:00": { "main": [ [ { "node": "Read Clients (DB)", "type": "main", "index": 0 } ] ] }, "Read Clients (DB)": { "main": [ [ { "node": "Find due renewals", "type": "main", "index": 0 } ] ] }, "Find due renewals": { "main": [ [ { "node": "Notify Telegram", "type": "main", "index": 0 } ] ] }, "Notify Telegram": { "main": [ [ { "node": "Build activity", "type": "main", "index": 0 } ] ] }, "Build activity": { "main": [ [ { "node": "Append to Activity Log (DB)", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false } }