AutomationFlowsSlack & Telegram › Send Whatsapp Medication Reminders and Caregiver Alerts with Wati and Google…

Send Whatsapp Medication Reminders and Caregiver Alerts with Wati and Google…

Original n8n title: Send Whatsapp Medication Reminders and Caregiver Alerts with Wati and Google Sheets

ByJitesh Dugar @jiteshdugar on n8n.io

Ensure health and safety with a fully automated medication adherence system. This workflow manages the entire patient care cycle—from scheduled dosage reminders to interactive logging and automated caregiver escalations—all through WhatsApp using WATI and Google Sheets.

Cron / scheduled trigger★★★★★ complexity42 nodesGoogle SheetsN8N Nodes Wati
Slack & Telegram Trigger: Cron / scheduled Nodes: 42 Complexity: ★★★★★ Added:

This workflow corresponds to n8n.io template #13702 — 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "3527dde7-199d-40e0-a68c-f424177655db",
      "name": "\ud83d\udccb Flow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        0
      ],
      "parameters": {
        "width": 640,
        "height": 494,
        "content": "## \ud83d\udc8a WhatsApp Medication Reminder Bot with Dosage Tracking\n\n**How it works:**\n1. Schedule Trigger runs 3x daily (morning, afternoon, night) and sends medication reminders to all active patients\n2. Patient replies *taken*, *skip* or *snooze* to log their response\n3. If no response within 2 hours \u2192 caregiver is auto-alerted\n4. Patient can type *mystats* to see their weekly adherence report\n5. Patient types *mymeds* to see today's medication schedule\n6. Caregiver types *report <phone>* to get a patient's full adherence summary\n\n**Credentials needed:** WATI, Google Sheets OAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "03b70fab-0e5c-4e8d-b85b-04676595348c",
      "name": "Sticky \u2013 Scheduled Reminders",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2512,
        1312
      ],
      "parameters": {
        "color": 7,
        "width": 1456,
        "height": 338,
        "content": "### 1\ufe0f\u20e3 Scheduled Reminder Dispatch\n**Schedule Trigger** fires 3x daily: 8AM, 1PM, 8PM.\n**Sheets \u2013 Read Medications** fetches all active medication records.\n**Filter by Time Slot Code** keeps only medications due in the current time window (morning / afternoon / night).\n**Build & Send Reminder Loop** sends a personalised reminder per patient with taken/skip/snooze options."
      },
      "typeVersion": 1
    },
    {
      "id": "c532ecf6-0291-44f9-b9b0-a77b205418ff",
      "name": "Sticky \u2013 Inbound Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        1184
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 770,
        "content": "### 2\ufe0f\u20e3 Inbound Reply Routing\n**WATI Trigger** receives patient replies.\n**Route Message Switch** detects keyword:\n- `taken` \u2192 log dose taken \u2705\n- `skip` \u2192 log dose skipped \u26a0\ufe0f\n- `snooze` \u2192 schedule a 30-min follow-up reminder\n- `mystats` \u2192 weekly adherence report\n- `mymeds` \u2192 today's medication schedule\n- `report <phone>` \u2192 caregiver pulls patient report\n- anything else \u2192 help message"
      },
      "typeVersion": 1
    },
    {
      "id": "9cc5de58-5424-440d-bde2-17b4455a3e7e",
      "name": "Sticky \u2013 Taken & Skip",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        592
      ],
      "parameters": {
        "color": 7,
        "width": 1052,
        "height": 456,
        "content": "### 3\ufe0f\u20e3 Dose Logging \u2014 Taken & Skip\n**Taken Flow:** Logs dose as Taken in AdherenceLog sheet with timestamp. Sends positive reinforcement message. Updates streak counter.\n**Skip Flow:** Logs dose as Skipped. Sends empathetic reminder message. Flags for caregiver alert if 2+ consecutive skips detected."
      },
      "typeVersion": 1
    },
    {
      "id": "facc287f-8f74-4864-9773-86a300c827b2",
      "name": "Sticky \u2013 Snooze & Missed Alert",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        1072
      ],
      "parameters": {
        "color": 7,
        "width": 1052,
        "height": 584,
        "content": "### 4\ufe0f\u20e3 Snooze & Missed Dose Alert\n**Snooze Flow:** Logs snooze request with a follow-up timestamp. A second Schedule Trigger (every 30 min) checks for pending snoozes and re-sends reminders.\n**Missed Dose Check:** Runs hourly. If a reminder was sent but no response logged within 2 hours \u2192 sends alert to caregiver WhatsApp with patient name, medication and time."
      },
      "typeVersion": 1
    },
    {
      "id": "bc02854c-cd3c-4ea7-9ed7-6779edc23c8f",
      "name": "Sticky \u2013 Reports",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1360,
        1696
      ],
      "parameters": {
        "color": 7,
        "width": 1036,
        "height": 760,
        "content": "### 5\ufe0f\u20e3 Reports & Stats\n**My Stats:** Reads AdherenceLog for this patient's phone, calculates 7-day adherence %, streak, best/worst day and sends a visual report.\n**My Meds:** Shows today's full medication schedule with timings and dosage.\n**Caregiver Report:** Caregiver sends *report <phone>* \u2192 pulls full adherence history for that patient and sends summary."
      },
      "typeVersion": 1
    },
    {
      "id": "a51374ef-5515-4379-9a8a-f295007f8883",
      "name": "Schedule Trigger \u2013 8AM 1PM 8PM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        2592,
        1472
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8,13,20 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "97d52ce9-2530-4477-8830-3f4c3058d990",
      "name": "Google Sheets \u2013 Read Medications",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2832,
        1472
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=0",
          "cachedResultName": "Medications"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "36903a3a-8b63-44a4-9c84-a7c093efb05a",
      "name": "Filter Medications by Time Slot",
      "type": "n8n-nodes-base.code",
      "position": [
        3072,
        1472
      ],
      "parameters": {
        "jsCode": "// Filter Medications by Current Time Slot\n// Determines if it's morning (8AM), afternoon (1PM) or night (8PM)\n// and returns only medications scheduled for this slot\n\nconst allRows = $input.all();\nconst now = new Date();\nconst hour = now.getHours();\n\n// Determine current time slot\nlet currentSlot = '';\nif (hour >= 6 && hour < 12)  currentSlot = 'morning';\nelse if (hour >= 12 && hour < 17) currentSlot = 'afternoon';\nelse currentSlot = 'night';\n\nconst slotLabel = { morning: '\ud83c\udf05 Morning', afternoon: '\u2600\ufe0f Afternoon', night: '\ud83c\udf19 Night' }[currentSlot];\n\n// Filter active medications for this time slot\nconst dueMeds = allRows.filter(r => {\n  const status = (r.json.status || '').toLowerCase();\n  const frequency = (r.json.frequency || '').toLowerCase();\n  const timeSlots = (r.json.timeSlots || '').toLowerCase();\n\n  if (status !== 'active') return false;\n\n  // Check if this medication is due in current slot\n  if (timeSlots.includes(currentSlot)) return true;\n  if (frequency === 'once daily' && currentSlot === 'morning') return true;\n  if (frequency === 'twice daily' && (currentSlot === 'morning' || currentSlot === 'night')) return true;\n  if (frequency === 'thrice daily') return true;\n  return false;\n});\n\nif (dueMeds.length === 0) {\n  return [{ json: { message: `No medications due for ${currentSlot} slot`, count: 0 } }];\n}\n\n// Group by patient phone \u2014 one message per patient with all their meds\nconst byPhone = {};\nfor (const row of dueMeds) {\n  const phone = row.json.phone;\n  if (!byPhone[phone]) {\n    byPhone[phone] = {\n      phone,\n      patientName: row.json.patientName,\n      caregiverPhone: row.json.caregiverPhone,\n      medications: [],\n      currentSlot,\n      slotLabel\n    };\n  }\n  byPhone[phone].medications.push({\n    medName: row.json.medName,\n    dosage: row.json.dosage,\n    instructions: row.json.instructions || '',\n    medId: row.json.medId || row.json.medName\n  });\n}\n\nreturn Object.values(byPhone).map(p => ({ json: p }));"
      },
      "typeVersion": 2
    },
    {
      "id": "06817ea7-55d0-41ba-b619-813eaa12fdc2",
      "name": "Build Medication Reminder",
      "type": "n8n-nodes-base.code",
      "position": [
        3312,
        1472
      ],
      "parameters": {
        "jsCode": "// Build Medication Reminder Message\n// Creates a WhatsApp reminder listing all meds due for this slot\n\nconst data = $json;\nconst { phone, patientName, medications, slotLabel, currentSlot } = data;\nconst now = new Date();\nconst logKey = `${phone}_${now.toISOString().split('T')[0]}_${currentSlot}`;\n\nconst medLines = medications.map((m, i) =>\n  `  ${i + 1}. \ud83d\udc8a *${m.medName}* \u2014 ${m.dosage}${m.instructions ? `\\n     \ud83d\udcdd ${m.instructions}` : ''}`\n).join('\\n');\n\nconst lines = [\n  `${slotLabel} *Medication Reminder* \ud83d\udc8a`,\n  `\ud83d\udc4b Hi *${patientName}!*`,\n  '',\n  `Please take your ${currentSlot} medications:`,\n  '',\n  medLines,\n  '',\n  '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501',\n  'Reply with:',\n  '\u2705 *taken* \u2013 I have taken my medication',\n  '\ud83d\ude34 *snooze* \u2013 Remind me in 30 minutes',\n  '\u23ed\ufe0f *skip* \u2013 I am skipping this dose',\n];\n\nreturn {\n  json: {\n    phone,\n    patientName,\n    caregiverPhone: data.caregiverPhone,\n    currentSlot,\n    logKey,\n    reminderMessage: lines.join('\\n'),\n    sentAt: now.toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "52c8e202-57b7-459a-9bac-97703e11d89b",
      "name": "Google Sheets \u2013 Log Reminder Sent",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3552,
        1472
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ new Date().toISOString().split('T')[0] }}",
            "slot": "={{ $json.currentSlot }}",
            "phone": "={{ $json.phone }}",
            "logKey": "={{ $json.logKey }}",
            "sentAt": "={{ $json.sentAt }}",
            "status": "Pending",
            "patientName": "={{ $json.patientName }}",
            "caregiverPhone": "={{ $json.caregiverPhone }}"
          },
          "schema": [
            {
              "id": "logKey",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "logKey",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "patientName",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "patientName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "slot",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "slot",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sentAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "sentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "respondAt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "respondAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "caregiverPhone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "caregiverPhone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "8efb5ffd-6859-48da-adc0-cef89cfcaf07",
      "name": "Route Message",
      "type": "n8n-nodes-base.switch",
      "position": [
        336,
        1456
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Taken",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9ce57f31-b5ab-46dd-9f8e-2b4c7392c2b2",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "taken"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Skip",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "34cea88a-cf1a-4b8d-af85-b07cbc031f67",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "skip"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Snooze",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "c13d23e0-e6d3-4e0d-bddc-3e9372a76601",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "snooze"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "My Stats",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "499dc0f0-288f-4b63-bfbe-c1e623cebbf1",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "mystats"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "My Meds",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "3ffcee4c-5b30-47a1-8ca5-e5b8ae314fd0",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "mymeds"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Caregiver Report",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "62b48c7a-9d33-466d-aaec-c7c649a71d18",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    },
                    "leftValue": "={{ $json.text }}",
                    "rightValue": "report "
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "d3513e9b-8943-423f-8e7e-e6acce415e18",
      "name": "Google Sheets \u2013 Read Log (Taken)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1424,
        704
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "159c0619-095a-47df-881c-6225d32b55d2",
      "name": "Process Taken",
      "type": "n8n-nodes-base.code",
      "position": [
        1680,
        704
      ],
      "parameters": {
        "jsCode": "// Process Taken Response\n// Finds the latest Pending log for this patient\n// Calculates streak and builds positive reinforcement message\n\nconst phone = $('Wati Trigger').first().json.waId|| $('WATI Trigger').item.json.from;\nconst patientName =  $('Wati Trigger').first().json.senderName|| 'Patient';\nconst allRows = $input.all();\nconst now = new Date();\nconst today = now.toISOString().split('T')[0];\n\n// Find latest pending log for this phone\nconst myLogs = allRows\n  .filter(r => r.json.phone === phone)\n  .sort((a, b) => new Date(b.json.sentAt) - new Date(a.json.sentAt));\n\nconst pendingLog = myLogs.find(r => r.json.status === 'Pending');\n\n// Calculate current streak (consecutive taken days)\nconst takenDays = new Set(\n  allRows\n    .filter(r => r.json.phone === phone && r.json.status === 'Taken')\n    .map(r => r.json.date)\n);\n\nlet streak = 0;\nconst checkDate = new Date(today);\nwhile (takenDays.has(checkDate.toISOString().split('T')[0])) {\n  streak++;\n  checkDate.setDate(checkDate.getDate() - 1);\n}\n\n// Streak emoji\nlet streakMsg = '';\nif (streak >= 30) streakMsg = `\ud83c\udfc6 Incredible! ${streak}-day streak!`;\nelse if (streak >= 14) streakMsg = `\ud83c\udf1f Amazing! ${streak}-day streak!`;\nelse if (streak >= 7)  streakMsg = `\ud83d\udd25 On fire! ${streak}-day streak!`;\nelse if (streak >= 3)  streakMsg = `\ud83d\udcaa ${streak}-day streak! Keep going!`;\nelse streakMsg = `\u2705 Great job taking your medication!`;\n\nconst lines = [\n  `\u2705 *Dose Logged \u2013 Taken!*`,\n  '',\n  `Well done, *${patientName}!* \ud83c\udf89`,\n  streakMsg,\n  '',\n  `\ud83d\udcc5 *Date:* ${today}`,\n  `\ud83d\udd50 *Time:* ${now.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' })}`,\n  '',\n  'Consistency is the key to good health! \ud83d\udc9a',\n  'Reply *mystats* to see your full adherence report.'\n];\n\nreturn [{ json: {\n  phone,\n  patientName,\n  logKey: pendingLog?.json.logKey || `${phone}_${today}_unknown`,\n  takenMessage: lines.join('\\n'),\n  today,\n  respondedAt: now.toISOString()\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ce837757-ed51-43ec-804a-536288661a96",
      "name": "Google Sheets \u2013 Update Taken",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1920,
        704
      ],
      "parameters": {
        "columns": {
          "value": {
            "logKey": "={{ $json.logKey }}",
            "status": "Taken"
          },
          "schema": [
            {
              "id": "logKey",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "logKey",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "patientName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "patientName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "slot",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "slot",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sentAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "sentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "respondAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "respondAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "caregiverPhone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "caregiverPhone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "logKey"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "4196c4a3-032e-41b8-9378-46142d7ea711",
      "name": "Google Sheets \u2013 Read Log (Skip)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1424,
        880
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "2a2bd2d7-421b-4a78-aaa4-380416f9571b",
      "name": "Process Skip",
      "type": "n8n-nodes-base.code",
      "position": [
        1680,
        880
      ],
      "parameters": {
        "jsCode": "// Process Skip Response\n// Logs the skip, checks for consecutive skips\n// If 2+ consecutive skips \u2192 flag for caregiver alert\n\nconst phone = $('Wati Trigger').first().json.waId || $('WATI Trigger').item.json.from;\nconst patientName = $input.first().json.patientName || 'Patient';\nconst allRows = $input.all();\nconst now = new Date();\nconst today = now.toISOString().split('T')[0];\n\nconst myLogs = allRows\n  .filter(r => r.json.phone === phone)\n  .sort((a, b) => new Date(b.json.sentAt) - new Date(a.json.sentAt));\n\nconst pendingLog = myLogs.find(r => r.json.status === 'Pending');\ncaregiverPhone = $input.first().json.caregiverPhone || '';\n\n// Count consecutive recent skips\nconst recentLogs = myLogs.slice(0, 6);\nconst consecutiveSkips = recentLogs.filter(r => r.json.status === 'Skipped').length;\nconst alertCaregiver = consecutiveSkips >= 2;\n\nconst skipLines = [\n  `\u23ed\ufe0f *Dose Logged \u2013 Skipped*`,\n  '',\n  `We've noted your skip, *${patientName}.*`,\n  '',\n  '\u26a0\ufe0f Please consult your doctor before skipping medication regularly.',\n  '\ud83d\udca1 Tip: If you are experiencing side effects, contact your healthcare provider.',\n  '',\n  'Your next reminder will be sent on schedule.',\n  'Reply *mystats* to see your adherence report.'\n];\n\nconst caregiverLines = alertCaregiver ? [\n  `\ud83d\udea8 *Medication Skip Alert*`,\n  '',\n  `\ud83d\udc64 *Patient:* ${patientName}`,\n  `\ud83d\udcde *Phone:* ${phone}`,\n  `\u23ed\ufe0f *Consecutive Skips Detected:* ${consecutiveSkips + 1}`,\n  `\ud83d\udcc5 *Date:* ${today}`,\n  `\ud83d\udd50 *Time:* ${now.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' })}`,\n  '',\n  'Please check in with the patient.'\n] : [];\n\nreturn [{ json: {\n  phone,\n  patientName,\n  caregiverPhone,\n  logKey: $input.first().json.logKey || `${phone}_${today}_unknown`,\n  skipMessage: skipLines.join('\\n'),\n  caregiverMessage: caregiverLines.join('\\n'),\n  alertCaregiver,\n  respondedAt: now.toISOString()\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c79c62f4-38be-4cf9-964a-b3cc46d1b0a3",
      "name": "Google Sheets \u2013 Update Skipped",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1920,
        880
      ],
      "parameters": {
        "columns": {
          "value": {
            "logKey": "={{ $json.logKey }}",
            "status": "Skipped"
          },
          "schema": [
            {
              "id": "logKey",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "logKey",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "patientName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "patientName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "slot",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "slot",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sentAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "sentAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "respondAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "respondAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "caregiverPhone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "caregiverPhone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "logKey"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "7426d957-e5ad-4f49-8a62-8f8a0d237740",
      "name": "Process Snooze",
      "type": "n8n-nodes-base.code",
      "position": [
        1440,
        1200
      ],
      "parameters": {
        "jsCode": "// Process Snooze Request\n// Logs snooze with a follow-up time 30 minutes from now\n\nconst phone = $json.waId || $json.from || 'unknown';\nconst patientName = $json.senderName || 'Patient';\nconst now = new Date();\nconst followUpTime = new Date(now.getTime() + 30 * 60 * 1000);\n\nconst snoozeLines = [\n  `\ud83d\ude34 *Snooze Set!*`,\n  '',\n  `No problem, *${patientName}!*`,\n  `We'll remind you again at *${followUpTime.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' })}*. \u23f0`,\n  '',\n  '\ud83d\udc8a Please don\\'t forget your medication!'\n];\n\nreturn [{ json: {\n  phone,\n  patientName,\n  snoozeMessage: snoozeLines.join('\\n'),\n  followUpAt: followUpTime.toISOString(),\n  snoozedAt: now.toISOString()\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c048e9e0-6e80-4a2d-ac9b-6f21caa484e4",
      "name": "Google Sheets \u2013 Log Snooze",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1824,
        1200
      ],
      "parameters": {
        "columns": {
          "value": {
            "phone": "={{ $json.phone }}",
            "status": "Pending",
            "snoozedAt": "={{ $json.snoozedAt }}",
            "followUpAt": "={{ $json.followUpAt }}"
          },
          "schema": [
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "patientName ",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "patientName ",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "followUpAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "followUpAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "snoozedAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "snoozedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1864808001,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=1864808001",
          "cachedResultName": "SnoozeQueue"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "3980b984-eecf-405b-82a1-04ff9bd4cca0",
      "name": "Schedule Trigger \u2013 Every 30 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1440,
        1328
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f2bbcd28-3930-44b3-a776-b8703ab598a8",
      "name": "Google Sheets \u2013 Read Snooze Queue",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1664,
        1328
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1864808001,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=1864808001",
          "cachedResultName": "SnoozeQueue"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5bb3d95b-41ff-43e8-ab61-e6a78ce8fcbd",
      "name": "Check Snooze Queue",
      "type": "n8n-nodes-base.code",
      "position": [
        1936,
        1328
      ],
      "parameters": {
        "jsCode": "// Check Snooze Queue & Missed Doses\n// 1. Finds snooze entries where followUpAt <= now \u2192 resend reminder\n// 2. Finds AdherenceLog entries that are still Pending after 2 hours \u2192 missed dose\n\nconst allSnooze = $input.all();\nconst now = new Date();\n\n// Filter snooze entries due for follow-up\nconst dueSnoozes = allSnooze.filter(r => {\n  const followUpAt = new Date(r.json.followUpAt);\n  return r.json.status === 'Pending' && followUpAt <= now;\n});\n\nif (dueSnoozes.length === 0) {\n  return [{ json: { message: 'No snooze follow-ups due', count: 0, items: [] } }];\n}\n\nreturn dueSnoozes.map(r => ({\n  json: {\n    phone: r.json.phone,\n    patientName: r.json.patientName,\n    followUpMessage: `\u23f0 *Snooze Reminder!*\\n\\n\ud83d\udc8a Hi *${r.json.patientName}!* It\\'s time to take your medication now.\\n\\nReply *taken*, *skip* or *snooze* to log your response.`\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "4d942933-3d56-4f07-b32b-fe7a0fd1d8bc",
      "name": "Schedule Trigger \u2013 Every 1 Hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1456,
        1488
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "21749e76-c828-4c3c-9c56-4ea5888fee31",
      "name": "Google Sheets \u2013 Read Log (Missed Check)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1696,
        1488
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "20aaac89-0f54-4d6a-a99f-95ea10627376",
      "name": "Detect Missed Doses",
      "type": "n8n-nodes-base.code",
      "position": [
        1936,
        1488
      ],
      "parameters": {
        "jsCode": "// Detect Missed Doses\n// Finds AdherenceLog entries still 'Pending' after 2 hours\n// Groups by caregiver phone and builds alert messages\n\nconst allRows = $input.all();\nconst now = new Date();\nconst twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000);\n\nconst missedItems = allRows.filter(r => {\n  const sentAt = new Date(r.json.sentAt || 0);\n  return r.json.status === 'Pending' && sentAt <= twoHoursAgo;\n});\n\nif (missedItems.length === 0) {\n  return [{ json: { message: 'No missed doses detected', count: 0 } }];\n}\n\n// Group by caregiver so one message per caregiver\nconst byCg = {};\nfor (const row of missedItems) {\n  const cg = row.json.caregiverPhone;\n  if (!cg) continue;\n  if (!byCg[cg]) byCg[cg] = [];\n  byCg[cg].push(row.json);\n}\n\nreturn Object.entries(byCg).map(([cgPhone, items]) => {\n  const lines = [\n    `\ud83d\udea8 *Missed Dose Alert*`,\n    '',\n    `The following patient(s) have NOT responded to their medication reminder:`,\n    ''\n  ];\n  for (const item of items) {\n    lines.push(`\ud83d\udc64 *${item.patientName}* (${item.phone})`);\n    lines.push(`   \ud83d\udcc5 Date: ${item.date} | Slot: ${item.slot}`);\n    lines.push(`   \u23f0 Reminder sent at: ${new Date(item.sentAt).toLocaleTimeString('en-IN', {hour:'2-digit',minute:'2-digit'})}`);\n    lines.push('');\n  }\n  lines.push('Please follow up with the patient immediately.');\n  return { json: { caregiverPhone: cgPhone, alertMessage: lines.join('\\n') } };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "f64cea68-3edf-46bb-b220-4808627a2286",
      "name": "Google Sheets \u2013 Read Log (Stats)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1488,
        1904
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "59897745-4615-4578-8402-87ccd0b23609",
      "name": "Build My Stats",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        1904
      ],
      "parameters": {
        "jsCode": "// Build My Stats Report\n// 7-day adherence %, streak, best day, visual bar chart\n\nconst phone = $('Wati Trigger').first().json.waId || $('WATI Trigger').item.json.from;\nconst patientName = $('Wati Trigger').first().json.senderName || 'Patient';\nconst allRows = $input.all();\nconst now = new Date();\n\nconst myLogs = allRows.filter(r => r.json.phone === phone);\n\nif (myLogs.length === 0) {\n  return [{ json: { phone, statsMessage: `\ud83d\udcca No medication history found yet, *${patientName}!*\\n\\nStart taking your medications and reply *taken* after each dose.` } }];\n}\n\n// Last 7 days analysis\nconst last7 = [];\nfor (let i = 6; i >= 0; i--) {\n  const d = new Date(now);\n  d.setDate(d.getDate() - i);\n  const dateStr = d.toISOString().split('T')[0];\n  const dayLogs = myLogs.filter(r => r.json.date === dateStr);\n  const taken  = dayLogs.filter(r => r.json.status === 'Taken').length;\n  const total  = dayLogs.filter(r => r.json.status !== 'Pending').length;\n  const pct    = total > 0 ? Math.round((taken / total) * 100) : null;\n  last7.push({ dateStr, taken, total, pct, dayName: d.toLocaleDateString('en-IN', { weekday: 'short' }) });\n}\n\nconst totalTaken   = myLogs.filter(r => r.json.status === 'Taken').length;\nconst totalLogged  = myLogs.filter(r => r.json.status !== 'Pending').length;\nconst overallPct   = totalLogged > 0 ? Math.round((totalTaken / totalLogged) * 100) : 0;\n\n// Current streak\nlet streak = 0;\nconst sortedDates = [...new Set(myLogs.filter(r=>r.json.status==='Taken').map(r=>r.json.date))].sort().reverse();\nconst checkDate = new Date(now);\nfor (const d of sortedDates) {\n  if (d === checkDate.toISOString().split('T')[0]) {\n    streak++;\n    checkDate.setDate(checkDate.getDate() - 1);\n  } else break;\n}\n\n// Bar chart for last 7 days\nconst barLines = last7.map(d => {\n  if (d.pct === null) return `${d.dayName}: \u2500\u2500\u2500 No data`;\n  const filled = Math.round(d.pct / 10);\n  const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(10 - filled);\n  const emoji = d.pct === 100 ? '\u2705' : d.pct >= 50 ? '\u26a0\ufe0f' : '\u274c';\n  return `${d.dayName} ${bar} ${d.pct}% ${emoji}`;\n});\n\n// Overall bar\nconst overallFilled = Math.round(overallPct / 10);\nconst overallBar = '\u2588'.repeat(overallFilled) + '\u2591'.repeat(10 - overallFilled);\n\nconst lines = [\n  `\ud83d\udcca *Medication Adherence Report*`,\n  `\ud83d\udc64 *${patientName}*`,\n  '',\n  `${overallBar} *${overallPct}% Overall*`,\n  `\ud83d\udd25 *Current Streak:* ${streak} day${streak !== 1 ? 's' : ''}`,\n  '',\n  '\u2501\u2501 *Last 7 Days* \u2501\u2501',\n  ...barLines,\n  '',\n  `\u2705 Taken: ${totalTaken} doses`,\n  `\u23ed\ufe0f Skipped: ${myLogs.filter(r=>r.json.status==='Skipped').length} doses`,\n  `\u2753 Missed: ${myLogs.filter(r=>r.json.status==='Pending').length} doses`,\n  '',\n  'Keep it up! Consistency saves lives \ud83d\udc9a'\n];\n\nreturn [{ json: { phone, statsMessage: lines.join('\\n') } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "293849b3-4226-4f7b-9eb4-25f47689209d",
      "name": "Google Sheets \u2013 Read My Meds",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1488,
        2080
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=0",
          "cachedResultName": "Medications"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "6ab026e7-4305-47ad-aa40-6a2afaac2905",
      "name": "Build My Meds Schedule",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        2080
      ],
      "parameters": {
        "jsCode": "// Build My Medications Schedule\n// Shows today's full schedule with timings and dosage\n\nconst phone = $('Wati Trigger').first().json.waId|| $('WATI Trigger').item.json.from;\nconst patientName =$('Wati Trigger').first().json.senderName|| 'Patient';\nconst allRows = $input.all();\n\nconst myMeds = allRows.filter(r =>\n  r.json.phone === phone && (r.json.status || '').toLowerCase() === 'active'\n);\n\nif (myMeds.length === 0) {\n  return [{ json: { phone, myMedsMessage: `\ud83d\udc8a No active medications found, *${patientName}.*\\n\\nPlease contact your healthcare provider.` } }];\n}\n\nconst slotMap = { morning: '\ud83c\udf05 Morning (8:00 AM)', afternoon: '\u2600\ufe0f Afternoon (1:00 PM)', night: '\ud83c\udf19 Night (8:00 PM)' };\nconst grouped = { morning: [], afternoon: [], night: [] };\n\nfor (const row of myMeds) {\n  const slots = (row.json.timeSlots || 'morning').split(',').map(s => s.trim().toLowerCase());\n  for (const slot of slots) {\n    if (grouped[slot]) grouped[slot].push(row.json);\n  }\n}\n\nconst today = new Date().toLocaleDateString('en-IN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n\nconst lines = [\n  `\ud83d\udc8a *Today's Medication Schedule*`,\n  `\ud83d\udc64 *${patientName}*`,\n  `\ud83d\udcc5 ${today}`,\n  ''\n];\n\nfor (const [slot, meds] of Object.entries(grouped)) {\n  if (meds.length === 0) continue;\n  lines.push(slotMap[slot]);\n  for (const m of meds) {\n    lines.push(`  \u2022 *${m.medName}* \u2014 ${m.dosage}`);\n    if (m.instructions) lines.push(`    \ud83d\udcdd ${m.instructions}`);\n  }\n  lines.push('');\n}\n\nlines.push('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');\nlines.push('Reply *taken*, *skip* or *snooze* after each dose.');\nlines.push('Reply *mystats* for your adherence report.');\n\nreturn [{ json: { phone, myMedsMessage: lines.join('\\n') } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "eccbd185-7ba8-49df-aeba-ca654e375606",
      "name": "Google Sheets \u2013 Read Log (Caregiver)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1488,
        2240
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 816163013,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit#gid=816163013",
          "cachedResultName": "AdherenceLog"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IGGQCHH59cFU-4A_ERv5s-SD0o1rrXwEZ9-jQX049g4/edit?usp=drivesdk",
          "cachedResultName": "Untitled spreadsheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "52f2e40d-c1c9-4f7b-97af-182b0271d0ee",
      "name": "Build Caregiver Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        2240
      ],
      "parameters": {
        "jsCode": "// Build Caregiver Patient Report\n// Caregiver sends: 'report 919876543210'\n// Returns full adherence summary for that patient\n\nconst text = $('Wati Trigger').first().json.text||''.trim();\nconst caregiverPhone =  $input.first().json.caregiverPhone|| $('WATI Trigger').item.json.from;\nconst patientPhone = $('Wati Trigger').first().json.waId;\n\nif (!patientPhone) {\n  return [{ json: { phone: caregiverPhone, reportMessage: '\u26a0\ufe0f Please provide a patient phone number.\\nExample: *report 919876543210*' } }];\n}\n\nconst allRows = $input.all();\nconst patientLogs = allRows.filter(r => r.json.phone === patientPhone);\n\nif (patientLogs.length === 0) {\n  return [{ json: { phone: caregiverPhone, reportMessage: `\u274c No records found for *${patientPhone}*.` } }];\n}\n\nconst patientName = patientLogs[0].json.patientName || 'Patient';\nconst totalTaken   = patientLogs.filter(r => r.json.status === 'Taken').length;\nconst totalSkipped = patientLogs.filter(r => r.json.status === 'Skipped').length;\nconst totalMissed  = patientLogs.filter(r => r.json.status === 'Pending').length;\nconst totalLogged  = totalTaken + totalSkipped + totalMissed;\nconst adherencePct = totalLogged > 0 ? Math.round((totalTaken / totalLogged) * 100) : 0;\n\n// Last 7 days summary\nconst now = new Date();\nconst recentDays = [];\nfor (let i = 6; i >= 0; i--) {\n  const d = new Date(now);\n  d.setDate(d.getDate() - i);\n  const dateStr = d.toISOString().split('T')[0];\n  const dayLogs = patientLogs.filter(r => r.json.date === dateStr);\n  const t = dayLogs.filter(r => r.json.status === 'Taken').length;\n  const tot = dayLogs.filter(r => r.json.status !== 'Pending').length;\n  const pct = tot > 0 ? Math.round((t / tot) * 100) : null;\n  const dayName = d.toLocaleDateString('en-IN', { weekday: 'short', month: 'short', day: 'numeric' });\n  const emoji = pct === null ? '\u2500' : pct === 100 ? '\u2705' : pct >= 50 ? '\u26a0\ufe0f' : '\u274c';\n  recentDays.push(`${dayName}: ${pct !== null ? pct + '%' : 'No data'} ${emoji}`);\n}\n\nconst overallFilled = Math.round(adherencePct / 10);\nconst overallBar = '\u2588'.repeat(overallFilled) + '\u2591'.repeat(10 - overallFilled);\n\nconst lines = [\n  `\ud83d\udccb *Patient Adherence Report*`,\n  `\ud83d\udc64 *Patient:* ${patientName}`,\n  `\ud83d\udcde *Phone:* ${patientPhone}`,\n  '',\n  `${overallBar} *${adherencePct}% Overall*`,\n  '',\n  `\u2705 *Taken:* ${totalTaken} doses`,\n  `\u23ed\ufe0f *Skipped:* ${totalSkipped} doses`,\n  `\u2753 *Missed:* ${totalMissed} doses`,\n  `\ud83d\udce6 *Total Logged:* ${totalLogged} doses`,\n  '',\n  '\u2501\u2501 *Last 7 Days* \u2501\u2501',\n  ...recentDays,\n  '',\n  `Report generated: ${now.toLocaleDateString('en-IN')}`\n];\n\nreturn [{ json: { phone: caregiverPhone, reportMessage: lines.join('\\n') } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "f03f68e3-a5b4-4f3e-aa07-408a2df7e6b1",
      "name": "Send a text message",
      "type": "n8n-nodes-wati.wati",
      "position": [
        3776,
        1472
      ],
      "parameters": {
        "target": "=917024935915",
        "messageText": "={{ $('Build Medication Reminder').item.json.reminderMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "509e2d6b-3205-4d08-969c-0b840ddd0eae",
      "name": "Wati Trigger",
      "type": "n8n-nodes-wati.watiTrigger",
      "position": [
        96,
        1536
      ],
      "parameters": {
        "event": "messageReceived"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "fe701382-9087-4e7a-85a5-6d5acf534f1a",
      "name": "Send a text message1",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2160,
        704
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $('Process Taken').item.json.takenMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "be7b476e-74f8-4de5-80fb-787e126b52ac",
      "name": "Send a text message2",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2160,
        880
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $('Process Skip').item.json.skipMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "25d4311c-b26d-4dff-b8d9-0d4c781bf224",
      "name": "Send a text message3",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2096,
        1200
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $('Process Snooze').item.json.snoozeMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8f28a3c3-9446-4fed-a69d-effe23c84a71",
      "name": "Send a text message4",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2000,
        1904
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $json.statsMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "682c2e3a-49b2-4bc7-a856-c3e8f60f107f",
      "name": "Send a text message5",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2000,
        2080
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $json.myMedsMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0b95e745-e435-4a5f-883b-7346a20178e5",
      "name": "Send a text message6",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2000,
        2240
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",

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

Ensure health and safety with a fully automated medication adherence system. This workflow manages the entire patient care cycle—from scheduled dosage reminders to interactive logging and automated caregiver escalations—all through WhatsApp using WATI and Google Sheets.

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

⚠️ Heads up: this is satire. The "Hell Yeah!" workflow is a parody of "automate your whole life with AI agents" grindset content. The API endpoints are fictional and the function nodes are illustrativ

HTTP Request, Salesforce, Telegram +4
Slack & Telegram

This workflow continuously monitors the Meta Ads Library for new creatives from a specific competitor pages, logs them into Google Sheets, and sends a concise Telegram notification with the number of

HTTP Request, Telegram, Google Sheets +1
Slack & Telegram

Enhance financial oversight with this automated n8n workflow. Triggered every 5 minutes, it fetches real-time bank transactions via an API, enriches and transforms the data, and applies smart logic to

HTTP Request, Email Send, Google Sheets +1
Slack & Telegram

This workflow automates competitive price intelligence using Bright Data's enterprise web scraping API. On a scheduled basis (default: daily at 9 AM), the system loops through configured competitor pr

HTTP Request, Google Sheets, Slack +1
Slack & Telegram

Ensure your customer SLAs never slip with this n8n automation template. The workflow runs on a schedule, fetching open tickets from Zendesk, calculating SLA time remaining, and sending proactive alert

Zendesk, Slack, Google Sheets