AutomationFlowsSlack & Telegram › Send Whatsapp Appointment Reminders and Reschedule Bookings with Wati and…

Send Whatsapp Appointment Reminders and Reschedule Bookings with Wati and…

Original n8n title: Send Whatsapp Appointment Reminders and Reschedule Bookings with Wati and Google Sheets

ByJitesh Dugar @jiteshdugar on n8n.io

Streamline your clinic's operations with a fully automated patient communication system. This workflow manages the entire appointment lifecycle—from automated morning reminders to real-time confirmations and self-service rescheduling—all through WhatsApp using WATI and Google…

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

This workflow corresponds to n8n.io template #13697 — 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": "587768b6-db4d-4cb8-91f9-defc74f86a98",
      "name": "\ud83d\udccb Flow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1664,
        -464
      ],
      "parameters": {
        "width": 660,
        "height": 802,
        "content": "## \ud83c\udfe5 WhatsApp Appointment Reminder System with Rescheduling\n\n**How it works:**\n1. A scheduled trigger runs every morning and reads today's + tomorrow's appointments from Google Sheets\n2. Automated reminder messages are sent via WATI to each patient\n3. Patient replies *confirm*, *cancel* or *reschedule* directly on WhatsApp\n4. If rescheduling \u2192 bot shows available slots \u2192 patient picks one \u2192 Google Sheets updated\n5. Doctor/admin gets notified of any cancellations or reschedules\n6. Patient can also type *myappointment* anytime to check their booking\n\n**Credentials needed:** WATI, OpenAI API Key (Header Auth), Google Sheets OAuth2, Google Calendar OAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "4eb92018-6466-4079-9175-82b3965ff326",
      "name": "Sticky \u2013 Scheduled Reminders",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        896
      ],
      "parameters": {
        "color": 7,
        "width": 1264,
        "height": 386,
        "content": "### 1\ufe0f\u20e3 Scheduled Reminder Dispatch\n**Schedule Trigger** fires every morning at 9 AM.\n**Sheets \u2013 Read Appointments** fetches all appointments for today and tomorrow.\n**Filter Upcoming Code** keeps only confirmed/pending appointments within 24 hours.\n**Send Reminders Loop** sends a personalised reminder to each patient via WATI with confirm/cancel/reschedule options."
      },
      "typeVersion": 1
    },
    {
      "id": "0ffa61fa-5bc4-430a-b338-cfa73cfdb9a5",
      "name": "Sticky \u2013 Inbound Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -672,
        736
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 722,
        "content": "### 2\ufe0f\u20e3 Inbound Reply Routing\n**WATI Trigger** receives patient replies.\n**Route Message Switch** detects the reply keyword:\n- `confirm` \u2192 mark confirmed in Sheets + send confirmation\n- `cancel` \u2192 mark cancelled + notify admin\n- `reschedule` \u2192 show available slots\n- `myappointment` \u2192 show current booking details\n- `slot <number>` \u2192 book the selected slot\n- anything else \u2192 help message"
      },
      "typeVersion": 1
    },
    {
      "id": "cc1be22d-b584-4126-add9-8e7d74486051",
      "name": "Sticky \u2013 Confirm & Cancel",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 578,
        "content": "### 3\ufe0f\u20e3 Confirm & Cancel\n**Confirm Flow:** Reads patient's appointment \u2192 updates status to Confirmed in Sheets \u2192 updates Google Calendar event \u2192 sends warm confirmation message.\n**Cancel Flow:** Updates status to Cancelled \u2192 removes/updates Google Calendar event \u2192 notifies admin via WATI \u2192 sends cancellation confirmation to patient."
      },
      "typeVersion": 1
    },
    {
      "id": "f6b88221-1a95-47fc-bb8a-0aedcf2db192",
      "name": "Sticky \u2013 Reschedule",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        880
      ],
      "parameters": {
        "color": 7,
        "width": 1036,
        "height": 610,
        "content": "### 4\ufe0f\u20e3 Reschedule Flow\n**Show Slots Code** reads available time slots from Sheets (AvailableSlots tab), filters out already-booked ones and sends a numbered list to patient.\n**Slot Selection Code** parses *slot 2* reply, validates availability, updates Sheets with new datetime, updates Google Calendar event, sends new confirmation."
      },
      "typeVersion": 1
    },
    {
      "id": "6b67e5a5-2c05-4d16-b9ec-aee0d34585c5",
      "name": "Sticky \u2013 Status & Admin",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        1536
      ],
      "parameters": {
        "color": 7,
        "width": 852,
        "height": 340,
        "content": "### 5\ufe0f\u20e3 My Appointment & Admin Notify\n**My Appointment Flow:** Reads Sheets for this patient's phone \u2192 sends current booking details card.\n**Admin Notify:** On cancel or reschedule, sends a summary message to the admin/doctor's WhatsApp number configured in the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "3caddb03-b62c-40e1-b18c-c540ffa91ac5",
      "name": "Schedule Trigger \u2013 9AM Daily",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1696,
        1056
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b5cd7697-4ea0-4560-b04e-d27e72e934e5",
      "name": "Google Sheets \u2013 Read Appointments",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1936,
        1056
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "8c629765-fb0c-4f8b-8f79-0e6a89ae34d3",
      "name": "Filter Upcoming Appointments",
      "type": "n8n-nodes-base.code",
      "position": [
        2176,
        1056
      ],
      "parameters": {
        "jsCode": "// Filter Upcoming Appointments\n// Keeps only appointments scheduled for today or tomorrow\n// with status Pending or Confirmed (not already Cancelled)\n\nconst allRows = $input.all();\nconst now = new Date();\nconst todayStr = now.toISOString().split('T')[0];\n\nconst tomorrow = new Date(now);\ntomorrow.setDate(tomorrow.getDate() + 1);\nconst tomorrowStr = tomorrow.toISOString().split('T')[0];\n\nconst upcoming = allRows.filter(r => {\n  const apptDate = (r.json.appointmentDate || '').split('T')[0];\n  const status = (r.json.status || '').toLowerCase();\n  return (\n    (apptDate === todayStr || apptDate === tomorrowStr) &&\n    (status === 'pending' || status === 'confirmed')\n  );\n});\n\nif (upcoming.length === 0) {\n  return [{ json: { message: 'No upcoming appointments to remind', count: 0 } }];\n}\n\n// Return each appointment as a separate item for the loop\nreturn upcoming.map(r => ({ json: r.json }));"
      },
      "typeVersion": 2
    },
    {
      "id": "094b69e1-deaa-40f0-90a0-084f47620302",
      "name": "Build Reminder Message",
      "type": "n8n-nodes-base.code",
      "position": [
        2400,
        1056
      ],
      "parameters": {
        "jsCode": "// Build Personalised Reminder Message\n// Creates a WhatsApp reminder with appointment details\n// and action options for the patient\n\nconst appt = $json;\n\nconst patientName = $('Google Sheets \u2013 Read Appointments').first().json.patientName|| 'Patient';\nconst phone =  $('Google Sheets \u2013 Read Appointments').first().json.phone|| '';\nconst doctorName = $('Google Sheets \u2013 Read Appointments').first().json.doctorName || 'your doctor';\nconst appointmentDate = $('Google Sheets \u2013 Read Appointments').first().json.appointmentDate || '';\nconst appointmentTime = $('Google Sheets \u2013 Read Appointments').first().json.appointmentTime || '';\nconst clinicName = $('Google Sheets \u2013 Read Appointments').first().json.clinicName || 'our clinic';\nconst appointmentId =  $('Google Sheets \u2013 Read Appointments').first().json.appointmentId|| appt.rowIndex || '';\nconst speciality = $('Google Sheets \u2013 Read Appointments').first().json.speciality || '';\n\n// Format date nicely\nlet formattedDate = appointmentDate;\ntry {\n  const d = new Date(appointmentDate);\n  formattedDate = d.toLocaleDateString('en-IN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n} catch(e) {}\n\nconst isToday = appointmentDate.split('T')[0] === new Date().toISOString().split('T')[0];\nconst urgency = isToday ? '\ud83d\udd34 *TODAY*' : '\ud83d\udcc5 *TOMORROW*';\n\nconst lines = [\n  `\ud83d\udc4b Hello *${patientName}!*`,\n  '',\n  `\u23f0 *Appointment Reminder* ${urgency}`,\n  '',\n  `\ud83c\udfe5 *Clinic:* ${clinicName}`,\n  `\ud83d\udc68\u200d\u2695\ufe0f *Doctor:* ${doctorName}${speciality ? ` (${speciality})` : ''}`,\n  `\ud83d\udcc5 *Date:* ${formattedDate}`,\n  `\ud83d\udd50 *Time:* ${appointmentTime}`,\n  '',\n  '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501',\n  'Please reply with one of:',\n  '',\n  '\u2705 *confirm* \u2013 I will be there',\n  '\ud83d\udd01 *reschedule* \u2013 I need another time',\n  '\u274c *cancel* \u2013 I need to cancel',\n  '',\n  'Reply *myappointment* to see full details.'\n];\n\nreturn {\n  json: {\n    phone,\n    patientName,\n    appointmentId,\n    reminderMessage: lines.join('\\n')\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "1105af0a-7f00-4fad-91bd-90883c730cd6",
      "name": "Route Message",
      "type": "n8n-nodes-base.switch",
      "position": [
        -336,
        1024
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Confirm",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "1dff507b-1b33-4032-af35-569caaef849d",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "confirm"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Cancel",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "b7ad7026-f9a7-4acc-a711-829d0ef3ef1c",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "cancel"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Reschedule",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "22960e42-4f8e-4913-aeb9-4e6b00044e42",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "reschedule"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "My Appointment",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "5746e0f3-ac08-4aa0-ad12-82f5dea21fac",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "myappointment"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Slot Selection",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "4ffcf9fe-51e5-4c6e-afe4-7fe4aa0bdcc4",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.text.toLowerCase().trim() }}",
                    "rightValue": "slot "
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "d4537013-0a96-45cf-bedb-75e71ae772d4",
      "name": "Google Sheets \u2013 Read for Confirm",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        352,
        416
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "8f8451ad-4ccd-44e0-b16f-d5c94266df73",
      "name": "Process Confirmation",
      "type": "n8n-nodes-base.code",
      "position": [
        560,
        416
      ],
      "parameters": {
        "jsCode": "// Find & Confirm Appointment\n// Finds the next upcoming appointment for this phone number\n// Returns appointment details + confirmation message\n\nconst phone =$input.first().json.phone;\nconst patientName = $input.first().json.patientName || 'Patient';\nconst allRows = $input.all();\nconst now = new Date();\n\n// Find the soonest upcoming appointment for this phone\nconst myAppts = allRows\n  .filter(r => r.json.phone === phone && ['pending','confirmed'].includes((r.json.status||'').toLowerCase()))\n  .sort((a, b) => new Date(a.json.appointmentDate) - new Date(b.json.appointmentDate));\n\nif (myAppts.length === 0) {\n  return [{ json: {\n    phone,\n    confirmMessage: `\u26a0\ufe0f *No upcoming appointment found.*\\n\\nReply *myappointment* to check your bookings.`,\n    hasAppointment: false\n  }}];\n}\n\nconst appt = myAppts[0].json;\nlet formattedDate = appt.appointmentDate;\ntry {\n  formattedDate = new Date(appt.appointmentDate).toLocaleDateString('en-IN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n} catch(e) {}\n\nconst confirmLines = [\n  `\u2705 *Appointment Confirmed!*`,\n  '',\n  `Thank you, *${patientName}!* We have noted your confirmation.`,\n  '',\n  `\ud83c\udfe5 *Clinic:* ${appt.clinicName || 'Our Clinic'}`,\n  `\ud83d\udc68\u200d\u2695\ufe0f *Doctor:* ${appt.doctorName || 'Your Doctor'}`,\n  `\ud83d\udcc5 *Date:* ${formattedDate}`,\n  `\ud83d\udd50 *Time:* ${appt.appointmentTime}`,\n  '',\n  '\ud83d\udccd Please arrive 10 minutes early.',\n  '\ud83d\udcc4 Bring any previous reports or prescriptions.',\n  '',\n  'See you soon! \ud83d\ude0a'\n];\n\nreturn [{ json: {\n  phone,\n  appointmentId: appt.appointmentId || appt.rowIndex,\n  calendarEventId: appt.calendarEventId || '',\n  confirmMessage: confirmLines.join('\\n'),\n  hasAppointment: true,\n  appointmentDate: appt.appointmentDate,\n  appointmentTime: appt.appointmentTime,\n  doctorName: appt.doctorName,\n  patientName\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a70d35fe-7f7d-43fc-82fc-9cbec4db162e",
      "name": "Google Sheets \u2013 Update Status Confirmed",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        768,
        416
      ],
      "parameters": {
        "columns": {
          "value": {
            "phone": "={{ $('Google Sheets \u2013 Read for Confirm').item.json.phone }}",
            "status": "=confirm"
          },
          "schema": [
            {
              "id": "appointmentId",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "appointmentId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "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": "doctorName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "doctorName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "appointmentDate",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "appointmentDate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "appointmentTime",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "appointmentTime",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "clinicName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "clinicName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "speciality",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "speciality",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "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": [
            "phone"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "c47004c0-95f6-455c-a443-5fc3083a5f4f",
      "name": "Google Sheets \u2013 Read for Cancel",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        352,
        656
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "51cfff13-6e93-4013-a3dc-ba9534c867e8",
      "name": "Process Cancellation",
      "type": "n8n-nodes-base.code",
      "position": [
        608,
        656
      ],
      "parameters": {
        "jsCode": "// Process Cancellation\n// Finds appointment, builds cancel + admin notification messages\n\nconst phone =  $input.first().json.phone|| $('WATI Trigger').item.json.from;\nconst patientName = $input.first().json.patientName || 'Patient';\nconst allRows = $input.all();\n\nconst myAppts = allRows\n  .filter(r => r.json.phone === phone && ['pending','confirmed'].includes((r.json.status||'').toLowerCase()))\n  .sort((a, b) => new Date(a.json.appointmentDate) - new Date(b.json.appointmentDate));\n\nif (myAppts.length === 0) {\n  return [{ json: {\n    phone,\n    cancelMessage: `\u26a0\ufe0f *No upcoming appointment found to cancel.*`,\n    adminMessage: '',\n    hasAppointment: false\n  }}];\n}\n\nconst appt = myAppts[0].json;\nlet formattedDate = appt.appointmentDate;\ntry {\n  formattedDate = new Date(appt.appointmentDate).toLocaleDateString('en-IN', { weekday: 'long', month: 'long', day: 'numeric' });\n} catch(e) {}\n\nconst cancelLines = [\n  `\u274c *Appointment Cancelled*`,\n  '',\n  `We have cancelled your appointment, *${patientName}.*`,\n  '',\n  `\ud83d\uddd1\ufe0f *Cancelled:* ${appt.doctorName || 'Doctor'} on ${formattedDate} at ${appt.appointmentTime}`,\n  '',\n  'If you wish to rebook, send *courses* or contact us directly.',\n  'We hope to see you again soon! \ud83d\ude4f'\n];\n\nconst adminLines = [\n  `\ud83d\udea8 *Appointment Cancelled*`,\n  '',\n  `\ud83d\udc64 *Patient:* ${patientName}`,\n  `\ud83d\udcde *Phone:* ${phone}`,\n  `\ud83d\udc68\u200d\u2695\ufe0f *Doctor:* ${appt.doctorName || 'N/A'}`,\n  `\ud83d\udcc5 *Date:* ${formattedDate}`,\n  `\ud83d\udd50 *Time:* ${appt.appointmentTime}`,\n  '',\n  'Slot is now available for rebooking.'\n];\n\nreturn [{ json: {\n  phone,\n  patientName,\n  appointmentId: appt.appointmentId || '',\n  calendarEventId: appt.calendarEventId || '',\n  cancelMessage: cancelLines.join('\\n'),\n  adminMessage: adminLines.join('\\n'),\n  hasAppointment: True\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2712317e-a6c7-4c90-b239-0661081c7f44",
      "name": "Google Sheets \u2013 Update Status Cancelled",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        816,
        656
      ],
      "parameters": {
        "columns": {
          "value": {
            "phone": "={{ $json.phone }}"
          },
          "schema": [
            {
              "id": "appointmentId",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "appointmentId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "removed": false,
              "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": "doctorName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "doctorName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "appointmentDate",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "appointmentDate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "appointmentTime",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "appointmentTime",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "clinicName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "clinicName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "speciality",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "speciality",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "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": [
            "phone"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "c4af005b-b3c6-403c-a615-9374cac32c95",
      "name": "Google Sheets \u2013 Read Available Slots",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        368,
        1024
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 658324122,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=658324122",
          "cachedResultName": "Available Slots"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5a32891a-5e54-496d-8929-a13e6151ab0a",
      "name": "Build Available Slots",
      "type": "n8n-nodes-base.code",
      "position": [
        624,
        1024
      ],
      "parameters": {
        "jsCode": "// Build Available Slots Message\n// Reads AvailableSlots sheet, filters out booked slots\n// Sends a numbered list to the patient\n\nconst phone = $('Wati Trigger').first().json.waId || $('WATI Trigger').item.json.from;\nconst patientName =  $('Wati Trigger').first().json.senderName|| 'Patient';\nconst allSlots = $input.all();\n\n// Filter only available (not booked) slots in the future\nconst now = new Date();\nconst availableSlots = allSlots.filter(r => {\n  const slotDate = new Date(`${r.json.date} ${r.json.time}`);\n  return (r.json.status || '').toLowerCase() === 'available' && slotDate > now;\n}).slice(0, 8); // show max 8 slots\n\nif (availableSlots.length === 0) {\n  return [{ json: {\n    phone,\n    slotsMessage: `\u26a0\ufe0f *No available slots right now.*\\n\\nPlease call us directly to reschedule.\\n\ud83d\udcde Contact: YOUR_CLINIC_PHONE`,\n    slots: []\n  }}];\n}\n\nconst lines = [\n  `\ud83d\udd01 *Reschedule Appointment*`,\n  `Hi *${patientName}*, here are the available slots:`,\n  ''\n];\n\nconst slotsData = [];\nfor (let i = 0; i < availableSlots.length; i++) {\n  const s = availableSlots[i].json;\n  let formattedDate = s.date;\n  try {\n    formattedDate = new Date(s.date).toLocaleDateString('en-IN', { weekday: 'short', month: 'short', day: 'numeric' });\n  } catch(e) {}\n  lines.push(`*${i + 1}.* \ud83d\udcc5 ${formattedDate} at \ud83d\udd50 ${s.time} \u2014 Dr. ${s.doctorName || 'Available'}`);\n  slotsData.push({ index: i + 1, date: s.date, time: s.time, doctorName: s.doctorName, slotId: s.slotId || `${s.date}_${s.time}` });\n}\n\nlines.push('');\nlines.push('Reply *slot <number>* to confirm.');\nlines.push('Example: *slot 2*');\n\n// Store slots data as JSON string for next step\nreturn [{ json: {\n  phone,\n  patientName,\n  slotsMessage: lines.join('\\n'),\n  slotsData: JSON.stringify(slotsData)\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "0dc22898-bdf1-4bee-9a16-243edc5c9422",
      "name": "Google Sheets \u2013 Save Reschedule Session",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        880,
        1024
      ],
      "parameters": {
        "columns": {
          "value": {
            "phone": "={{ $json.phone }}",
            "createdAt": "={{ new Date().toISOString() }}"
          },
          "schema": [
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Slotsdate",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Slotsdate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "createdAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "createdAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 241863058,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=241863058",
          "cachedResultName": "RescheduleSessions"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "d0c8c7c1-a871-4553-a6f5-d312617962b5",
      "name": "Google Sheets \u2013 Read Reschedule Session",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        384,
        1280
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 241863058,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=241863058",
          "cachedResultName": "RescheduleSessions"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "987860c3-237a-49d6-84b6-9ac8d70fb921",
      "name": "Process Slot Selection",
      "type": "n8n-nodes-base.code",
      "position": [
        624,
        1280
      ],
      "parameters": {
        "jsCode": "// Process Slot Selection\n// Input: 'slot 2'\n// Reads saved session, finds the slot, updates appointment\n\nconst text = ($('WATI Trigger').item.json.text || '').trim();\nconst phone = $('WATI Trigger').item.json.waId || $('WATI Trigger').item.json.from;\nconst patientName = $('WATI Trigger').item.json.senderName || 'Patient';\n\n// Parse slot number\nconst slotNum = parseInt(text.replace(/^slot\\s+/i, '').trim());\nif (isNaN(slotNum) || slotNum < 1) {\n  return [{ json: {\n    phone,\n    slotMessage: `\u26a0\ufe0f Invalid slot number. Reply *reschedule* to see available slots again.`,\n    success: False\n  }}];\n}\n\n// Find the most recent session for this phone\nconst allSessions = $input.all();\nconst mySessions = allSessions\n  .filter(r => r.json.phone === phone)\n  .sort((a, b) => new Date(b.json.createdAt) - new Date(a.json.createdAt));\n\nif (mySessions.length === 0) {\n  return [{ json: {\n    phone,\n    slotMessage: `\u26a0\ufe0f No active reschedule session found.\\nSend *reschedule* to start again.`,\n    success: False\n  }}];\n}\n\nlet slots = [];\ntry {\n  slots = JSON.parse(mySessions[0].json.slotsData || '[]');\n} catch(e) { slots = []; }\n\nconst selected = slots.find(s => s.index === slotNum);\nif (!selected) {\n  return [{ json: {\n    phone,\n    slotMessage: `\u26a0\ufe0f Slot *${slotNum}* not found. Send *reschedule* to see the list again.`,\n    success: False\n  }}];\n}\n\nlet formattedDate = selected.date;\ntry {\n  formattedDate = new Date(selected.date).toLocaleDateString('en-IN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n} catch(e) {}\n\nconst confirmLines = [\n  `\u2705 *Appointment Rescheduled!*`,\n  '',\n  `Great choice, *${patientName}!* Your appointment has been updated.`,\n  '',\n  `\ud83d\udc68\u200d\u2695\ufe0f *Doctor:* Dr. ${selected.doctorName || 'Available'}`,\n  `\ud83d\udcc5 *New Date:* ${formattedDate}`,\n  `\ud83d\udd50 *New Time:* ${selected.time}`,\n  '',\n  '\ud83d\udccd Please arrive 10 minutes early.',\n  'Reply *myappointment* to verify your booking.',\n  '',\n  'See you soon! \ud83d\ude0a'\n];\n\nconst adminLines = [\n  `\ud83d\udd01 *Appointment Rescheduled*`,\n  `\ud83d\udc64 *Patient:* ${patientName}`,\n  `\ud83d\udcde *Phone:* ${phone}`,\n  `\ud83d\udcc5 *New Date:* ${formattedDate}`,\n  `\ud83d\udd50 *New Time:* ${selected.time}`,\n  `\ud83d\udc68\u200d\u2695\ufe0f *Doctor:* Dr. ${selected.doctorName || 'N/A'}`\n];\n\nreturn [{ json: {\n  phone, patientName,\n  newDate: selected.date,\n  newTime: selected.time,\n  newDoctorName: selected.doctorName,\n  slotId: selected.slotId,\n  slotMessage: confirmLines.join('\\n'),\n  adminMessage: adminLines.join('\\n'),\n  success: True\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2f67337e-a68c-4c41-87f1-74015c5db23b",
      "name": "Google Sheets \u2013 Update Rescheduled",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        880,
        1280
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "Rescheduled",
            "doctorName": "={{ $json.newDoctorName }}",
            "appointmentDate": "={{ $json.newDate }}",
            "appointmentTime": "={{ $json.newTime }}"
          },
          "schema": [
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "required": true,
              "displayName": "phone",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "appointmentDate",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "appointmentDate",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "appointmentTime",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "appointmentTime",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "doctorName",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "doctorName",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "phone"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "89deab9b-54fa-4723-a57d-6fdfed6db894",
      "name": "Google Sheets \u2013 Read My Appointment",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        368,
        1680
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit#gid=0",
          "cachedResultName": "Appointments"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1u_tiPHcDwuI-zCQr7kyLMYgUqvFudIkU2zQSXjoieKU/edit?usp=drivesdk",
          "cachedResultName": "Wati - Appointments"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "db98d5c0-914a-48c7-82cf-6dd385b4c519",
      "name": "Build My Appointment Card",
      "type": "n8n-nodes-base.code",
      "position": [
        624,
        1680
      ],
      "parameters": {
        "jsCode": "// Build My Appointment Status Card\n// Shows patient's current and past appointments\n\nconst phone =  $input.first().json.phone|| $('WATI Trigger').item.json.from;\nconst patientName =  $input.first().json.patientName|| 'Patient';\nconst allRows = $input.all();\n\nconst myAppts = allRows\n  .filter(r => r.json.phone === phone)\n  .sort((a, b) => new Date(b.json.appointmentDate) - new Date(a.json.appointmentDate));\n\nif (myAppts.length === 0) {\n  return [{ json: {\n    phone,\n    myApptMessage: `\ud83d\udccb *No appointments found, ${patientName}!*\\n\\nPlease contact us to book an appointment.`\n  }}];\n}\n\nconst upcoming = myAppts.filter(r => ['pending','confirmed','rescheduled'].includes((r.json.status||'').toLowerCase()));\nconst past = myAppts.filter(r => ['cancelled','completed'].includes((r.json.status||'').toLowerCase())).slice(0, 3);\n\nconst lines = [`\ud83d\udccb *My Appointments*`, `\ud83d\udc64 *${patientName}*`, ''];\n\nconst statusEmoji = { confirmed: '\u2705', pending: '\u23f3', rescheduled: '\ud83d\udd01', cancelled: '\u274c', completed: '\ud83c\udfc1' };\n\nif (upcoming.length > 0) {\n  lines.push('*Upcoming:*');\n  for (const r of upcoming) {\n    let fd = r.json.appointmentDate;\n    try { fd = new Date(r.json.appointmentDate).toLocaleDateString('en-IN', { weekday: 'short', month: 'short', day: 'numeric' }); } catch(e) {}\n    const emoji = statusEmoji[(r.json.status||'').toLowerCase()] || '\ud83d\udcc5';\n    lines.push(`${emoji} *${fd}* at ${r.json.appointmentTime}`);\n    lines.push(`   Dr. ${r.json.doctorName || 'N/A'} | ${r.json.clinicName || 'Clinic'}`);\n    lines.push(`   Status: ${r.json.status}`);\n    lines.push('');\n  }\n}\n\nif (past.length > 0) {\n  lines.push('*Recent History:*');\n  for (const r of past) {\n    let fd = r.json.appointmentDate;\n    try { fd = new Date(r.json.appointmentDate).toLocaleDateString('en-IN', { month: 'short', day: 'numeric' }); } catch(e) {}\n    const emoji = statusEmoji[(r.json.status||'').toLowerCase()] || '\ud83d\udcc5';\n    lines.push(`${emoji} ${fd} \u2013 Dr. ${r.json.doctorName || 'N/A'} (${r.json.status})`);\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 *confirm*, *reschedule* or *cancel* to manage your appointment.');\n\nreturn [{ json: { phone, myApptMessage: lines.join('\\n') } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "43a7f4fd-46da-4cde-aa75-6f07167ac5f3",
      "name": "Send a text message",
      "type": "n8n-nodes-wati.wati",
      "position": [
        976,
        416
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $('Process Confirmation').item.json.confirmMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1394c240-f6f6-47cb-99d2-1fc1504c2939",
      "name": "Send a text message1",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1024,
        656
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $('Process Cancellation').item.json.cancelMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "04fd6678-77d2-40c9-9e24-b15112120022",
      "name": "Send a text message2",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1088,
        1024
      ],
      "parameters": {
        "target": "={{ $json.phone }}",
        "messageText": "={{ $('Build Available Slots').item.json.slotsMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a7036637-cf66-4a16-8975-aa8261d87770",
      "name": "Send a text message3",
      "type": "n8n-nodes-wati.wati",
      "position": [
        1104,
        1280
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "=hi"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1489cd18-a11e-4ecc-b79c-a72b5a26d182",
      "name": "Send a text message4",
      "type": "n8n-nodes-wati.wati",
      "position": [
        848,
        1680
      ],
      "parameters": {
        "target": "={{ $('Wati Trigger').item.json.waId }}",
        "messageText": "={{ $json.myApptMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "561443ba-02e2-4019-9c59-44db9af5ea9b",
      "name": "Wati Trigger",
      "type": "n8n-nodes-wati.watiTrigger",
      "position": [
        -608,
        1088
      ],
      "parameters": {
        "event": "messageReceived"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "2c1273ed-a893-437c-ad1c-452583ba9849",
      "name": "Send a text message5",
      "type": "n8n-nodes-wati.wati",
      "position": [
        2608,
        1056
      ],
      "parameters": {
        "target": "917024935915",
        "messageText": "={{ $json.reminderMessage }}"
      },
      "credentials": {
        "watiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Wati Trigger": {
      "main": [
        [
          {
            "node": "Route Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Message": {
      "main": [
        [
          {
            "node": "Google Sheets \u2013 Read for Confirm",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Sheets \u2013 Read for Cancel",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Sheets \u2013 Read Available Slots",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Sheets \u2013 Read My Appointment",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Sheets \u2013 Read Reschedule Session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Cancellation": {
      "main": [
        [
          {
            "node": "Google Sheets \u2013 Update Status Cancelled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Confirmation": {
      "main": [
        [
          {
            "node": "Google Sheets \u2013 Update Status Confirmed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Available Slots": {
      "main": [
        [
          {
            "node": "Google Sheets \u2013 Save Reschedule Session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Reminder Message": {
      "main": [
        [
          {
            "node": "Send a text message5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Slot Selection": {
      "main": [
        [
          {
            "node": "Google Sheets \u2013 Update Rescheduled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build My Appointment Card": {
      "main": [
        [
          {
            "node": "Send a text message4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Upcoming Appointments": {
      "main": [
        [
          {
            "node": "Build Reminder Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger \u2013 9AM Daily": {
      "main": [
        [
          {
            "node": "Google Sheets \u2013 Read Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Read for Cancel": {
      "main": [
        [
          {
            "node": "Process Cancellation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Read for Confirm": {
      "main": [
        [
          {
            "node": "Process Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Read Appointments": {
      "main": [
        [
          {
            "node": "Filter Upcoming Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Update Rescheduled": {
      "main": [
        [
          {
            "node": "Send a text message3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Read My Appointment": {
      "main": [
        [
          {
            "node": "Build My Appointment Card",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Read Available Slots": {
      "main": [
        [
          {
            "node": "Build Available Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Read Reschedule Session": {
      "main": [
        [
          {
            "node": "Process Slot Selection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Save Reschedule Session": {
      "main": [
        [
          {
            "node": "Send a text message2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Update Status Cancelled": {
      "main": [
        [
          {
            "node": "Send a text message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets \u2013 Update Status Confirmed": {
      "main": [
        [
          {
            "node": "Send a text message",
            "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

Streamline your clinic's operations with a fully automated patient communication system. This workflow manages the entire appointment lifecycle—from automated morning reminders to real-time confirmations and self-service rescheduling—all through WhatsApp using WATI and Google…

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

Streamline your automotive service center's operations with this comprehensive automation. This workflow manages the entire customer lifecycle—from automated service reminders and instant appointment

Google Sheets, N8N Nodes Wati
Slack & Telegram

Elevate your shopping experience with an AI-driven personal assistant that lives right in your WhatsApp. This template automates the entire lifecycle of a shopping list—from intelligent intake and liv

HTTP Request, Google Sheets, N8N Nodes Wati
Slack & Telegram

This workflow continuously monitors the TikTok Ads Library for new creatives from specific advertisers or keyword searches, scrapes them via Apify, logs them into Google Sheets, and sends concise noti

Google Sheets, Slack, Telegram +1
Slack & Telegram

This workflow automates plant care reminders and records using Google Sheets, Telegram, and OpenWeather API.

Google Sheets, HTTP Request, Telegram
Slack & Telegram

Apollo Data Enrichment Using Company Id to automatically finds contacts for companies listed in your Google Sheet, enriches each person with emails and phone numbers via Apollo’s API, and writes verif

Google Sheets, HTTP Request, Error Trigger +1