AutomationFlowsAI & RAG › Score Customer Churn Risk and Send Save-play Emails with Claude and Gmail

Score Customer Churn Risk and Send Save-play Emails with Claude and Gmail

BySwapnil Mandloi @swapnil-mandloi on n8n.io

Catch at-risk paying customers before they cancel. This workflow receives customer health signals through a webhook, scores churn risk from 0–100 with Claude, logs every account to a data table, and automatically sends a personalized save-play email to the customers who are…

Webhook trigger★★★★☆ complexityAI-powered15 nodesAgentAnthropic ChatOutput Parser StructuredData TableGmail
AI & RAG Trigger: Webhook Nodes: 15 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Agent → Datatable 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": "yKQKrojo96fDaYek",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Silent Churn Radar \u2014 Customer Health & Save-Play OS",
  "tags": [],
  "nodes": [
    {
      "id": "b2cfe2f5-da42-4add-a9e5-4893f16e1614",
      "name": "Customer Signal Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        0,
        304
      ],
      "parameters": {
        "path": "churn-radar",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "6849a720-e2a3-4869-9a39-6ac75fa4edc7",
      "name": "Normalize Signals",
      "type": "n8n-nodes-base.set",
      "position": [
        224,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cid",
              "name": "customer_id",
              "type": "string",
              "value": "={{ $json.body?.customer_id ?? $json.customer_id ?? \"\" }}"
            },
            {
              "id": "cname",
              "name": "customer_name",
              "type": "string",
              "value": "={{ $json.body?.customer_name ?? $json.customer_name ?? \"there\" }}"
            },
            {
              "id": "cemail",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body?.email ?? $json.email ?? \"\" }}"
            },
            {
              "id": "pval",
              "name": "plan_value",
              "type": "number",
              "value": "={{ $json.body?.plan_value ?? $json.plan_value ?? 0 }}"
            },
            {
              "id": "dsl",
              "name": "days_since_login",
              "type": "number",
              "value": "={{ $json.body?.days_since_login ?? $json.days_since_login ?? 0 }}"
            },
            {
              "id": "ucp",
              "name": "usage_change_pct",
              "type": "number",
              "value": "={{ $json.body?.usage_change_pct ?? $json.usage_change_pct ?? 0 }}"
            },
            {
              "id": "eor",
              "name": "email_open_rate",
              "type": "number",
              "value": "={{ $json.body?.email_open_rate ?? $json.email_open_rate ?? 0 }}"
            },
            {
              "id": "ss",
              "name": "support_sentiment",
              "type": "string",
              "value": "={{ $json.body?.support_sentiment ?? $json.support_sentiment ?? \"neutral\" }}"
            },
            {
              "id": "dtr",
              "name": "days_to_renewal",
              "type": "number",
              "value": "={{ $json.body?.days_to_renewal ?? $json.days_to_renewal ?? 999 }}"
            },
            {
              "id": "ot",
              "name": "open_tickets",
              "type": "number",
              "value": "={{ $json.body?.open_tickets ?? $json.open_tickets ?? 0 }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7bbd2b2f-565c-4a6c-a47a-eadbd0e0a56d",
      "name": "AI Churn Score",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        464,
        304
      ],
      "parameters": {
        "text": "=Score churn risk for this customer from the health signals below.\n\nCustomer: {{ $json.customer_name }} ({{ $json.customer_id }})\nMonthly plan value: {{ $json.plan_value }}\nDays since last login: {{ $json.days_since_login }}\nUsage change: {{ $json.usage_change_pct }} percent\nEmail open rate: {{ $json.email_open_rate }} percent\nSupport sentiment: {{ $json.support_sentiment }}\nDays to renewal: {{ $json.days_to_renewal }}\nOpen support tickets: {{ $json.open_tickets }}",
        "options": {
          "systemMessage": "You are a senior customer-retention analyst. Score churn risk from 0 to 100 (higher means more likely to cancel) using these weighted signals: days since last login (longer is worse), usage change (steep negative is worse), email open rate (low is worse), support sentiment (frustrated or angry is worse), days to renewal (fewer days combined with other red flags is more urgent), open tickets (more is worse), and plan value (higher value raises how urgent it is to save the account). Map the score to risk_tier: RED means 70-100 (act now), YELLOW means 40-69 (nurture), GREEN means 0-39 (healthy). Set top_signals to a short semicolon-separated list of the 2-3 biggest drivers. Set reasoning to one crisp sentence. Then write a warm, specific retention email: save_play_subject (60 characters or fewer, no clickbait) and save_play_message as clean HTML using <p> paragraphs and at most one <a href> call-to-action, addressed to the customer by first name, acknowledging their situation and offering concrete help. For GREEN customers write a light, friendly check-in instead. Return ONLY the JSON object."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "f3e5a119-6003-4f43-8ada-5ca2c01b7905",
      "name": "Claude Sonnet 4.6",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        368,
        544
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-6",
          "cachedResultName": "Claude Sonnet 4.6"
        },
        "options": {
          "temperature": 0.1,
          "maxTokensToSample": 1024
        }
      },
      "typeVersion": 1.5
    },
    {
      "id": "7dcbe7c7-ad60-4e0d-b031-36c939bae2ec",
      "name": "Churn Score Schema",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        560,
        544
      ],
      "parameters": {
        "jsonSchemaExample": "{ \"churn_score\": 82, \"risk_tier\": \"RED\", \"top_signals\": \"No login in 21 days; usage down 60 percent; renewal in 9 days\", \"reasoning\": \"High-value account went quiet right before renewal with a steep usage drop.\", \"save_play_subject\": \"Priya, noticed you have been away\", \"save_play_message\": \"<p>Hi Priya,</p><p>We have not seen you in a while and wanted to check in.</p>\" }"
      },
      "typeVersion": 1.3
    },
    {
      "id": "ada6f30e-06e2-4f92-9b6a-e47677ddab8e",
      "name": "Shape Health Record",
      "type": "n8n-nodes-base.set",
      "position": [
        720,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "s_cid",
              "name": "customer_id",
              "type": "string",
              "value": "={{ $(\"Normalize Signals\").item.json.customer_id }}"
            },
            {
              "id": "s_cname",
              "name": "customer_name",
              "type": "string",
              "value": "={{ $(\"Normalize Signals\").item.json.customer_name }}"
            },
            {
              "id": "s_email",
              "name": "email",
              "type": "string",
              "value": "={{ $(\"Normalize Signals\").item.json.email }}"
            },
            {
              "id": "s_pval",
              "name": "plan_value",
              "type": "number",
              "value": "={{ $(\"Normalize Signals\").item.json.plan_value }}"
            },
            {
              "id": "s_score",
              "name": "churn_score",
              "type": "number",
              "value": "={{ $json.churn_score }}"
            },
            {
              "id": "s_tier",
              "name": "risk_tier",
              "type": "string",
              "value": "={{ $json.risk_tier }}"
            },
            {
              "id": "s_signals",
              "name": "top_signals",
              "type": "string",
              "value": "={{ $json.top_signals }}"
            },
            {
              "id": "s_reason",
              "name": "reasoning",
              "type": "string",
              "value": "={{ $json.reasoning }}"
            },
            {
              "id": "s_subj",
              "name": "save_play_subject",
              "type": "string",
              "value": "={{ $json.save_play_subject }}"
            },
            {
              "id": "s_msg",
              "name": "save_play_message",
              "type": "string",
              "value": "={{ $json.save_play_message }}"
            },
            {
              "id": "s_action",
              "name": "action_taken",
              "type": "string",
              "value": "={{ $json.risk_tier === \"RED\" ? \"Save-play email sent\" : $json.risk_tier === \"YELLOW\" ? \"Nurture email sent\" : \"Monitored (healthy)\" }}"
            },
            {
              "id": "s_ts",
              "name": "logged_at",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "4b1d3787-1c1b-4c95-917f-7c01f0eacf1a",
      "name": "Log to Health Table",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        992,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "email": "={{ $(\"Shape Health Record\").item.json.email }}",
            "logged_at": "={{ $(\"Shape Health Record\").item.json.logged_at }}",
            "reasoning": "={{ $(\"Shape Health Record\").item.json.reasoning }}",
            "risk_tier": "={{ $(\"Shape Health Record\").item.json.risk_tier }}",
            "plan_value": "={{ $(\"Shape Health Record\").item.json.plan_value }}",
            "churn_score": "={{ $(\"Shape Health Record\").item.json.churn_score }}",
            "customer_id": "={{ $(\"Shape Health Record\").item.json.customer_id }}",
            "top_signals": "={{ $(\"Shape Health Record\").item.json.top_signals }}",
            "action_taken": "={{ $(\"Shape Health Record\").item.json.action_taken }}",
            "customer_name": "={{ $(\"Shape Health Record\").item.json.customer_name }}"
          },
          "schema": [
            {
              "id": "customer_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "customer_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "customer_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "plan_value",
              "type": "number",
              "display": true,
              "required": false,
              "displayName": "plan_value",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "churn_score",
              "type": "number",
              "display": true,
              "required": false,
              "displayName": "churn_score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "risk_tier",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "risk_tier",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "top_signals",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "top_signals",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "reasoning",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "reasoning",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "action_taken",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "action_taken",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "logged_at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "logged_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow"
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "customer_health_log"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "e4820ee0-e07e-4335-bad4-8a12376cf6bc",
      "name": "Route by Risk",
      "type": "n8n-nodes-base.switch",
      "position": [
        992,
        448
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "RED",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.risk_tier }}",
                    "rightValue": "RED"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "YELLOW",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.risk_tier }}",
                    "rightValue": "YELLOW"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "GREEN / Monitor"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f6f73ee4-586e-430d-83b6-b6dfe4a7e25a",
      "name": "Send Save-Play Email",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        1232,
        256
      ],
      "parameters": {
        "sendTo": "={{ $(\"Shape Health Record\").item.json.email }}",
        "message": "={{ $(\"Shape Health Record\").item.json.save_play_message }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $(\"Shape Health Record\").item.json.save_play_subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.2,
      "waitBetweenTries": 2000
    },
    {
      "id": "9040e671-10e4-4659-a1b2-e790dda015bb",
      "name": "Send Nurture Email",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        1232,
        448
      ],
      "parameters": {
        "sendTo": "={{ $(\"Shape Health Record\").item.json.email }}",
        "message": "={{ $(\"Shape Health Record\").item.json.save_play_message }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $(\"Shape Health Record\").item.json.save_play_subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.2,
      "waitBetweenTries": 2000
    },
    {
      "id": "4d1937a1-363a-41d6-b0a2-42e02b68a8c2",
      "name": "No Action \u2014 Healthy",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1232,
        624
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "09b8a5af-fd3b-4a0c-8cdf-8a711696be89",
      "name": "Sticky Note 64518f94",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        256
      ],
      "parameters": {
        "color": 4,
        "width": 520,
        "height": 584,
        "content": "## \ud83e\ude7a Silent Churn Radar \u2014 Customer Health & Save-Play OS\n\nDetects at-risk paying customers BEFORE they cancel, scores churn risk with AI, and auto-fires a personalized save-play email.\n\n**Flow:** Signal in \u2192 Normalize \u2192 AI Score \u2192 Shape \u2192 log to DataTable + route by risk \u2192 email.\n\n### Setup (3 steps)\n1. Connect **Anthropic** + **Gmail** credentials.\n2. Data Table `customer_health_log` is already created for this workflow.\n3. Activate + POST customer signals to the webhook (payload sample on the left)."
      },
      "typeVersion": 1
    },
    {
      "id": "bd7176f0-2ce7-4141-96b1-ed102dd62fdc",
      "name": "Sticky Note ef4d5c4a",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -176
      ],
      "parameters": {
        "color": 5,
        "width": 316,
        "height": 392,
        "content": "### \ud83d\udce5 Input payload (POST JSON)\n`customer_id, customer_name, email, plan_value, days_since_login, usage_change_pct, email_open_rate, support_sentiment, days_to_renewal, open_tickets`\n\nMissing fields default safely. Send one customer per call (loop upstream for bulk)."
      },
      "typeVersion": 1
    },
    {
      "id": "3314fc08-6e8b-418d-8022-06660bf3ece1",
      "name": "Sticky Note 15a1d09c",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        16
      ],
      "parameters": {
        "color": 6,
        "width": 516,
        "height": 248,
        "content": "### \ud83e\udde0 AI Score\nClaude scores churn 0-100 \u2192 RED / YELLOW / GREEN, picks top signals, and writes a ready-to-send HTML save-play email. Swap the model node for OpenAI if you prefer. Temp kept low (0.1) for consistency."
      },
      "typeVersion": 1
    },
    {
      "id": "4a797dc9-b95d-44d8-9c58-47cc098e0887",
      "name": "Sticky Note 75e1a459",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        784
      ],
      "parameters": {
        "color": 3,
        "width": 524,
        "height": 248,
        "content": "### \ud83d\udce7 Save-Play\nRED \u2192 save-play email, YELLOW \u2192 nurture email, GREEN \u2192 just monitored. All senders use retry 3\u00d7/2s and reference the **Shape** node explicitly (never $json after insert) so fields never come through null."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "ab050ae8-13d1-43b2-bf69-48a40e4438b1",
  "nodeGroups": [],
  "connections": {
    "Route by Risk": {
      "main": [
        [
          {
            "node": "Send Save-Play Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Nurture Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Action \u2014 Healthy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Churn Score": {
      "main": [
        [
          {
            "node": "Shape Health Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet 4.6": {
      "ai_languageModel": [
        [
          {
            "node": "AI Churn Score",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Signals": {
      "main": [
        [
          {
            "node": "AI Churn Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Churn Score Schema": {
      "ai_outputParser": [
        [
          {
            "node": "AI Churn Score",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Shape Health Record": {
      "main": [
        [
          {
            "node": "Log to Health Table",
            "type": "main",
            "index": 0
          },
          {
            "node": "Route by Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Customer Signal Webhook": {
      "main": [
        [
          {
            "node": "Normalize Signals",
            "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

Catch at-risk paying customers before they cancel. This workflow receives customer health signals through a webhook, scores churn risk from 0–100 with Claude, logs every account to a data table, and automatically sends a personalized save-play email to the customers who are…

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow automates enterprise resource planning (ERP) operations across Engineering, Finance, HR, and Admin departments for operations managers, ERP administrators, and business process owners wh

Agent, Gmail, Slack +6
AI & RAG

This workflow receives free-signup events via webhook, enriches the contact with Twain research (and optional Findymail LinkedIn lookup), uses Anthropic Claude to score ICP fit, then routes outcomes t

Slack, HTTP Request, Agent +6
AI & RAG

JoinDAn8n. Uses httpRequest, dataTable, emailReadImap, lmChatOpenAi. Webhook trigger; 37 nodes.

HTTP Request, Data Table, Email Read Imap +6
AI & RAG

This workflow automates end-to-end contract analysis using AI. It extracts clauses, evaluates risks, tracks obligations, and generates an executive summary from uploaded contracts.

Agent, Anthropic Chat, Output Parser Structured +3
AI & RAG

This workflow automates credit operations onboarding by running KYC verification, credit bureau checks, identity validation, and sanctions screening through a single AI-powered agent. Built for credit

Agent, OpenAI Chat, Output Parser Structured +8