AutomationFlowsSlack & Telegram › Send Multi-stage SAAS Renewal Alerts From Google Sheets to Slack

Send Multi-stage SAAS Renewal Alerts From Google Sheets to Slack

ByMychel Garzon @mychel-garzon on n8n.io

Stop missing renewal deadlines and overpaying on auto-renewals. This workflow monitors your contract calendar in Google Sheets, sends progressive Slack notifications with manual research checklists, and tracks alert history to prevent duplicate notifications. You manage the…

Event trigger★★★★☆ complexity14 nodesSlackError TriggerGoogle Sheets
Slack & Telegram Trigger: Event Nodes: 14 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15498 — we link there as the canonical source.

This workflow follows the Error Trigger → Google Sheets recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "14M43oMMxpMzlaSF",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "RenewalFlow Intelligence",
  "tags": [],
  "nodes": [
    {
      "id": "5e2e4a16-b291-41dd-846d-754fd7b1fcfc",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 240,
        "content": "### \ud83d\udea8 ERROR MONITORING\nIf credentials expire or Slack rate limits hit, this section fires an immediate administrative \nalert to prevent audit gaps."
      },
      "typeVersion": 1
    },
    {
      "id": "924b3593-c05c-4698-a6f9-12c34a47e26a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 272,
        "height": 496,
        "content": "### 3. STATUS SYNC\nLogs the alert back to the spreadsheet  to ensure no duplicate messages are sent for the same renewal stage."
      },
      "typeVersion": 1
    },
    {
      "id": "40d0d02c-7c46-42dc-86a9-b85543325f9c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 496,
        "content": "### 2. NOTIFICATION LAYER\n\nRoutes the processed data. \nIf an alert is due,  it sends a formatted Slack message with  personalized UTM links and manual research steps."
      },
      "typeVersion": 1
    },
    {
      "id": "2698d004-da32-4d6e-918f-bc065750be37",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 496,
        "content": "### 1. DATA & LOGIC ENGINE\n The workflow wakes up daily, pulls your contract\ndatabase from Google Sheets, and runs the \nproprietary validation & alert logic."
      },
      "typeVersion": 1
    },
    {
      "id": "2777381b-db5a-430c-9843-0781e8ee2958",
      "name": "README: Production Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        -160
      ],
      "parameters": {
        "width": 544,
        "height": 796,
        "content": "# RenewalFlow Intelligence\n\n**HOW IT WORKS:**\n\nRuns daily at 9 AM, sends 4 alerts per contract:\n\u2022 Day 45: Research (\ud83d\udfe1 REMINDER)\n\u2022 Day 30: Quotes (\ud83d\udfe0 ATTENTION)\n\u2022 Day 14: Decision (\ud83d\udd34 URGENT)\n\u2022 Day 7: Action (\ud83d\udea8 FINAL NOTICE)\n\n**CATCH-UP LOGIC:**\nIf workflow misses runs (weekend/outage),\nalerts fire within 5-day window of each stage.\n\n##  SETUP CHECKLIST\n\n**BEFORE FIRST RUN:**\n\n- [ ] **Google Sheets:**\n  \u2022 Get Spreadsheet ID\n  \u2022 Replace YOUR_SPREADSHEET_ID in Load Contracts node\n  \u2022 Set up OAuth2 credentials in n8n\n  \u2022 Share sheet with n8n service account\n\n- [ ] **Sheet Structure (Columns A-F):**\n  A: Contract Name (text)\n  B: Vendor Name (text)\n  C: Renewal Date (YYYY-MM-DD format)\n  D: Current Annual Cost (number, no \u20ac symbol)\n  E: Vendor Pricing URL (https://...)\n  F: Status (Active, Renewed, or Cancelled)\n\n- [ ] **Slack:**\n  \u2022 Create #contract-renewals channel\n  \u2022 Required scopes: chat:write, chat:write.public\n  \u2022 Add bot to #contract-renewals channel\n  \u2022 Configure Slack credentials in all Slack nodes\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "07ab785a-57b7-47e6-9ab7-7c39067c66aa",
      "name": "Slack: Error Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        864,
        448
      ],
      "parameters": {
        "text": "\ud83d\udea8 *WORKFLOW ERROR: Contract Renewal Monitor*\n\n*Error Message:*\n{{ $json.error.message }}\n\n*Failed Node:*\n{{ $json.error.node.name }}\n\n*Timestamp:*\n{{ $now.toISO() }}\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n*Common Fixes:*\n\u2022 Google Sheets credentials expired \u2192 Reconnect OAuth\n\u2022 Slack bot removed from channel \u2192 Re-add bot to #contract-renewals\n\u2022 Workflow deactivated \u2192 Toggle 'Active' back on\n\u2022 Invalid date in sheet \u2192 Check YYYY-MM-DD format\n\n*Check Execution Logs:*\nWorkflow Executions \u2192 Latest Failed Run \u2192 View Details\n\n_Your contract monitoring is currently down. Fix immediately._",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "26eab4af-a1ab-42f2-8f28-1622cda78570",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        640,
        448
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "b2a53ed0-e735-4997-b671-450237dcd3d5",
      "name": "Google Sheets: Update Status",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1760,
        -32
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "={{ $json.status }}, {{ $json._alertStage }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Google Sheets: Load Contracts').params.documentId.value }}"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "ea9a09ae-ccd0-47be-9a2f-bfc783bf6dcf",
      "name": "Slack: Daily Summary",
      "type": "n8n-nodes-base.slack",
      "position": [
        1536,
        160
      ],
      "parameters": {
        "text": "={{ $json.summaryMsg }}",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "5a157762-b92f-44bb-8015-91694809db57",
      "name": "Slack: Send Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        1536,
        -32
      ],
      "parameters": {
        "text": "*{{ $json.urgency }} RENEWAL ALERT: {{ $json.contractName }}*\n\n*Contract Details:*\n\u2022 *Vendor:* {{ $json.vendorName }}\n\u2022 *Renewal Date:* {{ $json.renewalDate }}\n\u2022 *Days Remaining:* {{ $json.daysUntilRenewal }}\n\u2022 *Annual Cost:* \u20ac{{ $json.currentCost.toLocaleString('en-US') }}\n\n*\ud83d\udca1 Potential Savings:* \u20ac{{ $json.potentialSavingsMin.toLocaleString('en-US') }}-\u20ac{{ $json.potentialSavingsMax.toLocaleString('en-US') }}\nResearch shows vendors typically offer 15-25% discounts during renewal negotiations.\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n*\ud83d\udccb ACTION STEPS ({{ $json._alertStage }}):*\n{{ $json._alertMessage }}\n\n1. Visit pricing page: {{ $json.pricingUrl }}\n2. Search Google: \"{{ $json.vendorName }} alternatives 2026\"\n3. Email your account manager for renewal quote\n4. Compare competitor pricing and features\n5. Negotiate based on market rates\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n_Automated by RenewalFlow Intelligence_",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "f8cd0a7d-601f-428a-9087-f0282bae6af5",
      "name": "IF: Is Alert?",
      "type": "n8n-nodes-base.if",
      "position": [
        1312,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json._isAlert }}",
              "value2": true
            }
          ],
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "436c4d37-5745-419d-9cad-79d8ad015f9d",
      "name": "Brain: Validate & Route",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        64
      ],
      "parameters": {
        "jsCode": "const alertStages = [\n  { days: 45, stage: 'alerted_45d', urgency: '\ud83d\udfe1 REMINDER', msg: '\ud83d\udcc5 Research Phase: Look for alternatives.' },\n  { days: 30, stage: 'alerted_30d', urgency: '\ud83d\udfe0 ATTENTION', msg: '\ud83d\udcb0 Quote Phase: Get competitor pricing.' },\n  { days: 14, stage: 'alerted_14d', urgency: '\ud83d\udd34 URGENT', msg: '\ud83c\udfaf Decision Phase: Finalize negotiation.' },\n  { days: 7, stage: 'alerted_7d', urgency: '\ud83d\udea8 FINAL NOTICE', msg: '\u26a0\ufe0f Action Phase: Confirm or Cancel.' }\n];\n\nconst now = new Date();\nconst results = [];\nconst processed = new Set();\n\nfor (const item of items) {\n  const data = item.json;\n  const status = data['Status'] || 'Active';\n  const rawDate = data['Renewal Date'];\n\n  // Skip invalid/completed contracts\n  if (!rawDate || status.includes('Cancelled') || status.includes('Renewed')) continue;\n\n  // Deduplication\n  const contractId = `${data['Contract Name']}-${rawDate}`;\n  if (processed.has(contractId)) continue;\n\n  // VALIDATION\n  const contractName = data['Contract Name']?.trim();\n  if (!contractName) continue;\n  \n  const currentCost = Number(data['Current Annual Cost']);\n  if (isNaN(currentCost) || currentCost < 0) continue;\n  \n  const url = data['Vendor Pricing URL'];\n  if (!url || (!url.startsWith('http://') && !url.startsWith('https://'))) continue;\n\n  // DATE CALCULATION (using standard JS Date)\n  const renewalDate = new Date(rawDate);\n  const diffInMs = renewalDate - now;\n  const daysUntil = Math.ceil(diffInMs / (1000 * 60 * 60 * 24));\n\n  // Skip expired or invalid dates\n  if (isNaN(daysUntil) || daysUntil < 0) continue;\n\n  // SLUG FOR UTM (deterministic fallback using row number)\n  let slug = contractName\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-+|-+$/g, '')\n    .substring(0, 50);\n  \n  // Fallback: use row number for consistency (not timestamp)\n  if (!slug) slug = `contract-row-${data.rowNumber || 'unknown'}`;\n\n  // MULTI-STAGE FILTER WITH CATCH-UP WINDOW\n  for (const alert of alertStages) {\n    // Window: If within alert.days OR slightly past it (catch-up for missed runs)\n    const inWindow = daysUntil <= alert.days && daysUntil > (alert.days - 5);\n    const notYetAlerted = !status.includes(alert.stage);\n\n    if (inWindow && notYetAlerted) {\n      processed.add(contractId);\n      \n      results.push({\n        json: {\n          contractName: contractName,\n          vendorName: data['Vendor Name'] || 'Unknown Vendor',\n          renewalDate: rawDate,\n          currentCost: currentCost,\n          pricingUrl: url,\n          status: status,\n          rowNumber: data.rowNumber,\n          daysUntilRenewal: daysUntil,\n          _alertStage: alert.stage,\n          _alertMessage: alert.msg,\n          urgency: alert.urgency,\n          contractSlug: slug,\n          _isAlert: true,\n          potentialSavingsMin: Math.round(currentCost * 0.15),\n          potentialSavingsMax: Math.round(currentCost * 0.25)\n        }\n      });\n      break;\n    }\n  }\n}\n\n// EMPTY RESULT HANDLER\nif (results.length === 0) {\n  return [{ \n    json: { \n      _isAlert: false, \n      summaryMsg: `\u2705 Daily renewal check complete.\\n\\nReviewed ${items.length} contract${items.length !== 1 ? 's' : ''}.\\nNo renewals in alert windows (45d, 30d, 14d, 7d).` \n    } \n  }];\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "bb64915b-7e7d-496b-a375-55727079a89c",
      "name": "Google Sheets: Load Contracts",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        864,
        64
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "4f5e6780-c8ea-4fe5-84c3-5a6a7d60254f",
      "name": "Schedule: Daily 9AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Runs at 9 AM in your configured timezone",
      "position": [
        640,
        64
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "9d208650-c7a9-4f7f-b471-9943b3f04c14",
  "connections": {
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Slack: Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Is Alert?": {
      "main": [
        [
          {
            "node": "Slack: Send Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Daily Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Send Alert": {
      "main": [
        [
          {
            "node": "Google Sheets: Update Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: Daily 9AM": {
      "main": [
        [
          {
            "node": "Google Sheets: Load Contracts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Brain: Validate & Route": {
      "main": [
        [
          {
            "node": "IF: Is Alert?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets: Load Contracts": {
      "main": [
        [
          {
            "node": "Brain: Validate & Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Stop missing renewal deadlines and overpaying on auto-renewals. This workflow monitors your contract calendar in Google Sheets, sends progressive Slack notifications with manual research checklists, and tracks alert history to prevent duplicate notifications. You manage the…

Source: https://n8n.io/workflows/15498/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

Track all n8n workflow failures with automatic error capture, severity classification, duplicate detection, Slack alerting, performance metrics, and log retention.

Error Trigger, HTTP Request, Slack
Slack & Telegram

This workflow automatically extracts, organizes, and tracks legal contract details from documents uploaded to Google Drive. Using VLM Run’s Execute Agent, it parses key metadata such as contract ID, p

Google Drive Trigger, Google Drive, @Vlm Run/N8N Nodes Vlmrun +3
Slack & Telegram

This workflow is designed for developers, DevOps engineers, and automation specialists who manage multiple n8n workflows and need a reliable way to monitor for failures and receive alerts in real time

Error Trigger, Execute Workflow Trigger, Gmail +4
Slack & Telegram

The Error Notification workflow is designed to instantly notify you whenever any other n8n workflow encounters an error, using popular communication channels like Telegram and Gmail—with optional supp

Error Trigger, Gmail, WhatsApp +3
Slack & Telegram

&gt; ⚠️ Multi-language Slack Error Notifier

Error Trigger, Slack