{
  "name": "Appointment Reminders & No-Show Rescue (Helm Systems)",
  "nodes": [
    {
      "parameters": {
        "content": "## Appointment Reminders & No-Show Rescue\n1. Make a Google Sheet with: Date, Time, Client, Phone, Email, Service, Status, Reminder Sent, Rescue Sent. Status: Booked, Confirmed, Completed, No-show or Cancelled.\n2. Pick that sheet in **Read appointments** and **Mark sent**. Fill in **Settings** (your phone number goes in the text).\n3. Connect Twilio, Gmail and the AI model credentials.\n4. Each morning it reminds tomorrow's clients and sends a kind rebook note to yesterday's no-shows.\n5. While **testMode** is true, everything goes to your test phone and email. Texting clients needs their permission and an opt-out.\nMade by Helm Systems: helmsystems.biz/field-kit",
        "width": 420,
        "height": 310,
        "color": 4
      },
      "id": "309f1834-1507-4b1a-a3e9-cdb703165862",
      "name": "Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -342
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "5cf50f37-d4f4-4db4-a766-d238bc3d170d",
      "name": "Every morning at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "9dbe5dfb-7080-4229-a4b3-ec082750f38c",
              "name": "business",
              "value": "Your Business Name",
              "type": "string"
            },
            {
              "id": "e7d22008-c5c5-4b92-aad3-b48eeec5ec05",
              "name": "owner",
              "value": "Your Name",
              "type": "string"
            },
            {
              "id": "21c870cf-028e-40a5-a34e-076cb503e0f0",
              "name": "ownerEmail",
              "value": "you@yourbusiness.com",
              "type": "string"
            },
            {
              "id": "153dbac1-e4d6-4a75-af25-42d2e9f713df",
              "name": "autoSend",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "3df9bdfa-fbb5-42d5-ad3e-bb408109e43e",
              "name": "phone",
              "value": "(555) 555-0100",
              "type": "string"
            },
            {
              "id": "9dc35613-6d3e-4599-a87f-b3cb87cb3fd2",
              "name": "twilioNumber",
              "value": "+15555550100",
              "type": "string"
            },
            {
              "id": "06637e78-8d96-4c9e-a733-86156c4db6a6",
              "name": "testMode",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "4d3db788-5a8a-47d3-a563-d4223fce57bb",
              "name": "testPhone",
              "value": "+15555550101",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "22a73ff7-5ceb-49a2-a3b5-82c08624dce9",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      },
      "id": "8a824645-bbf9-4960-aa00-43485f0415ad",
      "name": "Read appointments",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        520,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const asDate = (v) => { if (!v) return null; const d = new Date(v); return isNaN(d) ? null : d; };\nconst startOfDay = (d) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; };\nconst today = startOfDay(new Date());\nconst daysBetween = (a, b) => Math.round((b - a) / 86400000);\nconst s = $('Settings').first().json;\nconst out = [];\nfor (const item of $input.all()) {\n  const r = item.json;\n  const when = asDate(r['Date']);\n  if (!when) continue;\n  const diff = daysBetween(startOfDay(when), today);\n  const status = String(r['Status'] || '').trim().toLowerCase();\n  let kind = null;\n  if (diff === -1 && ['booked', 'confirmed', ''].includes(status) && !String(r['Reminder Sent'] || '').trim()) kind = 'reminder';\n  if (diff === 1 && status === 'no-show' && !String(r['Rescue Sent'] || '').trim()) kind = 'missed appointment';\n  if (!kind) continue;\n  const phone = String(r['Phone'] || '').trim();\n  const email = String(r['Email'] || '').trim();\n  if (!phone && !email) continue;\n  out.push({ json: {\n    row_number: r.row_number,\n    kind,\n    channel: phone ? 'sms' : 'email',\n    toPhone: s.testMode ? s.testPhone : phone,\n    toEmail: s.testMode ? s.ownerEmail : email,\n    name: String(r['Client'] || '').split(' ')[0] || 'there',\n    service: r['Service'] || 'your appointment',\n    when: [r['Date'], r['Time']].filter(Boolean).join(' at '),\n    phone: s.phone, business: s.business, owner: s.owner,\n    reminderSent: r['Reminder Sent'] || '', rescueSent: r['Rescue Sent'] || '',\n  } });\n}\nreturn out;"
      },
      "id": "4499f09f-5c9a-41ba-ae6e-1d971b2cbca9",
      "name": "Pick who to message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        0
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Type: {{ $json.kind }}\nClient: {{ $json.name }}\nService: {{ $json.service }}\nAppointment: {{ $json.when }}",
        "hasOutputParser": false,
        "messages": {
          "messageValues": [
            {
              "message": "=You write short text messages for {{ $json.business }} to a client about an appointment.\n\nRules:\n- Reminder: confirm the day, time and service. Under 200 characters. Say they can reply here or call {{ $json.phone }} to change it.\n- Missed appointment: kind, no guilt, offer to rebook. Under 240 characters.\n- Never mention fees or penalties. No emojis.\nReturn only the text message."
            }
          ]
        }
      },
      "id": "a4371b0f-30d0-4a9f-a7c0-4f2269738a26",
      "name": "Write the message",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1040,
        0
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5-mini",
          "mode": "list",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "id": "a1f27fff-a99b-4e48-a4c7-7b4d7a12f853",
      "name": "Write the message model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        1040,
        266
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const base = $('Pick who to message').item.json;\nreturn { json: { ...base, message: String($json.text || '').trim() } };"
      },
      "id": "f24bff56-264a-4311-abb4-2014362482e7",
      "name": "Prepare the message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "6bb419b4-7cf6-4af1-a2fa-ff6b41c37368",
              "leftValue": "={{ $json.channel }}",
              "rightValue": "sms",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "72b666bc-613f-43cb-abee-d6c0d7897da4",
      "name": "Text or email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1560,
        0
      ]
    },
    {
      "parameters": {
        "resource": "sms",
        "operation": "send",
        "from": "={{ $('Settings').first().json.twilioNumber }}",
        "to": "={{ $json.toPhone }}",
        "message": "={{ $json.message }}",
        "options": {}
      },
      "id": "0a7fce5b-7a2a-4e49-a22e-ee22ef84111c",
      "name": "Send the text",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        1820,
        -95
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "sendTo": "={{ $json.toEmail }}",
        "subject": "={{ $json.kind === 'reminder' ? 'Reminder: your appointment with ' + $json.business : 'We missed you at ' + $json.business }}",
        "emailType": "text",
        "message": "={{ $json.message }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "114adbda-ad55-4da2-a0bc-0120623210ab",
      "name": "Send the email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1820,
        114
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "row_number": "={{ $('Prepare the message').item.json.row_number }}",
            "Reminder Sent": "={{ $('Prepare the message').item.json.kind === 'reminder' ? $now.toFormat('yyyy-LL-dd') : $('Prepare the message').item.json.reminderSent }}",
            "Rescue Sent": "={{ $('Prepare the message').item.json.kind === 'reminder' ? $('Prepare the message').item.json.rescueSent : $now.toFormat('yyyy-LL-dd') }}"
          },
          "matchingColumns": [
            "row_number"
          ],
          "schema": []
        },
        "options": {}
      },
      "id": "bb174219-4791-40e8-ad71-2c638744e2b6",
      "name": "Mark sent",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2340,
        0
      ]
    }
  ],
  "connections": {
    "Write the message model": {
      "ai_languageModel": [
        [
          {
            "node": "Write the message",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Every morning at 8am": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Read appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read appointments": {
      "main": [
        [
          {
            "node": "Pick who to message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick who to message": {
      "main": [
        [
          {
            "node": "Write the message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the message": {
      "main": [
        [
          {
            "node": "Prepare the message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare the message": {
      "main": [
        [
          {
            "node": "Text or email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text or email?": {
      "main": [
        [
          {
            "node": "Send the text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send the email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send the text": {
      "main": [
        [
          {
            "node": "Mark sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send the email": {
      "main": [
        [
          {
            "node": "Mark sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "tags": []
}
