AutomationFlowsSlack & Telegram › Track Gym Renewals, Inactivity, and Check-in Streaks with Supabase and Whatsapp

Track Gym Renewals, Inactivity, and Check-in Streaks with Supabase and Whatsapp

ByKanishka Shrivastava @kanishka-shrivastava on n8n.io

This workflow runs daily to pull gym members from Supabase, calculate membership expiry and check-in activity, and send WhatsApp messages for renewal reminders, trainer inactivity alerts, and check-in streak milestone celebrations. Runs every day on a schedule. Loads…

Cron / scheduled trigger★★★★☆ complexity15 nodesSupabaseWhatsApp
Slack & Telegram Trigger: Cron / scheduled Nodes: 15 Complexity: ★★★★☆ Added:

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

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": "yB7RpmSpVKNHEEeW",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Track gym member renewals and check-in streaks with WhatsApp",
  "tags": [],
  "nodes": [
    {
      "id": "8000bb1b-19ff-41f2-a0b9-b7040d12c5cd",
      "name": "Template Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 4,
        "width": 700,
        "height": 1060,
        "content": "## \ud83d\udcaa Track Gym Member Renewals and Check-In Streaks with WhatsApp\n\n## Who's it for\nGyms and fitness studios that track membership expiry and check-ins in a database and want three things handled automatically: renewal reminders before a membership lapses, an early heads-up to a trainer when a member goes quiet before they've fully churned, and a small congratulatory nudge when someone hits a check-in streak milestone.\n\n## How it works\nOnce a day, the workflow pulls every member's record and computes their current status: days until membership expiry, days since their last check-in, and whether they've hit a new streak milestone. Each member is checked against three independent conditions, a member can match more than one at once. Expiring memberships get a WhatsApp renewal reminder. Members who've gone quiet for too long trigger a trainer alert instead of just another ignorable text. Milestone streaks get a celebratory message sent straight to the member, small positive reinforcement that's easy to forget to do by hand for every member.\n\n## How to set up\n1. Connect your Supabase credentials and create the members table described in the sticky note near the Fetch Members node.\n2. Connect your WhatsApp Business Cloud credentials to all three send nodes.\n3. Open the Set Configuration node and fill in your Supabase table name, WhatsApp phone number ID, trainer's WhatsApp number, renewal reminder window, and inactivity threshold.\n4. Set your preferred run time in the Schedule Trigger.\n5. Activate the workflow.\n\n## Requirements\n- Supabase project with member expiry and check-in data\n- WhatsApp Business Cloud API access\n\n## How to customize\nEdit the streak milestones list in the Compute Member Status node (defaults to 7, 30, and 100 days). Adjust the inactivity and renewal-reminder thresholds in Set Configuration to match how your gym actually operates."
      },
      "typeVersion": 1
    },
    {
      "id": "a92292cd-9026-41fa-9624-78ddeff19f79",
      "name": "Section - Trigger and Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1180,
        "height": 528,
        "content": "## 1\ufe0f\u20e3 2\ufe0f\u20e3 3\ufe0f\u20e3 Trigger + Fetch + Compute\nRuns daily, pulls every member, then computes their status (days to expiry, days since check-in, milestone reached) in one place before any branching happens."
      },
      "typeVersion": 1
    },
    {
      "id": "6bd58bcf-0ab6-4919-855b-2881fc9fa129",
      "name": "Supabase Schema Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        720
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "content": "\u26a0\ufe0f Table schema\nmembers: member_name, phone_number, membership_expiry (date), last_checkin_date (date), current_streak_days (int), last_milestone_notified (int, default 0)."
      },
      "typeVersion": 1
    },
    {
      "id": "d859e661-f5b2-456f-9fd4-4ad841840fc4",
      "name": "Section - Three Parallel Checks",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1984,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 1228,
        "height": 900,
        "content": "## 4\ufe0f\u20e3 Three Independent Checks\nEach member is checked against all three conditions. A member could be expiring soon AND inactive at the same time, both branches would fire independently."
      },
      "typeVersion": 1
    },
    {
      "id": "f3c20f07-0b17-4bee-ae49-3435c145950c",
      "name": "Set Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        1072,
        560
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-1",
              "name": "gym_name",
              "type": "string",
              "value": "REPLACE_WITH_GYM_NAME"
            },
            {
              "id": "cfg-2",
              "name": "whatsapp_phone_number_id",
              "type": "string",
              "value": "REPLACE_WITH_WHATSAPP_PHONE_NUMBER_ID"
            },
            {
              "id": "cfg-3",
              "name": "trainer_notify_number",
              "type": "string",
              "value": "REPLACE_WITH_TRAINER_WHATSAPP_NUMBER"
            },
            {
              "id": "cfg-4",
              "name": "supabase_table_members",
              "type": "string",
              "value": "members"
            },
            {
              "id": "cfg-5",
              "name": "renewal_reminder_days_before",
              "type": "number",
              "value": 3
            },
            {
              "id": "cfg-6",
              "name": "inactivity_threshold_days",
              "type": "number",
              "value": 3
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "3eba6661-065f-4379-ba74-0f9621d8200b",
      "name": "Fetch Members",
      "type": "n8n-nodes-base.supabase",
      "position": [
        1392,
        560
      ],
      "parameters": {
        "tableId": "={{ $('Set Configuration').item.json.supabase_table_members }}",
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0c1f2c24-c66c-4ce9-832a-e9db0da4a6c7",
      "name": "Compute Member Status",
      "type": "n8n-nodes-base.code",
      "position": [
        1648,
        560
      ],
      "parameters": {
        "jsCode": "const config = $('Set Configuration').first().json;\nconst now = new Date();\nconst milestones = [7, 30, 100];\n\nreturn $input.all().map(item => {\n  const m = item.json;\n  const expiry = new Date(m.membership_expiry);\n  const lastCheckin = new Date(m.last_checkin_date);\n\n  const daysUntilExpiry = Math.ceil((expiry - now) / (1000 * 60 * 60 * 24));\n  const daysSinceCheckin = Math.floor((now - lastCheckin) / (1000 * 60 * 60 * 24));\n  const streak = m.current_streak_days || 0;\n  const lastNotified = m.last_milestone_notified || 0;\n\n  const isExpiringSoon = daysUntilExpiry >= 0 && daysUntilExpiry <= config.renewal_reminder_days_before;\n  const isInactive = daysSinceCheckin >= config.inactivity_threshold_days;\n  const reachedMilestone = milestones.includes(streak) && streak > lastNotified;\n\n  return {\n    json: {\n      ...m,\n      days_until_expiry: daysUntilExpiry,\n      days_since_checkin: daysSinceCheckin,\n      is_expiring_soon: isExpiringSoon,\n      is_inactive: isInactive,\n      reached_milestone: reachedMilestone\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "42fd8c47-861e-4e81-8dad-be5c45b7df7e",
      "name": "Is Expiring Soon?",
      "type": "n8n-nodes-base.if",
      "position": [
        2080,
        448
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d9e7925b-f3a8-435a-a09e-7fae856f4c03",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.is_expiring_soon }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3094294e-9926-4807-8d7c-c067efb33582",
      "name": "Send Renewal Reminder",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2512,
        432
      ],
      "parameters": {
        "textBody": "=Hi {{ $json.member_name }}, your membership at {{ $('Set Configuration').item.json.gym_name }} expires in {{ $json.days_until_expiry }} day(s). Renew now to keep your streak going!",
        "operation": "send",
        "phoneNumberId": "={{ $('Set Configuration').item.json.whatsapp_phone_number_id }}",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $json.phone_number }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d53c715-95f2-43cc-ae24-345382680b25",
      "name": "Is Inactive?",
      "type": "n8n-nodes-base.if",
      "position": [
        2080,
        640
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c388ddcd-60c4-4fc2-a83e-11fb316dc77f",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.is_inactive }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d854132b-9587-460e-9001-708eed37d345",
      "name": "Notify Trainer - Member Going Cold",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2576,
        624
      ],
      "parameters": {
        "textBody": "=Heads up: {{ $json.member_name }} hasn't checked in for {{ $json.days_since_checkin }} days. Might be worth a personal check-in before they churn.",
        "operation": "send",
        "phoneNumberId": "={{ $('Set Configuration').item.json.whatsapp_phone_number_id }}",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Set Configuration').item.json.trainer_notify_number }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2e65fd00-692e-4ad4-804f-aff7adde88e5",
      "name": "Reached New Milestone?",
      "type": "n8n-nodes-base.if",
      "position": [
        2080,
        880
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "47407b8e-421b-4a49-af55-2ea064b21efb",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.reached_milestone }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6add7a92-5880-4ecf-9eee-1bc579383882",
      "name": "Send Streak Celebration",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2416,
        864
      ],
      "parameters": {
        "textBody": "=\ud83c\udf89 {{ $json.member_name }}, you just hit a {{ $json.current_streak_days }}-day check-in streak at {{ $('Set Configuration').item.json.gym_name }}! Keep it up.",
        "operation": "send",
        "phoneNumberId": "={{ $('Set Configuration').item.json.whatsapp_phone_number_id }}",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $json.phone_number }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "556e2b01-e054-4424-b663-d5f46b58339a",
      "name": "Update Milestone Notified",
      "type": "n8n-nodes-base.supabase",
      "position": [
        2736,
        864
      ],
      "parameters": {
        "tableId": "={{ $('Set Configuration').item.json.supabase_table_members }}",
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9afdcd14-a8d2-419a-b828-766a654fa0dc",
      "name": "Every Day",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        768,
        560
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "4a982318-4ec1-4e5d-b537-9ed4ee9d36ab",
  "nodeGroups": [],
  "connections": {
    "Every Day": {
      "main": [
        [
          {
            "node": "Set Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Inactive?": {
      "main": [
        [
          {
            "node": "Notify Trainer - Member Going Cold",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Members": {
      "main": [
        [
          {
            "node": "Compute Member Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Expiring Soon?": {
      "main": [
        [
          {
            "node": "Send Renewal Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Configuration": {
      "main": [
        [
          {
            "node": "Fetch Members",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute Member Status": {
      "main": [
        [
          {
            "node": "Is Expiring Soon?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Inactive?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Reached New Milestone?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reached New Milestone?": {
      "main": [
        [
          {
            "node": "Send Streak Celebration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Streak Celebration": {
      "main": [
        [
          {
            "node": "Update Milestone Notified",
            "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 workflow runs daily to pull gym members from Supabase, calculate membership expiry and check-in activity, and send WhatsApp messages for renewal reminders, trainer inactivity alerts, and check-in streak milestone celebrations. Runs every day on a schedule. Loads…

Source: https://n8n.io/workflows/17481/ — 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

This workflow runs every 15 minutes to find unprocessed clinic appointments in Supabase that are more than 30 minutes past their scheduled time and sends a WhatsApp Business template message to the pa

Supabase, WhatsApp
Slack & Telegram

This workflow runs daily to fetch pending student fee records from Supabase, filters fees due in three days, and sends WhatsApp Business template reminders via the Meta WhatsApp Cloud API, then update

WhatsApp, Supabase
Slack & Telegram

Wait Schedule. Uses spotify, supabase, compareDatasets, noOp. Scheduled trigger; 54 nodes.

Spotify, Supabase, YouTube +1
Slack & Telegram

This workflow is designed for **customer support teams, e-commerce founders, and operations managers** who want to handle thousands of customer queries seamlessly. Instead of building a brand-new chat

Telegram Trigger, Supabase, WhatsApp +2
Slack & Telegram

Regua-De-Cobrancas. Uses httpRequest, emailSend, whatsApp, twilio. Scheduled trigger; 25 nodes.

HTTP Request, Email Send, WhatsApp +2