{
  "name": "Appointment reminders with one-tap confirmation",
  "nodes": [
    {
      "parameters": {
        "content": "## Appointment reminders with one-tap confirmation\n\nWhat you get: a WhatsApp reminder 24 hours before each Google Calendar appointment; the customer replies 1 to confirm or 2 to reschedule, and the calendar event is updated for you.\nWhat it replaces: reminder calls, no-shows, and reminders that keep going to people who already confirmed.\nSetup time: about 15 minutes (needs a Google Calendar credential).\n\nFewer no-shows, and you know by the morning who is coming.\n\nWorks with a normal WhatsApp number -- no Meta Business account needed.\n\nConvention: put the customer's phone number in the calendar event's description, on its own line, as `phone: +40712345678` (any format with a leading + and digits works -- non-digit characters are ignored when matching). Events without a `phone:` line are skipped.\n\nTwo entry points live in this one workflow. Branch A (top lane, Schedule Trigger) runs hourly, looks at events starting in roughly the next 24-48 hours, and reminds each one once -- it marks the event's description with `[reminded]` after sending, so re-running the hourly check never double-sends. Branch B (the lane below it) reacts to the customer's reply: \"1\" looks up their nearest upcoming event by phone number and marks it `[confirmed]`; \"2\" tells the customer you'll be in touch and pings the owner phone in Settings so a human can actually reschedule it (this template does not move the event itself).\n\nThe Google Calendar node parameters here are intentionally minimal (calendar, timeMin/timeMax or eventId, and a plain-text description update) to stay valid across small version differences -- if your n8n version renders the node differently, re-point \"calendar\" at your calendar and keep the description update.\n\nSetup:\n1. Install the community node: n8n Settings -> Community Nodes -> npm package `n8n-nodes-wasync`.\n2. Create a free WASync account at https://developers.wasync.app/register, connect a WhatsApp number, then create an API key at https://developers.wasync.app/keys.\n3. In n8n, add a new \"WASync API\" credential using that key.\n4. Add a Google Calendar credential in n8n (Credentials -> New -> Google Calendar account) named exactly \"Google Calendar account\" -- both Google Calendar nodes reference it by that name.\n5. Open both \"Settings\" nodes (one per branch) and fill in your own calendar ID, WASync connection id, and owner phone number. Never commit real portal URLs, tokens, or phone numbers.\n\nDocs: https://docs.wasync.app/docs/guides/n8n?utm_source=n8n&utm_medium=template&utm_campaign=t8",
        "height": 740,
        "width": 2200
      },
      "name": "Read Me First",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        -820
      ]
    },
    {
      "parameters": {
        "content": "**① Trigger — hourly run**\nRuns every hour and looks ~24-48 h ahead.",
        "height": 300,
        "width": 280,
        "color": 7
      },
      "name": "① Trigger — hourly run",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        404
      ]
    },
    {
      "parameters": {
        "content": "**② Find tomorrow's appointments**\nKeeps events with a phone: +… line in the description and\nskips the ones already marked [reminded].",
        "height": 300,
        "width": 600,
        "color": 7
      },
      "name": "② Find tomorrow's appointments",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        180,
        404
      ]
    },
    {
      "parameters": {
        "content": "**③ Remind and mark the event**\nSends the reminder, then writes [reminded] back onto the\ncalendar event so it is never sent twice.",
        "height": 300,
        "width": 600,
        "color": 7
      },
      "name": "③ Remind and mark the event",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        820,
        404
      ]
    },
    {
      "parameters": {
        "content": "**Settings — fill these in**\nPlaceholders only — replace every value here before you activate the\nworkflow. Nothing else in the workflow needs editing.\nFields: calendarId, wasyncConnectionId, ownerPhone.",
        "height": 344,
        "width": 600,
        "color": 7
      },
      "name": "Settings — fill these in",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        0
      ]
    },
    {
      "parameters": {
        "content": "**① Trigger — customer reply**\nFires when the customer answers 1 or 2.",
        "height": 522,
        "width": 280,
        "color": 7
      },
      "name": "① Trigger — customer reply",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        1228
      ]
    },
    {
      "parameters": {
        "content": "**② Read the reply**\n1 = confirm, and the event is looked up by phone number.\n2 = reschedule.",
        "height": 522,
        "width": 600,
        "color": 7
      },
      "name": "② Read the reply",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        180,
        1228
      ]
    },
    {
      "parameters": {
        "content": "**③ Update the calendar and answer**\nA 1 marks the event [confirmed] and thanks the customer.\nA 2 tells the customer you will be in touch and pings the\nowner phone from Settings.",
        "height": 522,
        "width": 1240,
        "color": 7
      },
      "name": "③ Update the calendar and answer",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        820,
        1228
      ]
    },
    {
      "parameters": {
        "content": "**Settings — fill these in**\nPlaceholders only — replace every value here before you activate the\nworkflow. Nothing else in the workflow needs editing.\nFields: calendarId, wasyncConnectionId, ownerPhone.",
        "height": 344,
        "width": 600,
        "color": 7
      },
      "name": "Settings — fill these in (2)",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        824
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 1
            }
          ]
        }
      },
      "name": "Run Every Hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        584
      ]
    },
    {
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "Settings (Reminders)-0",
              "name": "calendarId",
              "value": "primary",
              "type": "string"
            },
            {
              "id": "Settings (Reminders)-1",
              "name": "wasyncConnectionId",
              "value": "REPLACE_WITH_YOUR_WASYNC_CONNECTION_ID",
              "type": "string"
            },
            {
              "id": "Settings (Reminders)-2",
              "name": "ownerPhone",
              "value": "REPLACE_WITH_YOUR_OWNER_WHATSAPP_NUMBER",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "name": "Settings (Reminders)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        224
      ]
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Settings (Reminders)').item.json.calendarId }}"
        },
        "returnAll": true,
        "timeMin": "={{ $now.plus({ hours: 24 }).toISO() }}",
        "timeMax": "={{ $now.plus({ hours: 48 }).toISO() }}",
        "options": {}
      },
      "name": "List Events Due Tomorrow",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        320,
        584
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "Google Calendar account"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Keep events that have a customer phone in their description (\"phone: +...\") and have not already\n// been reminded (no \"[reminded]\" marker yet).\nconst phoneLine = /phone:\\s*(\\+?[0-9][0-9 ()-]{5,})/i;\n\nconst out = [];\nfor (const item of $input.all()) {\n  const e = item.json;\n  const description = e.description || '';\n  if (description.includes('[reminded]')) continue;\n  const match = description.match(phoneLine);\n  if (!match) continue;\n  const customerPhone = match[1].replace(/[^\\d+]/g, '');\n  out.push({ json: { ...e, customerPhone } });\n}\nreturn out;"
      },
      "name": "Filter Events with Phone",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        584
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "connectionId": "={{ $('Settings (Reminders)').item.json.wasyncConnectionId }}",
        "to": "={{ $json.customerPhone }}",
        "text": "={{ 'Reminder: ' + ($json.summary || 'your appointment') + ' tomorrow at ' + (($json.start && $json.start.dateTime) ? DateTime.fromISO($json.start.dateTime).toFormat('HH:mm') : '') + '. Reply 1 to confirm, 2 to reschedule.' }}",
        "additionalFields": {}
      },
      "name": "Send Reminder on WhatsApp",
      "type": "n8n-nodes-wasync.wasync",
      "typeVersion": 1,
      "position": [
        960,
        584
      ],
      "credentials": {
        "wasyncApi": {
          "name": "WASync account"
        }
      }
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "update",
        "eventId": "={{ $('Filter Events with Phone').item.json.id }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Settings (Reminders)').item.json.calendarId }}"
        },
        "updateFields": {
          "description": "={{ ($('Filter Events with Phone').item.json.description || '') + '\\n[reminded]' }}"
        }
      },
      "name": "Mark Event as Reminded",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        1280,
        584
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "Google Calendar account"
        }
      }
    },
    {
      "parameters": {
        "events": [
          "message.received"
        ]
      },
      "name": "Receive WhatsApp Message",
      "type": "n8n-nodes-wasync.wasyncTrigger",
      "typeVersion": 1,
      "position": [
        0,
        1430
      ],
      "credentials": {
        "wasyncApi": {
          "name": "WASync account"
        }
      }
    },
    {
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "Settings (Confirmations)-0",
              "name": "calendarId",
              "value": "primary",
              "type": "string"
            },
            {
              "id": "Settings (Confirmations)-1",
              "name": "wasyncConnectionId",
              "value": "REPLACE_WITH_YOUR_WASYNC_CONNECTION_ID",
              "type": "string"
            },
            {
              "id": "Settings (Confirmations)-2",
              "name": "ownerPhone",
              "value": "REPLACE_WITH_YOUR_OWNER_WHATSAPP_NUMBER",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "name": "Settings (Confirmations)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        1048
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "Reply Is 1?-cond0",
              "leftValue": "={{ $('Receive WhatsApp Message').item.json.message.text.trim() }}",
              "rightValue": "1",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "Reply Is 1 (Confirm)?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        320,
        1430
      ]
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Settings (Confirmations)').item.json.calendarId }}"
        },
        "returnAll": true,
        "timeMin": "={{ $now.toISO() }}",
        "timeMax": "={{ $now.plus({ days: 14 }).toISO() }}",
        "options": {}
      },
      "name": "List Upcoming Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        640,
        1430
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "Google Calendar account"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Find the upcoming event whose description carries this sender's phone number, so we know which\n// appointment they're confirming.\nfunction digitsOf(s) { return String(s || '').replace(/\\D/g, ''); }\n\nconst senderPhone = digitsOf($('Receive WhatsApp Message').item.json.message.from);\nconst phoneLine = /phone:\\s*(\\+?[0-9][0-9 ()-]{5,})/i;\n\nlet match = null;\nfor (const item of $input.all()) {\n  const e = item.json;\n  const description = e.description || '';\n  const m = description.match(phoneLine);\n  if (!m) continue;\n  if (digitsOf(m[1]) === senderPhone) { match = e; break; }\n}\n\nif (!match) return [{ json: { eventId: '', description: '', summary: '' } }];\nreturn [{ json: { eventId: match.id, description: match.description || '', summary: match.summary || '' } }];"
      },
      "name": "Match Event by Phone",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        1430
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "Event Found?-cond0",
              "leftValue": "={{ $json.eventId ? $json.eventId.length : 0 }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "Event Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1280,
        1430
      ]
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "update",
        "eventId": "={{ $json.eventId }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Settings (Confirmations)').item.json.calendarId }}"
        },
        "updateFields": {
          "description": "={{ ($json.description || '') + '\\n[confirmed]' }}"
        }
      },
      "name": "Mark Event as Confirmed",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        1600,
        1430
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "Google Calendar account"
        }
      }
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "connectionId": "={{ $('Settings (Confirmations)').item.json.wasyncConnectionId }}",
        "to": "={{ $('Receive WhatsApp Message').item.json.message.from }}",
        "text": "={{ 'Thanks for confirming! See you then.' }}",
        "additionalFields": {}
      },
      "name": "Send Thank You on WhatsApp",
      "type": "n8n-nodes-wasync.wasync",
      "typeVersion": 1,
      "position": [
        1920,
        1430
      ],
      "credentials": {
        "wasyncApi": {
          "name": "WASync account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "Reply Is 2?-cond0",
              "leftValue": "={{ $('Receive WhatsApp Message').item.json.message.text.trim() }}",
              "rightValue": "2",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "Reply Is 2 (Reschedule)?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        640,
        1630
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "connectionId": "={{ $('Settings (Confirmations)').item.json.wasyncConnectionId }}",
        "to": "={{ $('Receive WhatsApp Message').item.json.message.from }}",
        "text": "={{ \"We'll contact you to reschedule.\" }}",
        "additionalFields": {}
      },
      "name": "Send Reschedule Notice on WhatsApp",
      "type": "n8n-nodes-wasync.wasync",
      "typeVersion": 1,
      "position": [
        960,
        1630
      ],
      "credentials": {
        "wasyncApi": {
          "name": "WASync account"
        }
      }
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "connectionId": "={{ $('Settings (Confirmations)').item.json.wasyncConnectionId }}",
        "to": "={{ $('Settings (Confirmations)').item.json.ownerPhone }}",
        "text": "={{ 'Reschedule requested by ' + $('Receive WhatsApp Message').item.json.message.from + ' on WhatsApp.' }}",
        "additionalFields": {}
      },
      "name": "Notify Owner on WhatsApp",
      "type": "n8n-nodes-wasync.wasync",
      "typeVersion": 1,
      "position": [
        1280,
        1630
      ],
      "credentials": {
        "wasyncApi": {
          "name": "WASync account"
        }
      }
    }
  ],
  "connections": {
    "Run Every Hour": {
      "main": [
        [
          {
            "node": "Settings (Reminders)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings (Reminders)": {
      "main": [
        [
          {
            "node": "List Events Due Tomorrow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Events Due Tomorrow": {
      "main": [
        [
          {
            "node": "Filter Events with Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Events with Phone": {
      "main": [
        [
          {
            "node": "Send Reminder on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reminder on WhatsApp": {
      "main": [
        [
          {
            "node": "Mark Event as Reminded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive WhatsApp Message": {
      "main": [
        [
          {
            "node": "Settings (Confirmations)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings (Confirmations)": {
      "main": [
        [
          {
            "node": "Reply Is 1 (Confirm)?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply Is 1 (Confirm)?": {
      "main": [
        [
          {
            "node": "List Upcoming Events",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reply Is 2 (Reschedule)?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match Event by Phone": {
      "main": [
        [
          {
            "node": "Event Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Event Found?": {
      "main": [
        [
          {
            "node": "Mark Event as Confirmed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Thank You on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Event as Confirmed": {
      "main": [
        [
          {
            "node": "Send Thank You on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply Is 2 (Reschedule)?": {
      "main": [
        [
          {
            "node": "Send Reschedule Notice on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reschedule Notice on WhatsApp": {
      "main": [
        [
          {
            "node": "Notify Owner on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Upcoming Events": {
      "main": [
        [
          {
            "node": "Match Event by Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
