{
  "name": "WF-03 · Renewal Rescue",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [{ "field": "cronExpression", "expression": "0 9 * * *" }]
        }
      },
      "id": "n1",
      "name": "Daily at 9 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [200, 300],
      "notes": "Runs every morning and checks all policies for upcoming renewals."
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://api.hubapi.com/crm/v3/objects/deals?properties=dealname,closedate,hs_deal_stage_probability,amount,associated_contact&limit=100",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Authorization", "value": "Bearer {{ HUBSPOT_PRIVATE_APP_TOKEN }}" }
          ]
        },
        "options": {},
        "notes": "Replace with your insurance CRM endpoint (AgencyZoom, AMS360, HawkSoft, etc.) or a filtered HubSpot deal search."
      },
      "id": "n2",
      "name": "Fetch Policies from CRM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [420, 300]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nconst results = $input.item.json.results || [];\n\nconst windows = { d90: [], d60: [], d30: [], winback: [] };\n\nfor (const deal of results) {\n  const renewDate = new Date(deal.properties.closedate);\n  const daysOut = Math.round((renewDate - today) / (1000 * 60 * 60 * 24));\n\n  const item = {\n    dealId:    deal.id,\n    dealName:  deal.properties.dealname,\n    renewDate: deal.properties.closedate,\n    amount:    deal.properties.amount,\n    daysOut\n  };\n\n  if (daysOut >= 88 && daysOut <= 92)  windows.d90.push(item);\n  if (daysOut >= 58 && daysOut <= 62)  windows.d60.push(item);\n  if (daysOut >= 28 && daysOut <= 32)  windows.d30.push(item);\n  if (daysOut >= 13 && daysOut <= 17)  windows.winback.push(item);\n}\n\nreturn [{ json: windows }];",
        "options": {}
      },
      "id": "n3",
      "name": "Filter by Renewal Window",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [640, 300]
    },
    {
      "parameters": {
        "fieldToSplitOut": "d90",
        "options": {}
      },
      "id": "n4a",
      "name": "Split 90-Day Policies",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [860, 120]
    },
    {
      "parameters": {
        "fieldToSplitOut": "d60",
        "options": {}
      },
      "id": "n4b",
      "name": "Split 60-Day Policies",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [860, 280]
    },
    {
      "parameters": {
        "fieldToSplitOut": "d30",
        "options": {}
      },
      "id": "n4c",
      "name": "Split 30-Day Policies",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [860, 440]
    },
    {
      "parameters": {
        "fieldToSplitOut": "winback",
        "options": {}
      },
      "id": "n4d",
      "name": "Split Win-Back Policies",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [860, 600]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.contactEmail || '{{ AGENT_EMAIL }}' }}",
        "subject": "=Your {{ $json.dealName }} renews in 90 days — here's what we found",
        "emailType": "html",
        "message": "=<p>Hi {{ $json.contactFirstName || 'there' }},</p><p>Your <strong>{{ $json.dealName }}</strong> policy renews on {{ $json.renewDate }}. We've already re-shopped your rates against the market — and we'll be in touch shortly with what we found.</p><p>If anything has changed on your end (new vehicles, address changes, coverage questions), now's a great time to let us know.</p><p>We'll follow up in 30 days with a full comparison. In the meantime, feel free to reach out anytime.</p><p>{{ YOUR_NAME }}<br>{{ YOUR_AGENCY }}</p>",
        "options": {}
      },
      "id": "n5",
      "name": "Day 90 Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [1080, 120],
      "credentials": {
        "gmailOAuth2": { "id": "gmail-creds", "name": "Gmail" }
      }
    },
    {
      "parameters": {
        "operation": "send",
        "from": "{{ YOUR_TWILIO_PHONE_NUMBER }}",
        "to": "={{ $json.contactPhone }}",
        "message": "=Hi {{ $json.contactFirstName || 'there' }}, this is {{ YOUR_NAME }} from {{ YOUR_AGENCY }}. Your {{ $json.dealName }} policy renews in about 60 days. Anything you'd like us to look at before then? Just reply here."
      },
      "id": "n6",
      "name": "Day 60 Text",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [1080, 280],
      "credentials": {
        "twilioApi": { "id": "twilio-creds", "name": "Twilio" }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/tasks",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Authorization", "value": "Bearer {{ HUBSPOT_PRIVATE_APP_TOKEN }}" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"properties\": {\n    \"hs_task_subject\": \"Renewal call — {{ $json.dealName }} (30 days out)\",\n    \"hs_task_body\": \"Policy renews {{ $json.renewDate }}. Amount: ${{ $json.amount }}. Emailed at 90 days, texted at 60. Ready for personal call.\",\n    \"hs_task_status\": \"NOT_STARTED\",\n    \"hs_task_priority\": \"HIGH\",\n    \"hs_timestamp\": \"{{ $now }}\"\n  }\n}",
        "options": {}
      },
      "id": "n7",
      "name": "Create Day 30 Call Task",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1080, 440]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.contactEmail }}",
        "subject": "=A quick note about your {{ $json.dealName }} renewal",
        "emailType": "html",
        "message": "=<p>Hi {{ $json.contactFirstName || 'there' }},</p><p>I wanted to reach out one more time about your <strong>{{ $json.dealName }}</strong> policy — it renews in about 2 weeks.</p><p>We've been holding a competitive rate for you, and I'd hate for you to miss it. If there's anything that made the timing feel off, I'm happy to talk through it.</p><p>No pressure at all — just want to make sure you have everything you need to make the best call.</p><p>{{ YOUR_NAME }}<br>{{ YOUR_AGENCY }}<br>{{ YOUR_PHONE_NUMBER }}</p>",
        "options": {}
      },
      "id": "n8",
      "name": "Win-Back Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [1080, 600],
      "credentials": {
        "gmailOAuth2": { "id": "gmail-creds", "name": "Gmail" }
      }
    }
  ],
  "connections": {
    "Daily at 9 AM":          { "main": [[{ "node": "Fetch Policies from CRM",  "type": "main", "index": 0 }]] },
    "Fetch Policies from CRM":{ "main": [[{ "node": "Filter by Renewal Window", "type": "main", "index": 0 }]] },
    "Filter by Renewal Window": {
      "main": [[
        { "node": "Split 90-Day Policies",    "type": "main", "index": 0 },
        { "node": "Split 60-Day Policies",    "type": "main", "index": 0 },
        { "node": "Split 30-Day Policies",    "type": "main", "index": 0 },
        { "node": "Split Win-Back Policies",  "type": "main", "index": 0 }
      ]]
    },
    "Split 90-Day Policies":  { "main": [[{ "node": "Day 90 Email",           "type": "main", "index": 0 }]] },
    "Split 60-Day Policies":  { "main": [[{ "node": "Day 60 Text",            "type": "main", "index": 0 }]] },
    "Split 30-Day Policies":  { "main": [[{ "node": "Create Day 30 Call Task","type": "main", "index": 0 }]] },
    "Split Win-Back Policies":{ "main": [[{ "node": "Win-Back Email",         "type": "main", "index": 0 }]] }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "id": "wf-03-renewal-rescue",
  "versionId": "1.0.0"
}
