{
  "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
          }
        ]
      ]
    }
  }
}