AutomationFlowsEmail & Gmail › Score Churn Risk and Send Retention Emails with Google Sheets and Gmail

Score Churn Risk and Send Retention Emails with Google Sheets and Gmail

ByStefan Joulien @stefanjoulien on n8n.io

This template is for gyms, fitness studios, and membership-based businesses that want to automatically detect at-risk members and act before they cancel. Ideal for operations teams and retention managers who want to replace manual follow-ups with a systematic, automated process.

Event trigger★★★★☆ complexity18 nodesGoogle SheetsGmail
Email & Gmail Trigger: Event Nodes: 18 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → 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": "hVnwL7kUfbNdWNZR",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Membership Retention & Churn Prevention System",
  "tags": [
    {
      "id": "ODwcqtH2rGDMvTeh",
      "name": "template",
      "createdAt": "2026-02-27T09:48:11.280Z",
      "updatedAt": "2026-02-27T09:48:11.280Z"
    }
  ],
  "nodes": [
    {
      "id": "b5bcafdb-b1c7-4201-ab6c-2171119c2406",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "7bd64aea-da7f-4361-b43c-ac425488e199",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -768,
        -1248
      ],
      "parameters": {
        "width": 736,
        "height": 912,
        "content": "## Who this template is for\n\nThis workflow is designed for gyms, fitness studios, and membership-based businesses that want to proactively reduce customer churn and improve retention.\n\nIt is ideal for businesses that manage recurring memberships and want to automatically detect members who may be at risk of canceling, then trigger the appropriate follow-up action based on their churn risk level.\n\nThis template is especially useful for operations teams, retention managers, and gym owners who want to replace reactive retention efforts with a more systematic and automated process.\n\n\nWhat this workflow does\n\nThis workflow automatically identifies members who may be at risk of canceling their membership and routes them through different retention paths based on their churn risk level.\n\nThe workflow:\n- Reads member data from a data source\n- Calculates a churn risk score for each member\n- Classifies each member into a retention category\n- Triggers different actions depending on the risk level\n- Sends follow-up communication when needed\n- Logs the outcome for tracking and operational visibility\n\n\nThis allows the business to prioritize outreach and intervene before a cancellation happens.\n\n\nHow it works\n\nThe workflow starts by loading member data into n8n. It then passes that data into a churn scoring engine, where each member is evaluated based on predefined business logic.\n\nThe scoring logic assigns a risk level that represents the likelihood of churn. This risk level is then used by a switch node to route each member into the correct path.\n\nMembers classified as critical or high risk are sent through a stronger intervention flow, which can include personalized email outreach and retention tracking. Members in medium risk can be logged for softer intervention or later monitoring. Members with low risk do not require immediate action and can be ignored or stored for reporting purposes.\n\nBy separating members into clearly defined churn categories, the workflow helps retention teams act faster and more consistently."
      },
      "typeVersion": 1
    },
    {
      "id": "936ef243-c2ab-4de2-998d-28b651754001",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        -208
      ],
      "parameters": {
        "color": 5,
        "width": 400,
        "height": 416,
        "content": "## A. Member data intake\n\nThis section starts the workflow and loads the member records that will be evaluated.\n\nIn the demo version, the workflow is triggered manually. In a real production setup, this step is usually replaced with a scheduled trigger that runs automatically every day."
      },
      "typeVersion": 1
    },
    {
      "id": "043fdf69-85ee-47a4-9ff2-a613daf2b38f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        -208
      ],
      "parameters": {
        "color": 5,
        "width": 576,
        "height": 416,
        "content": "## B. Churn scoring engine\n\nThis is the decision-making core of the workflow.\n\nThe code node analyzes each member using predefined retention logic and assigns a churn risk score or category. This score estimates how likely the member is to cancel their membership."
      },
      "typeVersion": 1
    },
    {
      "id": "d5f0a028-95a8-40bc-a946-cbeb4ef2c2d9",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        -400
      ],
      "parameters": {
        "color": 5,
        "width": 768,
        "height": 864,
        "content": "## C. Risk-based routing\n\nThis section uses the churn classification to route each member into the appropriate path.\n\nDepending on the assigned level, such as critical, high, medium, or low, the workflow determines whether the member should receive immediate intervention, be monitored, or require no action."
      },
      "typeVersion": 1
    },
    {
      "id": "8f911082-4cfd-4537-99e3-8e317613b7d2",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -672
      ],
      "parameters": {
        "color": 4,
        "width": 544,
        "height": 336,
        "content": "## \ud83d\udcca Sample Data Sheet\n\nTo use this template, make a copy of the sample Google Sheet below and connect it to the Google Sheets nodes:\n\n## \ud83d\udc49 [Click here to access the sample sheet](https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit?usp=sharing)\n\n## \u2699\ufe0f Setup steps\n- Go to **File \u2192 Make a copy**\n- Connect your Google account in the Google Sheets nodes\n- Point the nodes to your copied sheet"
      },
      "typeVersion": 1
    },
    {
      "id": "d11b3655-b5b0-41e4-829f-1870d33c1137",
      "name": "Fetch Member Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        208,
        0
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit#gid=0",
          "cachedResultName": "Members_Database"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit?usp=drivesdk",
          "cachedResultName": "AI Membership Retention & Churn Prevention System"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "12187428-7256-4186-9831-8c3527210858",
      "name": "Calculate Churn Ris",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        0
      ],
      "parameters": {
        "jsCode": "function parseDate(s) {\n  if (!s || typeof s !== 'string') return null;\n\n  // Supports \"YYYY-MM-DD\"\n  if (/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) {\n    const d = new Date(s + 'T00:00:00');\n    return isNaN(d.getTime()) ? null : d;\n  }\n\n  // Supports \"dd/MM/yyyy\"\n  if (/^\\d{2}\\/\\d{2}\\/\\d{4}$/.test(s)) {\n    const [dd, mm, yyyy] = s.split('/').map(Number);\n    const d = new Date(yyyy, mm - 1, dd);\n    return isNaN(d.getTime()) ? null : d;\n  }\n\n  // Last attempt\n  const d = new Date(s);\n  return isNaN(d.getTime()) ? null : d;\n}\n\nfunction daysBetween(a, b) {\n  return Math.floor((b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24));\n}\n\nfunction clamp(n, min, max) {\n  return Math.max(min, Math.min(max, n));\n}\n\nfunction round(n) {\n  return Math.round(n);\n}\n\nfunction bucketRenewal(daysToRenewal) {\n  if (daysToRenewal === null || daysToRenewal === undefined) return 'n/a';\n  if (daysToRenewal < 0) return 'expired';\n  if (daysToRenewal <= 7) return '7d';\n  if (daysToRenewal <= 15) return '15d';\n  if (daysToRenewal <= 30) return '30d';\n  return 'ok';\n}\n\n// today at midnight (avoids 0/1 day differences due to hours/timezone)\nconst today = new Date(new Date().toISOString().slice(0, 10) + 'T00:00:00');\n\nreturn items.map(item => {\n  const data = item.json;\n\n  // Normalize values\n  const checkins30 = Number(data.checkins_30d ?? 0);\n  const checkinsPrev = Number(data.checkins_prev_month ?? 0);\n  const cancels30 = Number(data.cancelled_classes_30d ?? 0);\n  const monthlyRevenue = Number(data.equivalent_monthly_price ?? 0);\n\n  const paymentStatus = String(data.payment_status ?? '').toLowerCase().trim();\n  const membershipStatus = String(data.membership_status ?? '').toLowerCase().trim();\n\n  // Dates\n  const lastCheckin = parseDate(data.last_checkin);\n  const renewalDate = parseDate(data.renewal_date);\n\n  const daysSinceLastVisit = lastCheckin ? daysBetween(lastCheckin, today) : null;\n  const daysToRenewal = renewalDate ? daysBetween(today, renewalDate) : null;\n\n  // Visit variation\n  let visitVariationPct = 0;\n  if (checkinsPrev > 0) {\n    visitVariationPct = ((checkins30 - checkinsPrev) / checkinsPrev) * 100;\n  } else if (checkinsPrev === 0 && checkins30 > 0) {\n    visitVariationPct = 100;\n  }\n\n  // If cancelled: no score\n  if (membershipStatus === 'cancelled') {\n    return {\n      json: {\n        ...data,\n        today_iso: today.toISOString().slice(0, 10),\n        days_since_last_visit: daysSinceLastVisit,\n        days_to_renewal: daysToRenewal,\n        visit_variation_pct: round(visitVariationPct),\n        churn_risk_score: 0,\n        risk_level: 'low',\n        main_trigger: 'cancelled',\n        renewal_bucket: 'n/a',\n        value_at_risk: 0,\n      }\n    };\n  }\n\n  // Renewal bucket (separate)\n  const renewBucket = bucketRenewal(daysToRenewal);\n\n  // ===============================\n  // 1) Payment risk (highest priority)\n  // ===============================\n  if (paymentStatus === 'failed') {\n    const score = 95;\n    return {\n      json: {\n        ...data,\n        today_iso: today.toISOString().slice(0, 10),\n        days_since_last_visit: daysSinceLastVisit,\n        days_to_renewal: daysToRenewal,\n        visit_variation_pct: round(visitVariationPct),\n        churn_risk_score: score,\n        risk_level: 'critical',\n        main_trigger: 'payment_failed',\n        renewal_bucket: renewBucket,\n        value_at_risk: Number(monthlyRevenue.toFixed(2)),\n      }\n    };\n  }\n\n  // ===============================\n  // 2) Engagement risk (usage/inactivity/friction)\n  // ===============================\n  let engScore = 0;\n  let trigger = 'stable';\n\n  // Inactivity (days since last visit)\n  if (daysSinceLastVisit !== null) {\n    if (daysSinceLastVisit >= 30) { engScore += 45; trigger = 'inactivity_30d'; }\n    else if (daysSinceLastVisit >= 14) { engScore += 30; if (trigger === 'stable') trigger = 'inactivity_14d'; }\n    else if (daysSinceLastVisit >= 7) { engScore += 15; if (trigger === 'stable') trigger = 'inactivity_7d'; }\n  }\n\n  // Low absolute usage\n  if (checkins30 <= 2) { engScore += 20; if (trigger === 'stable') trigger = 'low_usage'; }\n  else if (checkins30 <= 4) { engScore += 10; if (trigger === 'stable') trigger = 'low_usage'; }\n\n  // Drop vs previous month\n  if (visitVariationPct <= -50) { engScore += 30; trigger = 'usage_drop'; }\n  else if (visitVariationPct <= -30) { engScore += 20; if (trigger === 'stable') trigger = 'usage_drop'; }\n\n  // Cancellations\n  if (cancels30 >= 3) { engScore += 20; if (trigger === 'stable') trigger = 'cancellations'; }\n  else if (cancels30 === 2) { engScore += 12; if (trigger === 'stable') trigger = 'cancellations'; }\n  else if (cancels30 === 1) { engScore += 6; }\n\n  // Cap engagement\n  engScore = clamp(engScore, 0, 85);\n\n  const score = engScore;\n\n  // ===============================\n  // 3) Adjusted thresholds (fewer false criticals)\n  // ===============================\n  let riskLevel = 'low';\n  if (score >= 90) riskLevel = 'critical';\n  else if (score >= 70) riskLevel = 'high';\n  else if (score >= 45) riskLevel = 'medium';\n\n  // Value at risk: simple and defensible\n  const valueAtRisk = (riskLevel === 'high' || riskLevel === 'critical')\n    ? Number(monthlyRevenue.toFixed(2))\n    : 0;\n\n  return {\n    json: {\n      ...data,\n      today_iso: today.toISOString().slice(0, 10),\n      days_since_last_visit: daysSinceLastVisit,\n      days_to_renewal: daysToRenewal,\n      visit_variation_pct: round(visitVariationPct),\n      churn_risk_score: score,\n      risk_level: riskLevel,\n      main_trigger: trigger,\n      renewal_bucket: renewBucket,\n      value_at_risk: valueAtRisk,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "7352d46c-2974-4946-a22a-d715333eef53",
      "name": "Route by Risk Level",
      "type": "n8n-nodes-base.switch",
      "position": [
        624,
        -32
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "CRITICAL",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "23090f43-aa2f-40ab-9cbb-58af5a8265d9",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{$json.risk_level}}",
                    "rightValue": "critical"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "HIGH",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "4c1364b9-06ed-4aee-b256-bd6bb8e053ab",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{$json.risk_level}}",
                    "rightValue": "high"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "MEDIUM",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "3801f099-4acf-4842-82ba-7f3dae8b9f24",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{$json.risk_level}}",
                    "rightValue": "medium"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "LOW",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "53291a5f-0c31-45f7-a71a-24429141b5f1",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{$json.risk_level}}",
                    "rightValue": "low"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "4f149e8d-9746-4a0b-9c3b-43765e93b97d",
      "name": "Prepare Critical Alert",
      "type": "n8n-nodes-base.set",
      "position": [
        880,
        -176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0655ffb3-b85f-4d0e-89e1-4ec3a0a9a228",
              "name": "message",
              "type": "string",
              "value": "=\u26a0\ufe0f PAYMENT FAILED & MEMBERSHIP EXPIRED\n\nA critical case has been detected that requires immediate contact.\n\nMain trigger: {{ $json.main_trigger }}\nDays since last visit: {{ $json.days_since_last_visit }}\nDays to renewal: {{ $json.days_to_renewal }}\n\nMonthly value at risk: {{ $json.value_at_risk }}\u20ac\n"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "8ced6c40-82b9-4fd6-9d2b-eae34fec264b",
      "name": "Prepare High Risk Email",
      "type": "n8n-nodes-base.set",
      "position": [
        880,
        -16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0655ffb3-b85f-4d0e-89e1-4ec3a0a9a228",
              "name": "mensaje_winback",
              "type": "string",
              "value": "=\ud83d\udcc9 ACTIVITY DROP DETECTED\n\nA significant decrease in gym usage has been detected.\n\nMain trigger: {{ $json.main_trigger }}\nVisit variation: {{ $json.visit_variation_pct }}%\nDays since last visit: {{ $json.days_since_last_visit }}\nRenewal in {{ $json.days_to_renewal }} days.\n\nMonthly value at risk: {{ $json.value_at_risk }}\u20ac\n\n\n"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "f70ab5c8-9d4c-4760-933c-77fbf2144fdb",
      "name": "Format Medium Risk Log",
      "type": "n8n-nodes-base.set",
      "position": [
        880,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0655ffb3-b85f-4d0e-89e1-4ec3a0a9a228",
              "name": "mensaje_winback",
              "type": "string",
              "value": "=\ud83d\udfe1 FOLLOW-UP (MEDIUM RISK)\n\nAn irregular usage pattern has been detected, but not critical.\n\nTrigger: {{ $json.main_trigger }}\nDays since last visit: {{ $json.days_since_last_visit }}\nVisit variation: {{ $json.visit_variation_pct }}%\n\nRecommendation:\nSend a soft message or team check-in within 48\u201372h."
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "be7241f5-c227-4114-b0ec-d17707e83570",
      "name": "Send Critical Notification",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1072,
        -176
      ],
      "parameters": {
        "sendTo": "your email here",
        "message": "=\ud83d\udea8 CRITICAL ALERT \u2013 IMMEDIATE ACTION REQUIRED\n\nMember: {{ $('Route by Risk Level').item.json.name }}\nCenter: {{ $('Route by Risk Level').item.json.main_center }}\nPlan: {{ $('Route by Risk Level').item.json.plan }}\nRisk level: {{ $('Route by Risk Level').item.json.risk_level }}\n\nPayment status: {{ $('Route by Risk Level').item.json.payment_status }}\nRenewal: {{ $('Route by Risk Level').item.json.renewal_date }} ({{ $('Route by Risk Level').item.json.days_to_renewal }} days)\n\nDetected trigger:\n{{ $json.message }}\n\nRecommended action:\nContact the member today to regularize the payment and prevent cancellation.",
        "options": {
          "appendAttribution": false
        },
        "subject": "= \ud83d\udea8 CRITICAL ALERT \u2013 {{ $('Route by Risk Level').item.json.name }} ({{ $('Route by Risk Level').item.json.main_center }})",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "5c1991f9-ef4a-4819-8037-80fc1ec40948",
      "name": "Send Winback Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1072,
        -16
      ],
      "parameters": {
        "sendTo": "=your email here",
        "message": "=\u26a0\ufe0f MEMBER AT HIGH CHURN RISK\n\nMember: {{ $json.name }}\nCenter: {{ $json.main_center }}\nPlan: {{ $json.plan }}\nRisk level: {{ $json.risk_level }}\n\n\nRecent activity:\n\nCheck-ins last 30 days: {{ $json.checkins_30d }}\nUsage variation: {{ $json.visit_variation_pct }}%\nDays since last visit: {{ $json.days_since_last_visit }}\n\nDetail:\n{{ $json.winback_message }}\n\nRecommended action:\nPreventive contact (message or call) + light incentive if applicable.",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\u26a0\ufe0f HIGH Risk - {{ $json.name }} {{ $json.main_center }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "4948c2b2-d760-4f2b-80a1-ddbbc90c3d75",
      "name": "Log Critical Action",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1280,
        -176
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $now.format(('dd/LL/yyyy')) }}",
            "name": "={{ $('Calculate Churn Ris').item.json.name }}",
            "action": "critical_alert_sent",
            "trigger": "={{ $('Calculate Churn Ris').item.json.main_trigger }}",
            "member_id": "={{ $('Calculate Churn Ris').item.json.member_id }}",
            "risk_level": "={{ $('Calculate Churn Ris').item.json.risk_level }}",
            "risk_value": "={{ $('Calculate Churn Ris').item.json.value_at_risk }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "member_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "member_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_level",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "risk_level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "trigger",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "trigger",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_value",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "risk_value",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1456278820,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit#gid=1456278820",
          "cachedResultName": "Retention_Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit?usp=drivesdk",
          "cachedResultName": "AI Membership Retention & Churn Prevention System"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "21f5338b-3181-4211-b08d-d5e8fb3f0106",
      "name": "Log High Risk Action",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1296,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $now.format(('dd/LL/yyyy')) }}",
            "name": "={{ $('Calculate Churn Ris').item.json.name }}",
            "action": "winback_sent",
            "trigger": "={{ $('Calculate Churn Ris').item.json.main_trigger }}",
            "member_id": "={{ $('Calculate Churn Ris').item.json.member_id }}",
            "risk_level": "={{ $('Calculate Churn Ris').item.json.risk_level }}",
            "risk_value": "={{ $('Calculate Churn Ris').item.json.value_at_risk }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "member_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "member_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_level",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "risk_level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "trigger",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "trigger",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_value",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "risk_value",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1456278820,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit#gid=1456278820",
          "cachedResultName": "Retention_Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit?usp=drivesdk",
          "cachedResultName": "AI Membership Retention & Churn Prevention System"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "18ed4309-7e19-4742-bd41-54a06a294b78",
      "name": "Log Medium Risk Member",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1104,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $now.format(('dd/LL/yyyy')) }}",
            "name": "={{ $('Calculate Churn Ris').item.json.name }}",
            "action": "scheduled_followup",
            "trigger": "={{ $('Calculate Churn Ris').item.json.main_trigger }}",
            "member_id": "={{ $('Calculate Churn Ris').item.json.member_id }}",
            "risk_level": "={{ $('Calculate Churn Ris').item.json.risk_level }}",
            "risk_value": "={{ $('Calculate Churn Ris').item.json.value_at_risk }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "member_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "member_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_level",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "risk_level",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "trigger",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "trigger",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_value",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "risk_value",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1456278820,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit#gid=1456278820",
          "cachedResultName": "Retention_Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1HGWMasSZvefI3wt5KgXlvh4l6WssMHHjNtJABZEKbAk/edit?usp=drivesdk",
          "cachedResultName": "AI Membership Retention & Churn Prevention System"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "e2e32ccf-6582-410e-b4ac-6187e5d753cd",
      "name": "Ignore Low Risk",
      "type": "n8n-nodes-base.noOp",
      "position": [
        880,
        336
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "timezone": "Europe/Madrid",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "21cbee97-bad4-4ff3-9e0a-5260877a2b34",
  "connections": {
    "Fetch Member Data": {
      "main": [
        [
          {
            "node": "Calculate Churn Ris",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Winback Email": {
      "main": [
        [
          {
            "node": "Log High Risk Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Churn Ris": {
      "main": [
        [
          {
            "node": "Route by Risk Level",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Risk Level": {
      "main": [
        [
          {
            "node": "Prepare Critical Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare High Risk Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Medium Risk Log",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ignore Low Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Medium Risk Log": {
      "main": [
        [
          {
            "node": "Log Medium Risk Member",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Critical Alert": {
      "main": [
        [
          {
            "node": "Send Critical Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare High Risk Email": {
      "main": [
        [
          {
            "node": "Send Winback Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Critical Notification": {
      "main": [
        [
          {
            "node": "Log Critical Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Fetch Member Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

About this workflow

This template is for gyms, fitness studios, and membership-based businesses that want to automatically detect at-risk members and act before they cancel. Ideal for operations teams and retention managers who want to replace manual follow-ups with a systematic, automated process.

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

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Loan eligibility workflow. Uses formTrigger, googleSheets, gmail. Event-driven trigger; 53 nodes.

Form Trigger, Google Sheets, Gmail
Email & Gmail

Splitout Code. Uses manualTrigger, httpRequest, stickyNote, splitOut. Event-driven trigger; 46 nodes.

HTTP Request, Execute Workflow Trigger, Gmail +1
Email & Gmail

Automate CSV imports into HubSpot without the mess. Powered by n8n. Supercharged by Pollup AI.

HTTP Request, Execute Workflow Trigger, Gmail +1
Email & Gmail

AICARE Email Blast System. Uses googleDrive, httpRequest, googleSheets, gmail. Event-driven trigger; 39 nodes.

Google Drive, HTTP Request, Google Sheets +2
Email & Gmail

Telegram Code. Uses stickyNote, telegramTrigger, telegram, googleDrive. Event-driven trigger; 37 nodes.

Telegram Trigger, Telegram, Google Drive +2