{
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Contract Renewal Watchdog Daily Slack Alerts",
  "tags": [],
  "nodes": [
    {
      "id": "bbbe82aa-9e9b-4ff8-a21c-119132a123df",
      "name": "Needs Alert Today?",
      "type": "n8n-nodes-base.if",
      "position": [
        880,
        624
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d325af5d-5241-4baf-8b02-406b725cfe11",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json['Cancellation Deadline'] }}",
              "rightValue": ""
            },
            {
              "id": "5224a5b3-393c-4a93-9927-daa56591d3d6",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.days_left }}",
              "rightValue": 30
            },
            {
              "id": "a6eaad68-55b9-4807-bbc6-66335b737d09",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.days_left }}",
              "rightValue": 0
            },
            {
              "id": "26b6a84b-dcaf-43f4-a0df-7a70abd65baa",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ ({red: 3, yellow: 2, green: 1, none: 0}[$json.tier] || 0) > ({red: 3, yellow: 2, green: 1, '': 0}[($json['Last Alert Tier'] || '').toLowerCase()] || 0) }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ec84588c-2aa7-4740-a11d-9dcc27f8d1f1",
      "name": "Route by Tier",
      "type": "n8n-nodes-base.switch",
      "position": [
        1168,
        592
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Red",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "1a6d0b2f-a334-4f5f-95e3-f8f55792da94",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "red"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Yellow",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "73e1171c-33bc-4b62-b954-abba75505bdf",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "yellow"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Green",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "eb626e3e-d730-4d91-8c89-ab2f0cb11774",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "green"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "536797a1-a49a-4ce6-ac5e-9cc4af7e5f6e",
      "name": "Slack: Urgent (Red)",
      "type": "n8n-nodes-base.slack",
      "position": [
        1440,
        432
      ],
      "parameters": {
        "text": "=\ud83d\udd34 *URGENT: {{ $json.vendor_label }} cancellation deadline in {{ $json.days_left }} day{{ $json.days_left === 1 ? '' : 's' }}*\n\n- *Contract type:* {{ $json['Contract Class'] }}\n- *Cancellation deadline:* {{ $json['Cancellation Deadline'] }}\n- *End date:* {{ $json['End Date'] }}\n- *Value:* {{ $json['Contract Value'] }}\n- *Notice period:* {{ $json['Notice Period Days'] }} days\n{{ $json.auto_renew_bool ? '\\n\u26a0\ufe0f *This contract will AUTO-RENEW* unless notice of cancellation is given before the deadline.' : '\\n\ud83d\udcc5 This contract will expire at the end of its term (no auto-renewal).' }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SLACK_CHANNEL_ID"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    },
    {
      "id": "0c75e393-c495-40c7-b765-5b81c1cc390d",
      "name": "Slack: Action (Yellow)",
      "type": "n8n-nodes-base.slack",
      "position": [
        1440,
        608
      ],
      "parameters": {
        "text": "=\ud83d\udfe1 *Action needed: {{ $json.vendor_label }} cancellation deadline in {{ $json.days_left }} day{{ $json.days_left === 1 ? '' : 's' }}*",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SLACK_CHANNEL_ID"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    },
    {
      "id": "f2b74ea1-8bd9-49e8-a6d8-a5905a06b872",
      "name": "Slack: Heads-up (Green)",
      "type": "n8n-nodes-base.slack",
      "position": [
        1440,
        784
      ],
      "parameters": {
        "text": "=\ud83d\udfe2 *Heads-up: {{ $json.vendor_label }} cancellation deadline in {{ $json.days_left }} day{{ $json.days_left === 1 ? '' : 's' }}*",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SLACK_CHANNEL_ID"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    },
    {
      "id": "09be045a-1ec3-411b-947d-561bb459ea0a",
      "name": "Trigger: Daily at 9am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -256,
        624
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "b3a8b02e-6c40-4c68-ad09-f50f033c0a0c",
      "name": "Read: Services Tab",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        16,
        624
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Services"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "e44852b3-c060-436c-8f8c-a6efe50f6c56",
      "name": "Read: Leases Tab",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        16,
        448
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Leases"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "c5dbdf98-9ff8-43cf-a7b2-7cc30e7c74eb",
      "name": "Read: SaaS Tab",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        16,
        272
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "SaaS"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "dc6ba403-ffdb-46cf-8e17-359888dc9b0f",
      "name": "Read: Insurance Tab",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        16,
        800
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Insurance"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "b9e469c1-e150-4a45-a3d4-188147301f49",
      "name": "Read: Other Tab",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        16,
        976
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Other"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "cef80888-a96c-403b-9bcb-47163e1f5345",
      "name": "Merge: All Contracts",
      "type": "n8n-nodes-base.merge",
      "position": [
        288,
        576
      ],
      "parameters": {
        "numberInputs": 5
      },
      "typeVersion": 3.2
    },
    {
      "id": "a5f75835-aede-4788-85ff-ac4189a65a0e",
      "name": "Calculate Tier & Days Left",
      "type": "n8n-nodes-base.set",
      "position": [
        576,
        624
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "76ce752f-acde-48ac-a083-d65487eb6c2b",
              "name": "days_left",
              "type": "number",
              "value": "={{ Math.round((DateTime.fromISO($json['Cancellation Deadline']).startOf('day').toMillis() - DateTime.now().startOf('day').toMillis()) / 86400000) }}"
            },
            {
              "id": "7bce067b-37b0-4868-8326-7f0749754277",
              "name": "tier",
              "type": "string",
              "value": "={{ (() => { const d = Math.round((DateTime.fromISO($json['Cancellation Deadline']).startOf('day').toMillis() - DateTime.now().startOf('day').toMillis()) / 86400000); return d <= 7 ? 'red' : d <= 14 ? 'yellow' : d <= 30 ? 'green' : 'none'; })() }}"
            },
            {
              "id": "d2e49f8d-987a-4e36-b821-dffbb99915fb",
              "name": "vendor_label",
              "type": "string",
              "value": "={{ $json['Provider Name'] || $json['Client Name'] || 'Unknown contract' }}"
            },
            {
              "id": "1729e5d4-aa22-42cc-88ad-94095a162f72",
              "name": "auto_renew_bool",
              "type": "boolean",
              "value": "={{ String($json['Auto Renew']).toLowerCase() === 'true' }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "80e01f6e-0927-4bb2-b202-6bcdc8cf2d5b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 352,
        "content": "### \u23f0 Daily Trigger\nFires every morning at 9am. Change the hour in the node if your team prefers a different check-in time."
      },
      "typeVersion": 1
    },
    {
      "id": "a247f9bd-3cb4-4b9d-acd1-0cdaa31c9100",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 1040,
        "content": "### \ud83d\udce5 Read All 5 Tabs\nOne Google Sheets read per contract class. Each returns every row from its tab. They converge in the merge node next."
      },
      "typeVersion": 1
    },
    {
      "id": "c198349a-55ce-4036-a516-ab633b9fd5e5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 400,
        "content": "### \ud83d\udd17 Merge: All Contracts\nAppend mode combines rows from all 5 tabs into one stream so the filtering logic lives in a single place downstream."
      },
      "typeVersion": 1
    },
    {
      "id": "40da47f5-4b6d-41ec-b720-088cf2656e9b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 496,
        "content": "### \ud83e\uddee Calculate Tier & Days Left\nFor every contract row, derives:\n- `days_left` \u2013 days between today and `Cancellation Deadline`\n- `tier` \u2013 \ud83d\udd34 red (\u22647), \ud83d\udfe1 yellow (\u226414), \ud83d\udfe2 green (\u226430), `none` otherwise\n- `vendor_label` \u2013 Provider or Client name for the Slack message\n- `auto_renew_bool` \u2013 normalizes the string `\"true\"/\"false\"` from the sheet into a real boolean"
      },
      "typeVersion": 1
    },
    {
      "id": "ff1b0154-ed64-4267-8b10-b9107aeaeab4",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 496,
        "content": "### \u2753 Needs Alert Today?\nPasses a contract through only if:\n- `Cancellation Deadline` exists\n- `days_left` is between 0 and 30\n- the new tier is more urgent than the `Last Alert Tier` already stored on the row\n\nThis is the escalation gate \u2013 contracts that already got their \ud83d\udfe2 ping won't ping again until they cross into \ud83d\udfe1."
      },
      "typeVersion": 1
    },
    {
      "id": "fe072824-4672-4894-9c70-5a8708f2b340",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 400,
        "content": "### \ud83d\udd00 Route by Tier\nExpression-based Switch on `tier`. Each output feeds a tier-specific Slack message so the urgency of the ping matches the urgency of the deadline."
      },
      "typeVersion": 1
    },
    {
      "id": "f812f850-0459-41df-b611-8cf44882c087",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1360,
        208
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 752,
        "content": "### \ud83d\udcac Slack Alerts\nOne message per contract that crossed a new tier today. Auto-renewing contracts get a stronger warning (\"this WILL auto-renew unless cancelled\") than contracts that just expire \u2013 that's the distinction that actually matters financially."
      },
      "typeVersion": 1
    },
    {
      "id": "5efea265-9448-44e9-a83c-7aac0b55f54f",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1072,
        16
      ],
      "parameters": {
        "width": 720,
        "height": 1264,
        "content": "## \ud83d\udd14 Contract Renewal Watchdog \u2013 Daily Slack Alerts\n\n**What this workflow does**\nRuns every morning at 9am. Reads every contract from the 5 tabs in your Contracts sheet, checks how many days are left until each `Cancellation Deadline`, and pings Slack with tiered alerts so no renewal slips through.\n\n### Alert tiers\n\n**\ud83d\udd34 Urgent** \u2013 \u2264 7 days until deadline\nAct now\n\n**\ud83d\udfe1 Action needed** \u2013 \u2264 14 days until deadline\nPlan this week\n\n**\ud83d\udfe2 Heads-up** \u2013 \u2264 30 days until deadline\nOn your radar\n\n**No alert** \u2013 more than 30 days out, or already past due\n\nAuto-renewing contracts get a stronger warning than contracts that simply expire \u2013 because that's the case where money is actually at stake.\n\n### Setup guide\n\n**1. Add two columns to every tab in your Contracts sheet**\nOpen your sheet and add these two columns to all 5 tabs (SaaS, Leases, Services, Insurance, Other):\n- `Last Alert Tier`\n- `Last Alert Date`\n\nLeave both empty for existing rows. These drive the de-duplication \u2013 without them you'll get the same ping every day for weeks.\n\n**2. Paste your Sheet ID**\nReplace `YOUR_GOOGLE_SHEET_ID` in all 5 Read nodes. It's the same Sheet you used in Part 1.\n\n**3. Set your Slack channel**\nReplace `YOUR_SLACK_CHANNEL_ID` in all 3 Slack nodes with the channel (or DM) where alerts should land. `#contracts` or `#finance-alerts` work well; DM to the contract owner is fine too.\n\n**4. Connect credentials**\n- Google Sheets \u2192 OAuth\n- Slack \u2192 OAuth\n\n**5. Activate and test**\nBefore turning it on, temporarily set one row's `Cancellation Deadline` to ~10 days from today and clear its `Last Alert Tier`. Click **Execute Workflow** and confirm a \ud83d\udfe1 Slack message lands. Revert the row, then activate.\n\n### Known limitations (v1)\n\n**No automatic writeback.** Today the workflow alerts but doesn't mark the row as \"alerted\". To stop daily re-pings, either:\n- Fill `Last Alert Tier` manually when the alert fires (\ud83d\udfe2 / \ud83d\udfe1 / \ud83d\udd34), or\n- Add a Google Sheets Update node per tab (Switch by `Contract Class`, match on Start Date + Provider Name as a composite key)\n\n**Works on ISO dates only.** The `days_left` calculation assumes `Cancellation Deadline` is stored as `YYYY-MM-DD`. Part 1 produces this format correctly, so this is only a concern if you're adding rows manually."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "connections": {
    "Route by Tier": {
      "main": [
        [
          {
            "node": "Slack: Urgent (Red)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Action (Yellow)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Heads-up (Green)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read: SaaS Tab": {
      "main": [
        [
          {
            "node": "Merge: All Contracts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read: Other Tab": {
      "main": [
        [
          {
            "node": "Merge: All Contracts",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Read: Leases Tab": {
      "main": [
        [
          {
            "node": "Merge: All Contracts",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Needs Alert Today?": {
      "main": [
        [
          {
            "node": "Route by Tier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read: Services Tab": {
      "main": [
        [
          {
            "node": "Merge: All Contracts",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Read: Insurance Tab": {
      "main": [
        [
          {
            "node": "Merge: All Contracts",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Merge: All Contracts": {
      "main": [
        [
          {
            "node": "Calculate Tier & Days Left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger: Daily at 9am": {
      "main": [
        [
          {
            "node": "Read: SaaS Tab",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read: Leases Tab",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read: Services Tab",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read: Insurance Tab",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read: Other Tab",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Tier & Days Left": {
      "main": [
        [
          {
            "node": "Needs Alert Today?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}