AutomationFlowsData & Sheets › Fill Cancelled Appointments From a Waitlist with Google Sheets, Google…

Fill Cancelled Appointments From a Waitlist with Google Sheets, Google…

Original n8n title: Fill Cancelled Appointments From a Waitlist with Google Sheets, Google Calendar and Twilio

ByMD Faizan Uzzaman @buildwithfaizan on n8n.io

This n8n workflow watches a Google Sheets appointments list for cancellations, then texts matching waitlist candidates via Twilio to claim the open slot within 20 minutes and, on confirmation, books the appointment in Google Calendar and updates both Google Sheets tabs. Monitors…

Event trigger★★★★☆ complexity26 nodesGoogle Sheets TriggerGoogle SheetsTwilioGoogle Calendar
Data & Sheets Trigger: Event Nodes: 26 Complexity: ★★★★☆ Added:
Fill Cancelled Appointments From a Waitlist with Google Sheets, Google… — n8n workflow card showing Google Sheets Trigger, Google Sheets, Twilio integration

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

This workflow follows the Google Calendar → Google Sheets recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "8sVx88tgELHkbDsh",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true,
    "templateCredsSetupCompleted": true
  },
  "name": "Waitlist Auto-Fill on Cancellation",
  "tags": [],
  "nodes": [
    {
      "id": "4967a398-9572-43c9-b2ed-8d568bd6e13d",
      "name": "Appointment Cancelled Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "maxTries": 3,
      "position": [
        144,
        -128
      ],
      "parameters": {
        "event": "rowUpdate",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 5
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "waitBetweenTries": 1000
    },
    {
      "id": "acde8b74-882a-4b69-bf42-def4c9314a7d",
      "name": "Mark Waitlist Processing Started",
      "type": "n8n-nodes-base.googleSheets",
      "maxTries": 3,
      "position": [
        704,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "AppointmentID": "={{ $json.AppointmentID }}",
            "WaitlistProcessed": "Yes"
          },
          "schema": [
            {
              "id": "AppointmentID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "AppointmentID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PatientName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "PatientName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ServiceType",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ServiceType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Provider",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Provider",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Time",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WaitlistProcessed",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "WaitlistProcessed",
              "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": [
            "AppointmentID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7,
      "waitBetweenTries": 1000
    },
    {
      "id": "d92a216f-ff43-453a-b16e-3233e140b9e3",
      "name": "Get Waiting Candidates",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        912,
        -128
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "Waiting",
              "lookupColumn": "Status"
            },
            {
              "lookupValue": "={{ $('Appointment Cancelled Trigger').item.json.ServiceType }}",
              "lookupColumn": "ServiceType"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "331e424f-7151-4342-a260-971e723ae30b",
      "name": "Mark Slot Unfilled",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1360,
        112
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Cancelled - Unfilled",
            "AppointmentID": "={{ $('Appointment Cancelled Trigger').item.json.AppointmentID }}"
          },
          "schema": [
            {
              "id": "AppointmentID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "AppointmentID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PatientName",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "PatientName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ServiceType",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ServiceType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Provider",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Provider",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Time",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WaitlistProcessed",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "WaitlistProcessed",
              "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": [
            "AppointmentID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "cc3a7eee-f952-4841-b5d4-1b5b39861f97",
      "name": "Notify Owner - No Fill",
      "type": "n8n-nodes-base.twilio",
      "position": [
        1616,
        112
      ],
      "parameters": {
        "to": "YOUR_OWNER_PHONE_NUMBER",
        "from": "YOUR_TWILIO_FROM_NUMBER",
        "message": "=No one on the waitlist confirmed the open {{ $('Appointment Cancelled Trigger').item.json.ServiceType }} slot on {{ $('Format Date & Time').item.json.Date }}. It's still open.",
        "options": {}
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "38ec2ca4-e9b1-4dd5-897e-a26e52d97e2f",
      "name": "Mark Candidate Offered",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1360,
        -256
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Offered",
            "WaitlistID": "={{ $json.WaitlistID }}"
          },
          "schema": [
            {
              "id": "WaitlistID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "WaitlistID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ServiceType",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ServiceType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "AddedAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "AddedAt",
              "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": [
            "WaitlistID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "f4253a0d-5231-4149-94aa-9b1e25a3fe22",
      "name": "Send Waitlist Offer",
      "type": "n8n-nodes-base.twilio",
      "position": [
        1552,
        -256
      ],
      "parameters": {
        "to": "={{ $('Loop Over Waitlist Candidates').item.json.Phone }}",
        "from": "YOUR_TWILIO_FROM_NUMBER",
        "message": "=Hi {{ $('Loop Over Waitlist Candidates').item.json.Name }}! A {{ $('Appointment Cancelled Trigger').item.json.ServiceType }} slot opened on {{ $('Format Date & Time').item.json.Date }} at {{ $('Format Date & Time').item.json.Time }}. Tap to confirm within 20 min: {{ $execution.resumeUrl }}",
        "options": {}
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "6af9574a-a409-461c-93d2-1a8f23e275ac",
      "name": "Wait For Confirmation",
      "type": "n8n-nodes-base.wait",
      "position": [
        1760,
        -256
      ],
      "parameters": {
        "resume": "webhook",
        "options": {},
        "resumeUnit": "minutes",
        "resumeAmount": 20,
        "limitWaitTime": true
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "7d97f17b-7958-4a7a-8467-8b65190fed96",
      "name": "Candidate Confirmed?",
      "type": "n8n-nodes-base.if",
      "position": [
        1952,
        -256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "07e3700b-39e8-44c1-8f3c-34dd32238c82",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.query ? \"confirmed\" : \"timeout\" }}",
              "rightValue": "confirmed"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ae81862e-2341-49b3-8baf-9deef2e06a7e",
      "name": "Book Appointment in Calendar",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        2416,
        -400
      ],
      "parameters": {
        "end": "={{ $('Format Date & Time').item.json.EndDate }}T{{ $('Format Date & Time').item.json.EndTime }}",
        "start": "={{ $('Format Date & Time').item.json.Date }}T{{ $('Format Date & Time').item.json.Time }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "additionalFields": {
          "summary": "={{ $('Appointment Cancelled Trigger').item.json.ServiceType }} - {{ $('Loop Over Waitlist Candidates').item.json.Name }}",
          "description": "=Waitlist auto-fill booking. Phone: {{ $('Loop Over Waitlist Candidates').item.json.Phone }}",
          "sendUpdates": "none"
        }
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.3
    },
    {
      "id": "786286f3-06a6-47ef-b181-8b6487768c16",
      "name": "Add Booked Appointment Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2592,
        -400
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('Format Date & Time').item.json.Date }}",
            "Time": "={{ $('Format Date & Time').item.json.Time }}",
            "Phone": "={{ $('Get Waiting Candidates').item.json.Phone }}",
            "Status": "Filled - Waitlist",
            "Provider": "={{ $('Appointment Cancelled Trigger').item.json.Provider }}",
            "PatientName": "={{ $('Get Waiting Candidates').item.json.Name }}",
            "ServiceType": "={{ $('Get Waiting Candidates').item.json.ServiceType }}",
            "AppointmentID": "={{ $('Mark Waitlist Processing Started').item.json.AppointmentID }}-{{ $('Get Waiting Candidates').item.json.WaitlistID }}",
            "WaitlistProcessed": "No"
          },
          "schema": [
            {
              "id": "AppointmentID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "AppointmentID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PatientName",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "PatientName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ServiceType",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "ServiceType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Provider",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Provider",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Time",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "WaitlistProcessed",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "WaitlistProcessed",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        },
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "cfb620d9-2d0d-40cb-96d7-7cf69684af33",
      "name": "Update Waitlist - Booked",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2784,
        -400
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Booked",
            "WaitlistID": "={{ $('Get Waiting Candidates').item.json.WaitlistID }}"
          },
          "schema": [
            {
              "id": "WaitlistID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "WaitlistID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ServiceType",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ServiceType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "AddedAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "AddedAt",
              "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": [
            "WaitlistID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "f8eaa9e0-ebc6-47c4-ab3a-1a3e88582c49",
      "name": "Send Booking Confirmation",
      "type": "n8n-nodes-base.twilio",
      "position": [
        2960,
        -400
      ],
      "parameters": {
        "to": "={{ $('Loop Over Waitlist Candidates').item.json.Phone }}",
        "from": "YOUR_TWILIO_FROM_NUMBER",
        "message": "=You're confirmed! {{ $('Appointment Cancelled Trigger').item.json.ServiceType }} on {{ $('Format Date & Time').item.json.Date }} at {{ $('Format Date & Time').item.json.Time }}. See you then!",
        "options": {}
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "571e9b28-2b2a-4828-9365-30df65cc5312",
      "name": "Mark Candidate Expired",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2160,
        -128
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Expired",
            "WaitlistID": "={{ $('Get Waiting Candidates').item.json.WaitlistID }}"
          },
          "schema": [
            {
              "id": "WaitlistID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "WaitlistID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ServiceType",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "ServiceType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "AddedAt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "AddedAt",
              "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": [
            "WaitlistID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.7
    },
    {
      "id": "818a366f-1e97-4f2a-8479-2983184cef3d",
      "name": "Loop Over Waitlist Candidates",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1120,
        -128
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "ebf82576-5aa3-4f09-a2dd-81450a766d57",
      "name": "Cancelled & Not Yet Offered?",
      "type": "n8n-nodes-base.filter",
      "position": [
        512,
        -128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c116d30f-207a-4892-9758-ea4ed35d966e",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Cancelled"
            },
            {
              "id": "2e234015-36bd-40a6-9876-023a6b6bc49d",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.WaitlistProcessed }}",
              "rightValue": "Yes"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "22145734-ffd3-4cba-9eef-b91aed4d9cd4",
      "name": "Format Date & Time",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        -128
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item;\nconst serialDate = item.json.Date;\nconst serialTime = item.json.Time;\n\n// Google Sheets stores Date/Time cells as serial numbers (days since 1899-12-30).\nconst epoch = Date.UTC(1899, 11, 30);\nconst startMs = epoch + (serialDate + serialTime) * 86400000;\n\nconst APPOINTMENT_DURATION_MINUTES = 30;\nconst endMs = startMs + APPOINTMENT_DURATION_MINUTES * 60000;\n\nconst startDateObj = new Date(startMs);\nconst endDateObj = new Date(endMs);\n\nitem.json.Date = startDateObj.toISOString().slice(0, 10);\nitem.json.Time = startDateObj.toISOString().slice(11, 16);\nitem.json.EndDate = endDateObj.toISOString().slice(0, 10);\nitem.json.EndTime = endDateObj.toISOString().slice(11, 16);\n\nreturn item;"
      },
      "typeVersion": 2
    },
    {
      "id": "0f01da54-08b5-47e0-ba87-6203552b33f9",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2336,
        -176
      ],
      "parameters": {
        "color": 4,
        "width": 880,
        "height": 720,
        "content": "# \ud83c\udfa5 Video Demo\n\n## See the complete workflow in action:\n\n@[youtube](rAbg-GW0NwU)"
      },
      "typeVersion": 1
    },
    {
      "id": "e9f02100-5809-4682-9815-32964d322f4d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 352,
        "content": "## Detect valid cancellation\n\nStarts when the appointments sheet reports a cancellation, formats the slot date and time, and filters out records that are not eligible for a waitlist offer."
      },
      "typeVersion": 1
    },
    {
      "id": "ff09ec71-3e93-40e8-b80a-66b9cc98d8d9",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 352,
        "content": "## Prepare waitlist search\n\nMarks the cancelled slot as being processed, retrieves eligible waiting candidates from Google Sheets, and starts looping through them one at a time."
      },
      "typeVersion": 1
    },
    {
      "id": "c71de6d8-cc19-4470-a883-2e84e6b28a6b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 352,
        "content": "## Handle no available fill\n\nRuns when the loop exhausts candidates without a booking, updates the slot as unfilled, and sends a Twilio notification to the owner."
      },
      "typeVersion": 1
    },
    {
      "id": "79eb4c2f-c5b3-4eb5-be51-2ef4da1f00f2",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1312,
        -400
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 320,
        "content": "## Send candidate offer\n\nMarks the current waitlist candidate as offered, sends the open-slot offer by Twilio, and pauses while waiting for the candidate to respond."
      },
      "typeVersion": 1
    },
    {
      "id": "80d1614e-cab3-45ef-9cc5-b45214c106a1",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        -496
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 528,
        "content": "## Evaluate candidate reply\n\nChecks whether the candidate confirmed the offer. If not confirmed in time, the candidate is marked expired and routed back to the waitlist loop for the next person."
      },
      "typeVersion": 1
    },
    {
      "id": "7cf8e5cf-ee7b-4800-972e-8579200987ec",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2336,
        -576
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 352,
        "content": "## Create confirmed booking\n\nFor a confirming candidate, creates the appointment in Google Calendar and writes the booked appointment details back into Google Sheets."
      },
      "typeVersion": 1
    },
    {
      "id": "e548b9db-96b9-40b0-84fd-ebc2a1e4899e",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2720,
        -576
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 368,
        "content": "## Finalize booked candidate\n\nUpdates the waitlist entry to booked and sends the candidate a Twilio booking confirmation message."
      },
      "typeVersion": 1
    },
    {
      "id": "c5b8b1a7-de05-4c7c-9a4e-a4116ad7151a",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -496,
        -528
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Waitlist Auto-Fill on Cancellation\n\n### How it works\n\nThis workflow monitors a Google Sheet for cancelled appointments, validates that the slot has not already been offered, and prepares it for waitlist processing. It retrieves waiting candidates, offers the open slot one at a time via Twilio, waits for a reply, and either books the first confirming candidate or continues through the waitlist. If no one accepts, it marks the slot as unfilled and notifies the owner.\n\n### Setup steps\n\n- Connect Google Sheets credentials for the cancellation source sheet, waitlist sheet, and booked appointments sheet used by the workflow.\n- Connect Google Calendar credentials and select the calendar where replacement appointments should be created.\n- Connect Twilio credentials and configure the sender phone number plus recipient phone fields for candidates and owner notifications.\n- Verify the sheet columns used for appointment status, offered/processing flags, candidate contact details, appointment date/time, and waitlist status match the node mappings.\n- Configure the Wait node duration and confirmation-detection logic so candidate replies are evaluated within the intended response window.\n\n### Customization\n\nAdjust the waitlist ordering rules, SMS message templates, response timeout, and no-fill owner notification to match the business policy for filling cancelled appointments."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "timezone": "Asia/Kolkata",
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "timeSavedMode": "fixed",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "ff7c1edf-e931-43c6-a78f-0ff6444e089b",
  "nodeGroups": [],
  "connections": {
    "Format Date & Time": {
      "main": [
        [
          {
            "node": "Cancelled & Not Yet Offered?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Slot Unfilled": {
      "main": [
        [
          {
            "node": "Notify Owner - No Fill",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Waitlist Offer": {
      "main": [
        [
          {
            "node": "Wait For Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Candidate Confirmed?": {
      "main": [
        [
          {
            "node": "Book Appointment in Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Candidate Expired",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait For Confirmation": {
      "main": [
        [
          {
            "node": "Candidate Confirmed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Waiting Candidates": {
      "main": [
        [
          {
            "node": "Loop Over Waitlist Candidates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Candidate Expired": {
      "main": [
        [
          {
            "node": "Loop Over Waitlist Candidates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Candidate Offered": {
      "main": [
        [
          {
            "node": "Send Waitlist Offer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Waitlist - Booked": {
      "main": [
        [
          {
            "node": "Send Booking Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Booked Appointment Row": {
      "main": [
        [
          {
            "node": "Update Waitlist - Booked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Book Appointment in Calendar": {
      "main": [
        [
          {
            "node": "Add Booked Appointment Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cancelled & Not Yet Offered?": {
      "main": [
        [
          {
            "node": "Mark Waitlist Processing Started",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Appointment Cancelled Trigger": {
      "main": [
        [
          {
            "node": "Format Date & Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Waitlist Candidates": {
      "main": [
        [
          {
            "node": "Mark Slot Unfilled",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Candidate Offered",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Waitlist Processing Started": {
      "main": [
        [
          {
            "node": "Get Waiting Candidates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This n8n workflow watches a Google Sheets appointments list for cancellations, then texts matching waitlist candidates via Twilio to claim the open slot within 20 minutes and, on confirmation, books the appointment in Google Calendar and updates both Google Sheets tabs. Monitors…

Source: https://n8n.io/workflows/17957/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

[](https://youtu.be/6QKbzoWMaXE)

Google Sheets Trigger, Twilio, Google Sheets
Data & Sheets

This template is ideal for solo store owners, eCommerce marketers, automation beginners, or anyone using Shopify and Gmail who wants to recover lost revenue without coding.

HTTP Request, Gmail, Twilio +3
Data & Sheets

Stickynote Workflow. Uses googleTranslate, googleSheetsTrigger, googleDrive, httpRequest. Event-driven trigger; 22 nodes.

Google Translate, Google Sheets Trigger, Google Drive +2
Data & Sheets

This workflow is triggered by another n8n workflow to reschedule an existing appointment by looking up the current booking in Google Sheets, validating the requested new slot via a sub-workflow, and u

Google Sheets, Google Calendar, Execute Workflow Trigger
Data & Sheets

Automate IT asset allocation for new hires with an intelligent, AI-powered workflow 🤖. This automation reads employee data from Google Sheets, determines role-based requirements, and intelligently ass

Google Sheets Trigger, Airtable, HTTP Request +2