2026-09-07 · 原文发布日
健身房流失预警:自动识别高风险会员
每天定时从Supabase读活跃会员、近两周打卡和近三周周报表数据,算出距上次打卡天数、周打卡次数、缺勤活动数等指标,给出风险分与风险类型,并带三天的重复告警冷却期防止骚扰。筛出的高风险会员由Gemini生成简短挽回邮件草稿,Slack通知对应教练,Gmail发给会员本人,风险事件同时写回Supabase和Google Sheets两套台账。
原文要点
WeblineIndia发布的n8n定时工作流:每天从Supabase读取活跃会员、近14天打卡和近21天周报表三张表,按会员合并后计算距上次打卡天数、周打卡次数、缺勤活动数,得出风险分与风险类型,并有3天重复告警冷却期(alert_allowed_now为假时跳过)。高风险会员由Gemini(models/gemini-2.5-flash)生成JSON格式邮件草稿(主题+正文),Slack通知教练、Gmail发给会员,风险事件写回Supabase的risk_events表并追加到Google Sheets的Risk_Alert_Log,同时更新会员告警状态并同步到Clients_Master表。需要clients、checkins、weekly_forms、risk_events四张表及对应字段。
这次做成什么
用本站设计的合成CSV数据在n8n里复刻完整链路:定时触发→三源数据合并→Code节点算风险分→冷却期过滤→Gemini起草挽回邮件→Slack/Gmail通知→双台账写回,先用合成数据验证评分与冷却逻辑再换真库。
准备什么
- n8n实例及Schedule Trigger可用
- Supabase项目与凭据,已建clients、checkins、weekly_forms、risk_events四张表(练习阶段可先用合成CSV代替)
- Google Gemini API凭据,节点需允许调用models/gemini-2.5-flash
- Slack凭据和教练告警频道ID
- Gmail OAuth发送邮箱凭据
- Google Sheets凭据,以及建好Clients_Master和Risk_Alert_Log两个工作表的表格ID
具体怎么做
配置定时触发与三路数据读取
第一个节点用Schedule Trigger设为每天固定时间(如早8点)。后面并排放三个Supabase读取节点:clients表过滤status为active的行;checkins表过滤checkin_date在近14天内;weekly_forms表过滤submitted_at在近21天内。练习阶段用三个Read Binary Files或Code节点载入下方合成CSV代替。
按会员合并并计算风险指标
用Merge节点以client_id把三路数据合并(多对一用Merge的Combine模式或Code节点聚合)。接一个Code节点逐会员计算:days_since_checkin(今天减最后一次打卡日)、weekly_checkin_count(近14天打卡数除以2)、missed_activities(应打卡次数减实际次数),再按本站示例评分规则算出risk_score和risk_type,同时读取last_alert_sent_at判断距今天数是否≥3,得到alert_allowed_now布尔值。注意:本站练习评分只基于打卡数据,原文指标还包含距上次提交周报表的天数,需要时可仿照days_since_checkin的算法把weekly_forms纳入评分。
过滤高风险且过冷却期的会员
接一个IF节点:risk_score达到高风险阈值且alert_allowed_now为true才放行。冷却期内的会员被跳过(原文建议可加一条blocked分支记录被跳过者,练习阶段可省略)。
Gemini生成挽回邮件草稿
对通过的会员,用Set节点拼接个性化提示词(含会员名、缺席天数、风险类型),接Gemini节点调用gemini-2.5-flash,要求只返回JSON。后接Code节点解析subject和body两个字段;解析失败说明模型加了多余格式,需收紧提示词中的"仅输出JSON"要求。
通知与双台账写回
Slack节点向教练频道发汇总(会员名、风险分、缺席天数、邮件草稿摘要);Gmail节点向会员邮箱发草稿邮件。然后Supabase节点向risk_events插入风险事件,Google Sheets节点向Risk_Alert_Log追加同一记录;再更新clients表的last_alert_sent_at与升级字段,并同步更新Clients_Master工作表对应行。
可直接复制的材料
合成练习数据(三个CSV)
本站虚构合成数据,假设运行日为2026-09-07;邮箱均为示例地址,不要发真实邮件;真实环境替换为Supabase查询
clients.csv:
client_id,full_name,email,coach_slack_id,status,last_alert_sent_at
C001,林晓,linxiao@example.com,U08COACH1,active,2026-08-20
C002,张远,zhangyuan@example.com,U08COACH2,active,
C003,王蕾,wanglei@example.com,U08COACH1,active,
checkins.csv:
client_id,checkin_date
C001,2026-09-06
C002,2026-08-24
C003,2026-08-25
weekly_forms.csv:
client_id,submitted_at
C001,2026-09-05
C002,2026-08-18
C003,2026-08-26风险评分与冷却判断Code节点(本站示例规则)
评分规则为本站独立设计的练习方案,原文未公开阈值细节,可按门店实际调整;冷却期3天与原文一致
// 假设运行日为当天;输入为已按client_id合并的记录
const today = new Date();
const out = [];
for (const item of $input.all()) {
const c = item.json;
const last = c.last_checkin ? new Date(c.last_checkin) : null;
const daysSince = last ? Math.floor((today - last) / 86400000) : 999;
const weeklyCount = Math.round((c.checkins_14d || 0) / 2 * 10) / 10;
const missed = Math.max(0, (c.expected_checkins || 6) - (c.checkins_14d || 0));
let score = 0;
if (daysSince >= 10) score += 50; else if (daysSince >= 7) score += 35; else if (daysSince >= 4) score += 15;
if (weeklyCount < 1) score += 30; else if (weeklyCount < 2) score += 15;
score += Math.min(20, missed * 4);
const type = score >= 70 ? "high" : score >= 40 ? "medium" : "low";
const lastAlert = c.last_alert_sent_at ? new Date(c.last_alert_sent_at) : null;
const allowed = !lastAlert || ((today - lastAlert) / 86400000) >= 3;
out.push({ json: { ...c, days_since_checkin: daysSince, weekly_checkin_count: weeklyCount, missed_activities: missed, risk_score: score, risk_type: type, alert_allowed_now: allowed } });
}
return out;Gemini挽回邮件提示词
本站独立编写的提示词,n8n表达式需在Gemini节点中按实际字段名绑定;解析失败时优先检查模型是否加了markdown包裹
你是一位中文健身房的会员关怀专员。请为以下会员起草一封简短、温暖、不带指责的挽回邮件。
会员姓名:{{$json.full_name}}
距上次到店打卡天数:{{$json.days_since_checkin}}天
近期每周打卡次数:{{$json.weekly_checkin_count}}
缺勤活动数:{{$json.missed_activities}}
风险类型:{{$json.risk_type}}
要求:
1. 语气鼓励而非施压,可提到教练关心、可预约一节恢复课。
2. 正文不超过120个中文字。
3. 只输出JSON,不要任何额外文字、不要用代码块包裹,格式为:{"subject":"邮件主题","body":"邮件正文"}输入与输出示例
本站虚构演示,未真实调用Gemini/Slack/Gmail,邮件文案为示意,非原作者运行结果
输入
运行日2026-09-07,载入上述合成CSV:张远(C002)上次打卡2026-08-24、近14天窗口内打卡0次、无告警记录;林晓(C001)2026-09-06刚打过卡;王蕾(C003)上次打卡2026-08-25、无告警记录。对应输出
张远:days_since_checkin=14,weekly_checkin_count=0,missed_activities=6,risk_score=50+30+20=100,risk_type=high,alert_allowed_now=true,进入通知分支。Gemini草稿示意:{"subject":"好久不见,教练想你了","body":"张远你好,最近两周没见到你,教练挂念你的训练进度。本周帮你留了一节恢复课,随时回复预约。"}。Slack向U08COACH2频道推送汇总,Gmail发出邮件,risk_events与Risk_Alert_Log各写入一条记录,clients表last_alert_sent_at更新为2026-09-07。林晓:days_since_checkin=1,weekly_checkin_count=0.5,missed_activities=5,risk_score=50为medium,未达高风险阈值被过滤。王蕾:days_since_checkin=13,weekly_checkin_count=0.5,missed_activities=5,risk_score=100同为high且alert_allowed_now=true,同样进入通知分支并各自写入台账。做完检查这些
- 用合成数据运行后,张远必须进入通知分支且risk_type为high,林晓不得通过风险过滤
- 把某会员last_alert_sent_at改为昨天再运行,该会员alert_allowed_now为false并被跳过
- Gemini输出能被解析节点直接JSON.parse出subject和body两个字段,无markdown包裹
- risk_events表和Risk_Alert_Log在每次成功通知后各新增一行且client_id一致
- clients表last_alert_sent_at在通知成功后更新为当天,Clients_Master对应行同步
容易卡住的地方
- 三张源表任一读数为空都会让Merge输出残缺,应先单独测试每个Supabase读节点和日期过滤
- 冷却期导致会员被跳过属预期行为,排查时先查last_alert_sent_at而不是怀疑评分逻辑
- Gemini返回非纯JSON是常见失败点,提示词中"仅输出JSON"指令不能删
- Sheets追加失败多半是spreadsheet ID、工作表名或列映射与实际文件不符,需逐节点重选
- Supabase更新不生效时核对client_id在流程输出与clients表中完全一致
- Slack、Gmail、Gemini均可能产生权限或配额问题,发送前先在各自节点做一次单独测试
本站借助AI整理来源事实,独立编写中文操作指南与示例。示例不代表实际运行结果;来源效果尚未由本站独立复现。付费模板与模型、服务费用分别计算。
- 原文标题
- Detect at-risk gym clients with Supabase, Gemini, Slack, Gmail, and Sheets
- 原作者
- WeblineIndia
- 原始发布日期
- 2026-09-07T09:41:58.058Z
- 来源
- https://n8n.io/workflows/19314