{
  "id": "hqQSZC40uc2RLEQR",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Appointment Reminder Sender",
  "tags": [],
  "nodes": [
    {
      "id": "f44f1348-3658-4945-b370-70de7e3e8f77",
      "name": "Every Morning at 9",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        2128,
        1584
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "abca920b-ed55-46b5-b957-93d1cf1e5b12",
      "name": "Get Appointments",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2416,
        1584
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo/edit?usp=drivesdk",
          "cachedResultName": "New Form_fashinbrand"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "2ef514e2-a4f5-4327-b1a4-dae39c124bfc",
      "name": "Filter For Tomorrow",
      "type": "n8n-nodes-base.code",
      "position": [
        2672,
        1584
      ],
      "parameters": {
        "jsCode": "const tomorrow = new Date();\ntomorrow.setDate(tomorrow.getDate() + 1);\nconst target = tomorrow.toISOString().slice(0, 10);\nreturn $input.all().filter(i => String(i.json['Date']).slice(0, 10) === target);"
      },
      "typeVersion": 2
    },
    {
      "id": "93f1d14c-00fa-4914-973b-52b034d8fb95",
      "name": "Filter Out Cancelled",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        1584
      ],
      "parameters": {
        "jsCode": "return $input.all().filter(i => String(i.json['Status'] || '').toLowerCase() !== 'cancelled');"
      },
      "typeVersion": 2
    },
    {
      "id": "be35c346-db7b-4049-9f05-1092fcc34538",
      "name": "Has Valid Email",
      "type": "n8n-nodes-base.if",
      "position": [
        3184,
        1584
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e1",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{$json['Email']}}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "3911748e-9bfc-48e0-95ee-d1a210b73ee0",
      "name": "Flag Missing Contact Info",
      "type": "n8n-nodes-base.set",
      "position": [
        3456,
        1632
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f1",
              "name": "issue",
              "type": "string",
              "value": "Missing email \u2014 call patient directly"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "68dc533e-3512-4b85-bf32-143816e434b8",
      "name": "Alert Front Desk Missing Info",
      "type": "n8n-nodes-base.slack",
      "position": [
        3712,
        1632
      ],
      "parameters": {
        "text": "No email on file for {{$json['Patient']}}'s appointment on {{$json['Date']}} at {{$json['Time']}}. Please call to confirm.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B1SAX5L3W",
          "cachedResultName": "new_ai-updates"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "fe664c3c-06cf-4ecb-be3a-b4d02bf60e11",
      "name": "Send Reminder Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3440,
        1440
      ],
      "parameters": {
        "sendTo": "={{$json['Email']}}",
        "message": "Hi {{$json['Patient']}}, this is a reminder of your appointment on {{$json['Date']}} at {{$json['Time']}}. Please reply if you need to reschedule.",
        "options": {},
        "subject": "Appointment reminder for {{$json['Date']}}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "426aeb36-e68b-4809-837b-3781bbbabf5b",
      "name": "Mark As Reminded",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3696,
        1440
      ],
      "parameters": {
        "columns": {
          "value": {
            "Email": "={{ $json['Email'] }}",
            "Status": "Reminded"
          },
          "schema": [
            {
              "id": "Email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Email"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo/edit?usp=drivesdk",
          "cachedResultName": "New Form_fashinbrand"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "7e213592-d57e-4672-86e8-cecc385b18f9",
      "name": "Build Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        3296,
        2320
      ],
      "parameters": {
        "jsCode": "const count = $input.all().length;\nreturn [{ json: { count, date: new Date(Date.now() + 86400000).toISOString().slice(0,10) } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "0becdb88-6b4d-4aaf-a846-2ab692462960",
      "name": "Front Desk Summary",
      "type": "n8n-nodes-base.slack",
      "position": [
        3552,
        2320
      ],
      "parameters": {
        "text": "Reminders sent for {{$json.count}} appointments scheduled on {{$json.date}}.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B1SAX5L3W",
          "cachedResultName": "new_ai-updates"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "30d7c1f1-cd7b-40aa-bf02-67083afade60",
      "name": "Two Hour Reminder Check",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        2128,
        2384
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f47caca1-b488-4eac-b0c4-5edf47294f2d",
      "name": "Get Today Appointments",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2416,
        2384
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/12lsTQ_B6G_39C0JwXZB_N-9Gk0STtrvZQ9bMAyX7ELo/edit?usp=drivesdk",
          "cachedResultName": "New Form_fashinbrand"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "4955a1d2-844d-4102-a451-e3dad7e49337",
      "name": "Filter Next Two Hours",
      "type": "n8n-nodes-base.code",
      "position": [
        2672,
        2384
      ],
      "parameters": {
        "jsCode": "const now = new Date();\nconst today = now.toISOString().slice(0, 10);\nreturn $input.all().filter(i => {\n  const status = String(i.json['Status'] || '').toLowerCase();\n  if (status === 'cancelled') return false;\n  if (String(i.json['Date']).slice(0, 10) !== today) return false;\n  const apptTime = new Date(`${today}T${String(i.json['Time']).padStart(5, '0')}:00`);\n  const diffMins = (apptTime.getTime() - now.getTime()) / 60000;\n  return diffMins > 0 && diffMins <= 120;\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "e4de2b86-c040-4b76-bc92-556a4f7426e3",
      "name": "Send Final Reminder",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2928,
        2384
      ],
      "parameters": {
        "sendTo": "={{$json['Email']}}",
        "message": "Hi {{$json['Patient']}}, your appointment starts soon today at {{$json['Time']}}. We look forward to seeing you.",
        "options": {},
        "subject": "Your appointment starts soon"
      },
      "typeVersion": 2.1
    },
    {
      "id": "c92b1633-89fa-42ba-ae93-7cdcbe8bf7bc",
      "name": "Workflow Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        3280,
        2576
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "a5bd6949-e07b-4fd2-9a1d-8e322edba442",
      "name": "Alert Front Desk on Workflow Failure",
      "type": "n8n-nodes-base.slack",
      "position": [
        3568,
        2576
      ],
      "parameters": {
        "text": "Appointment Reminder Sender workflow failed: {{$json.execution.error.message}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B1SAX5L3W",
          "cachedResultName": "new_ai-updates"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "57b74a56-2351-4d82-a969-b9e540dab024",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        1184
      ],
      "parameters": {
        "color": 7,
        "width": 580,
        "height": 1192,
        "content": "## \ud83e\ude7a Appointment Reminder Sender\n\nEvery morning it reads your appointment sheet, finds tomorrow's bookings, skips cancellations, emails each patient a reminder, flags anyone missing contact info for a phone call instead, and posts a summary to your front desk channel. A second check throughout the day sends a final reminder two hours before same-day appointments, and a Slack alert fires if anything in the workflow fails.\n\n**Perfect for:** clinics, dental practices and small healthcare offices.\n\n---\n\n## How it works\n\n1. **Every Morning at 9** \u2014 schedule trigger that runs once daily.\n2. **Get Appointments** \u2014 reads all rows from your appointments sheet.\n3. **Filter For Tomorrow** \u2014 keeps only appointments dated for the next day.\n4. **Filter Out Cancelled** \u2014 drops any appointment marked Cancelled.\n5. **Has Valid Email** \u2014 branches on whether the patient has an email on file.\n6. **Send Reminder Email** / **Mark As Reminded** \u2014 *(has email)* emails the reminder, then marks the row as reminded so a retry never double-sends.\n7. **Flag Missing Contact Info** / **Alert Front Desk Missing Info** \u2014 *(no email)* tells front desk to call this patient instead.\n8. **Build Summary** / **Front Desk Summary** \u2014 counts how many reminders went out and posts the total to your team channel.\n9. **Two Hour Reminder Check** \u2014 *(second touchpoint)* runs every 30 minutes through the day.\n10. **Get Today Appointments** / **Filter Next Two Hours** \u2014 finds today's appointments starting within the next two hours.\n11. **Send Final Reminder** \u2014 *(optional path)* sends a same-day, starting-soon reminder.\n12. **Workflow Error Trigger** / **Alert Front Desk on Workflow Failure** \u2014 *(error path)* posts to Slack if any step in this workflow throws an error.\n\n---\n\n## Setup (~15 min)\n\n1. **Google Sheets** \u2014 connect your account and sheet id in *Get Appointments*, *Mark As Reminded*, and *Get Today Appointments*. Use columns Patient, Email, Date, Time, and add a Status column for Cancelled/Reminded.\n2. **Gmail** \u2014 connect Gmail in *Send Reminder Email* and *Send Final Reminder* and adjust the wording.\n3. **Slack** \u2014 connect Slack and set the channel in *Front Desk Summary*, *Alert Front Desk Missing Info*, and *Alert Front Desk on Workflow Failure*.\n> Make sure your Date column uses a format the *Filter For Tomorrow* code can read, such as YYYY-MM-DD, and Time uses 24-hour HH:MM."
      },
      "typeVersion": 1
    },
    {
      "id": "240e9ab0-f629-4285-8f64-ea0c48c7bd0c",
      "name": "1\ufe0f\u20e3 Daily Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        1424
      ],
      "parameters": {
        "color": 5,
        "width": 1040,
        "height": 400,
        "content": "## 1\ufe0f\u20e3 Daily Fetch\n\n**Every Morning at 9** triggers the run, **Get Appointments** pulls the full appointment list from your sheet, **Filter For Tomorrow** narrows it to next day bookings, and **Filter Out Cancelled** drops anything marked Cancelled."
      },
      "typeVersion": 1
    },
    {
      "id": "74c03a5c-910a-4e04-a6c7-7b1cc80acd51",
      "name": "2\ufe0f\u20e3 Reminders & Exceptions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3120,
        1248
      ],
      "parameters": {
        "color": 3,
        "width": 880,
        "height": 816,
        "content": "## 2\ufe0f\u20e3 Reminders & Exceptions\n\n**Has Valid Email** checks each appointment for contact info. **Send Reminder Email** and **Mark As Reminded** handle the normal case, while **Flag Missing Contact Info** and **Alert Front Desk Missing Info** make sure anyone without an email still gets a human follow-up instead of being silently skipped."
      },
      "typeVersion": 1
    },
    {
      "id": "66bad02c-5d47-4c61-9141-341852cc2332",
      "name": "3\ufe0f\u20e3 Same-Day Reminder",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        2208
      ],
      "parameters": {
        "color": 6,
        "width": 1056,
        "height": 496,
        "content": "## 3\ufe0f\u20e3 Same-Day Reminder\n\n**Two Hour Reminder Check** runs throughout the day, **Get Today Appointments** and **Filter Next Two Hours** find bookings starting soon, and **Send Final Reminder** gives patients a last-minute nudge."
      },
      "typeVersion": 1
    },
    {
      "id": "a59a7a33-65a4-43a7-a93f-28347cdb29e7",
      "name": "4\ufe0f\u20e3 Summary & Error Handling",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3232,
        2080
      ],
      "parameters": {
        "color": 4,
        "width": 880,
        "height": 664,
        "content": "## 4\ufe0f\u20e3 Summary & Error Handling\n\n**Build Summary** counts how many appointments were processed and **Front Desk Summary** posts the total to your staff channel. **Workflow Error Trigger** and **Alert Front Desk on Workflow Failure** catch and surface any failure in this workflow."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "c86b55b5-e257-4969-9fa0-5fb0aaf722d2",
  "nodeGroups": [],
  "connections": {
    "Build Summary": {
      "main": [
        [
          {
            "node": "Front Desk Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Valid Email": {
      "main": [
        [
          {
            "node": "Send Reminder Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag Missing Contact Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Appointments": {
      "main": [
        [
          {
            "node": "Filter For Tomorrow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Morning at 9": {
      "main": [
        [
          {
            "node": "Get Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter For Tomorrow": {
      "main": [
        [
          {
            "node": "Filter Out Cancelled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reminder Email": {
      "main": [
        [
          {
            "node": "Mark As Reminded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Out Cancelled": {
      "main": [
        [
          {
            "node": "Has Valid Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Next Two Hours": {
      "main": [
        [
          {
            "node": "Send Final Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Today Appointments": {
      "main": [
        [
          {
            "node": "Filter Next Two Hours",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Error Trigger": {
      "main": [
        [
          {
            "node": "Alert Front Desk on Workflow Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Two Hour Reminder Check": {
      "main": [
        [
          {
            "node": "Get Today Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag Missing Contact Info": {
      "main": [
        [
          {
            "node": "Alert Front Desk Missing Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}