AutomationFlowsGeneral › Send Consult Confirmations and Reminders with Calendly, Twilio, and SMTP Email

Send Consult Confirmations and Reminders with Calendly, Twilio, and SMTP Email

ByDele Tosh @ Protomated @deletosh on n8n.io

This workflow receives new consult bookings via webhook (Calendly v2 or a generic scheduling tool), generates timed confirmation and reminder messages, runs each SMS through a separate compliance workflow, and sends SMS via Twilio plus matching emails via SMTP, including a…

Webhook trigger★★★★★ complexity39 nodesTwilioEmail Send
General Trigger: Webhook Nodes: 39 Complexity: ★★★★★ Added:

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

The workflow JSON

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

Download .json
{
  "name": "Consult No-Show Killer",
  "tags": [],
  "nodes": [
    {
      "id": "7ed4d1e2-2fca-4249-8a2d-ebed781a0d4d",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        -496
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Consult No-Show Killer\n\n### How it works\n\nThis workflow receives consultation bookings, normalizes Calendly or generic booking payloads, and ignores invalid events. For valid bookings, it prepares firm-specific confirmation and reminder messages, runs opt-out/compliance checks before each SMS stage, then sends confirmation, 24-hour reminder, 1-hour reminder, and no-show reschedule outreach by text and email. Wait nodes time each follow-up relative to the appointment lifecycle.\n\n### Setup steps\n\n- Configure the booking webhook URL in Calendly or the booking system that will send appointment events.\n- Set the required n8n Variables for firm details and message content used by the message setup code node.\n- Connect Twilio credentials and verify the sending phone number used by all SMS nodes.\n- Configure email sending credentials for the confirmation, reminder, and reschedule email nodes.\n- Select or create the compliance/opt-out sub-workflow used by each Execute Workflow compliance check node.\n- Review each Wait node timing so the 24-hour reminder, 1-hour reminder, and no-show check align with the appointment start time.\n\n### Customization\n\nAdjust the message templates, firm variables, wait intervals, and compliance rules to match the consult type, jurisdiction, and preferred reminder cadence."
      },
      "typeVersion": 1
    },
    {
      "id": "0d3c05be-18cb-4832-861e-c72618efcaa0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Receive booking event\n\nAccepts incoming booking webhook data and normalizes it into a consistent format for downstream processing."
      },
      "typeVersion": 1
    },
    {
      "id": "93d92cb0-7218-4095-9e8f-bad2b317d5db",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 464,
        "content": "## Validate booking path\n\nChecks whether the normalized booking should continue through the reminder sequence and diverts invalid events to a no-op endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "7bb18111-3774-4a22-a841-3b92be8dcd84",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 448,
        "content": "## Confirm compliance approval\n\nBuilds firm-specific message content, runs the confirmation text through the compliance/opt-out sub-workflow, and skips contacts that should not receive the SMS."
      },
      "typeVersion": 1
    },
    {
      "id": "e09a42fd-d3ba-4d50-8a25-b1406f6b1ead",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 304,
        "content": "## Send initial confirmation\n\nSends the approved confirmation SMS and email, then waits until it is time to begin the 24-hour reminder stage."
      },
      "typeVersion": 1
    },
    {
      "id": "df49590a-b817-4d85-94d9-cea39bf6c460",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 464,
        "content": "## Approve 24h reminder\n\nAfter the initial wait, checks whether the contact is still eligible to receive the 24-hour reminder text and routes opted-out contacts to a skip node."
      },
      "typeVersion": 1
    },
    {
      "id": "8c6d9b47-635b-4e4f-af84-af348ad90c76",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2640,
        -496
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 304,
        "content": "## Send 24h reminder\n\nSends the approved 24-hour reminder by SMS and email, then waits until the 1-hour reminder window."
      },
      "typeVersion": 1
    },
    {
      "id": "cb64c3d6-efca-47cd-a5ec-822f08e1d718",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3312,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 448,
        "content": "## Approve 1h reminder\n\nRuns the 1-hour reminder SMS through the compliance/opt-out check and skips delivery when the contact is not approved."
      },
      "typeVersion": 1
    },
    {
      "id": "be2b4b50-9900-43d0-993d-7f0aeb5a96bd",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3984,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 304,
        "content": "## Send 1h reminder\n\nSends the approved 1-hour reminder by SMS and email, then waits until the post-appointment no-show check."
      },
      "typeVersion": 1
    },
    {
      "id": "a02d5718-aedf-4bb5-895b-a1b4f40dc301",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4656,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 448,
        "content": "## Approve no-show text\n\nRuns the no-show reschedule text through the compliance/opt-out workflow and skips the reschedule SMS for ineligible contacts."
      },
      "typeVersion": 1
    },
    {
      "id": "900324ef-088a-4fc7-b130-30e96162cc28",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4880,
        -480
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Send reschedule outreach\n\nSends a post no-show reschedule text and follow-up email to encourage the prospect to book another consultation."
      },
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000001",
      "name": "Setup Booking Listener",
      "type": "n8n-nodes-base.webhook",
      "notes": "Receives the booking event from your scheduling tool.\n\nThis URL is the address your booking tool will notify whenever someone books a consult. Copy the Production URL from this node (visible after you activate the workflow) and register it with your booking tool.\n\nCalendly: Calendly v2 requires registering the webhook via API \u2014 you cannot paste a URL through the Calendly interface. See the deploy guide for the exact registration command. Activate the workflow first so the URL is live before registering.\n\nOther tools (Acuity, Cal.com, Fluent Forms, practice management systems): paste the Production URL into your tool's webhook or notification settings and select the 'new booking created' event. The exact label varies by tool.\n\nThe next step (Normalize and Validate Booking) automatically detects whether the event came from Calendly or another tool and pulls the right fields from each.",
      "position": [
        0,
        0
      ],
      "parameters": {
        "path": "consult-booking",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000002",
      "name": "Normalize & Validate Booking Data",
      "type": "n8n-nodes-base.code",
      "notes": "Reads the incoming booking event and extracts everything the workflow needs: the prospect's name, email, phone number, appointment time, consult type, and whether this is a new booking or a cancellation.\n\nThis step works with both Calendly and other booking tools \u2014 it detects which one sent the event and pulls the right fields from each.\n\nPhone number for Calendly: the workflow looks first for Calendly's built-in text reminder number field, then for any question-and-answer field whose label contains the word 'phone'. If neither is present, the SMS steps will skip gracefully and email still sends.\n\nValidity check: only new bookings proceed. Cancellations and reschedule confirmations are filtered out at the next step so no reminders are sent for a booking that has already been cancelled.\n\nOutput available to all later steps: contact_name, contact_email, contact_phone, appointment_time, consult_type, reschedule_url, booking_event, source, is_valid_booking, timestamp.",
      "position": [
        224,
        0
      ],
      "parameters": {
        "jsCode": "// Handles both Calendly v2 (invitee.created / invitee.canceled) and generic\n// booking webhook formats. Sets is_valid_booking = true only for new-booking\n// events \u2014 guards against cancellation and reschedule events on the same endpoint.\n\nconst raw  = $input.first().json;\nconst body = raw.body || raw; // n8n webhook configs may nest the payload inside .body\n\nlet normalized;\n\n// Calendly v2 real webhook: invitee data sits directly on body.payload (not body.payload.invitee).\n// The scheduled event details are at body.payload.scheduled_event.\nif (body.payload && body.payload.scheduled_event) {\n  // \u2500\u2500 Calendly v2 shape \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const p   = body.payload;\n  const evt = p.scheduled_event || {};\n\n  // Phone: prefer text_reminder_number; fall back to a custom Q&A 'phone' field.\n  // text_reminder_number is opt-in in Calendly \u2014 it may be absent or null.\n  let phone = p.text_reminder_number || '';\n  if (!phone && Array.isArray(p.questions_and_answers)) {\n    const phoneQ = p.questions_and_answers.find(\n      q => /phone/i.test(q.question) && q.answer\n    );\n    if (phoneQ) phone = phoneQ.answer;\n  }\n\n  normalized = {\n    contact_name:     p.name || `${p.first_name || ''} ${p.last_name || ''}`.trim(),\n    contact_email:    p.email || '',\n    contact_phone:    phone,\n    appointment_time: evt.start_time || '',\n    consult_type:     evt.name || 'consultation',\n    reschedule_url:   p.reschedule_url || '',\n    booking_event:    body.event || 'invitee.created',\n    source:           'calendly',\n    is_valid_booking: body.event === 'invitee.created'\n                      && !!(p.email || phone)\n                      && !!evt.start_time,\n  };\n} else {\n  // \u2500\u2500 Generic booking webhook (Acuity, Cal.com, Fluent Forms, PM systems, etc.) \u2500\n  const evtType = body.event_type || '';\n  normalized = {\n    contact_name:     body.contact_name || body.name || '',\n    contact_email:    body.contact_email || body.email || '',\n    contact_phone:    body.contact_phone || body.phone || '',\n    appointment_time: body.appointment_time || body.start_time || '',\n    consult_type:     body.consult_type || body.appointment_type || 'consultation',\n    reschedule_url:   body.reschedule_url || '',\n    booking_event:    evtType,\n    source:           'generic',\n    // Reject explicit cancel/reschedule events; require contact info and a time\n    is_valid_booking: !/cancel|reschedul/i.test(evtType)\n                      && !!(body.contact_email || body.email\n                            || body.contact_phone || body.phone)\n                      && !!(body.appointment_time || body.start_time),\n  };\n}\n\nreturn [{ json: { ...normalized, timestamp: new Date().toISOString() } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000003",
      "name": "Check Booking Validity",
      "type": "n8n-nodes-base.if",
      "notes": "Decides whether to proceed or stop \u2014 only genuine new bookings trigger the reminder sequence.\n\nCalendly sends notifications for cancellations and reschedule confirmations on the same webhook endpoint. Without this check, a cancelled booking could kick off a confirmation message and a full reminder sequence for an appointment that will never happen.\n\nYes (new booking with contact details and appointment time) \u2192 proceed to opt-out check and messages.\nNo (cancellation, reschedule confirmation, or missing contact details) \u2192 stop here, nothing sent.",
      "onError": "continueErrorOutput",
      "position": [
        448,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c_valid",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.is_valid_booking }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000004",
      "name": "Ignore Invalid Bookings",
      "type": "n8n-nodes-base.noOp",
      "notes": "Safely ends the workflow for cancellations, reschedule confirmations, and events with missing contact details. No messages are sent. This endpoint is visible in the execution history so you can confirm the filter is working correctly.",
      "position": [
        672,
        150
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000005",
      "name": "Prepare Notification Messages",
      "type": "n8n-nodes-base.code",
      "notes": "Writes all four message texts and calculates the exact times each reminder should fire.\n\nReads from n8n Variables (project \u2192 Variables tab):\n  FIRM_NAME \u2014 your law firm's name as it should appear in messages\n  FIRM_EMAIL \u2014 your intake email address (used for firm-side copies of reminders)\n  FIRM_FROM_EMAIL \u2014 the email address reminder emails are sent from; must match an address your SMTP account is authorised to send from\n  FIRM_TWILIO_NUMBER \u2014 your Twilio number in +1XXXXXXXXXX format\n  FIRM_BOOKING_URL \u2014 your scheduling link, used as the reschedule link if your booking tool doesn't include one per booking\n  FIRM_TIMEZONE \u2014 your state's timezone (e.g. 'America/Chicago', 'America/Los_Angeles')\n\nAll four text messages are personalised with the prospect's first name when available. All include 'Reply STOP to opt out' as required by TCPA law.\n\nTiming notes: if a booking comes in less than 24 hours before the appointment (same-day booking), the 24-hour reminder fires within 90 seconds of confirmation. The reschedule nudge always fires 15 minutes after the scheduled start regardless of whether the prospect attended.\n\nAll later steps in this workflow read their data from this node \u2014 not from their own immediate input \u2014 because Twilio and email responses have a different shape than the booking data.",
      "position": [
        896,
        0
      ],
      "parameters": {
        "jsCode": "// Reads firm details from n8n Variables (project \u2192 Variables tab).\n// Set: FIRM_NAME, FIRM_EMAIL, FIRM_FROM_EMAIL, FIRM_TWILIO_NUMBER, FIRM_BOOKING_URL, FIRM_TIMEZONE\nconst FIRM_NAME          = $vars.FIRM_NAME;\nconst FIRM_EMAIL         = $vars.FIRM_EMAIL;            // where firm copies of reminders go\nconst FROM_EMAIL         = $vars.FIRM_FROM_EMAIL;       // the address reminder emails come from (must be authorised by your SMTP account)\nconst FIRM_TWILIO_NUMBER = $vars.FIRM_TWILIO_NUMBER;    // your Twilio SMS number (E.164)\nconst BOOKING_URL        = $vars.FIRM_BOOKING_URL;      // fallback if no per-booking reschedule URL\nconst TIMEZONE           = $vars.FIRM_TIMEZONE;         // e.g. 'America/Chicago', 'America/Los_Angeles'\n\nconst item    = $input.first().json;\nconst now     = new Date();\nconst aptTime = new Date(item.appointment_time);\n\n// Calculate ISO timestamps for each Wait node.\n// If a booking arrives less than 24 h before the appointment, the 24 h reminder\n// fires almost immediately (floors to now + 90 s). Same for the 1 h reminder.\nconst MIN_WAIT = 90_000; // 90-second floor so Wait never fires before prior steps finish\nconst MS_24H   = 24 * 60 * 60 * 1000;\nconst MS_1H    =      60 * 60 * 1000;\nconst MS_15M   =      15 * 60 * 1000;\n\nconst reminder_24h_at = new Date(\n  Math.max(aptTime.getTime() - MS_24H, now.getTime() + MIN_WAIT)\n).toISOString();\nconst reminder_1h_at = new Date(\n  Math.max(aptTime.getTime() - MS_1H, now.getTime() + MIN_WAIT * 2)\n).toISOString();\nconst noshow_check_at = new Date(aptTime.getTime() + MS_15M).toISOString();\n\nconst aptDateStr = aptTime.toLocaleDateString('en-US', {\n  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: TIMEZONE,\n});\nconst aptTimeStr = aptTime.toLocaleTimeString('en-US', {\n  hour: 'numeric', minute: '2-digit', timeZoneName: 'short', timeZone: TIMEZONE,\n});\n\nconst rescheduleUrl = item.reschedule_url || BOOKING_URL;\nconst firstName     = (item.contact_name || '').split(' ')[0] || '';\nconst greeting      = firstName ? `Hi ${firstName}` : 'Hello';\nconst consultType   = item.consult_type || 'consultation';\n\n// Pre-build all four SMS bodies in one place \u2014 all include TCPA opt-out instruction.\nconst confirmSms  = `${greeting}, your ${consultType} with ${FIRM_NAME} is confirmed for ${aptDateStr} at ${aptTimeStr}. We look forward to speaking with you! Reply STOP to opt out.`;\nconst remind24Sms = `${greeting}, a friendly reminder: your ${consultType} with ${FIRM_NAME} is tomorrow at ${aptTimeStr}. See you then! Reply STOP to opt out.`;\nconst remind1hSms = `${greeting}, your ${consultType} with ${FIRM_NAME} starts in 1 hour at ${aptTimeStr}. Can't make it? Reschedule: ${rescheduleUrl} \u2014 Reply STOP to opt out.`;\nconst noshowSms   = `${greeting}, we had your ${consultType} scheduled today at ${aptTimeStr} with ${FIRM_NAME}. If anything came up, no worries \u2014 reschedule here: ${rescheduleUrl} \u2014 Reply STOP to opt out.`;\n\nreturn [{ json: {\n  ...item,\n  firm_name:            FIRM_NAME,\n  firm_email:           FIRM_EMAIL,\n  from_email:           FROM_EMAIL,\n  firm_twilio_number:   FIRM_TWILIO_NUMBER,\n  booking_url:          BOOKING_URL,\n  reschedule_url_final: rescheduleUrl,\n  apt_date_str:         aptDateStr,\n  apt_time_str:         aptTimeStr,\n  greeting,\n  first_name:           firstName,\n  consult_type:         consultType,\n  reminder_24h_at,\n  reminder_1h_at,\n  noshow_check_at,\n  confirm_sms:          confirmSms,\n  remind_24h_sms:       remind24Sms,\n  remind_1h_sms:        remind1hSms,\n  noshow_sms:           noshowSms,\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000006",
      "name": "Verify Confirmation Compliance",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Calls the Bar-Compliance Guardrail before sending the SMS.\n\nChecks whether the client has opted out. If not, confirms the TCPA opt-out instruction is present in the message and logs the send attempt to your audit sheet.\n\nThe guardrail workflow ID is read from the GUARDRAIL_WORKFLOW_ID n8n Variable (project \u2192 Variables tab). Set it to the numeric ID from the Bar-Compliance Guardrail workflow URL: .../workflow/WORKFLOW_ID.\n\nReturns: { approved, message_out, recipient, suppression_reason } plus all upstream fields.",
      "position": [
        1120,
        0
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "expression",
          "value": "={{ $vars.GUARDRAIL_WORKFLOW_ID }}"
        },
        "workflowInputs": {
          "value": {
            "channel": "sms",
            "message": "={{ $('Prepare Notification Messages').item.json.confirm_sms }}",
            "matter_id": "",
            "recipient": "={{ $('Prepare Notification Messages').item.json.contact_phone }}",
            "template_id": "NTC-15"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000007",
      "name": "Check Confirmation Approval",
      "type": "n8n-nodes-base.if",
      "notes": "Routes based on the compliance guardrail's decision.\n\nYes (approved) \u2192 sends the SMS.\nNo (opted out) \u2192 message suppressed; guardrail has logged the suppression.",
      "onError": "continueErrorOutput",
      "position": [
        1344,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c_approved_confirm",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000008",
      "name": "Skip Opt-Out (Confirmation)",
      "type": "n8n-nodes-base.noOp",
      "notes": "Client is on the opt-out list \u2014 confirmation text not sent and the full reminder sequence is stopped. The Bar-Compliance Guardrail has logged the suppression.",
      "position": [
        1568,
        150
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000009",
      "name": "Dispatch Confirmation SMS",
      "type": "n8n-nodes-base.twilio",
      "notes": "Sends the instant booking confirmation text to the prospect \u2014 so they know the appointment is locked in before they even close the booking page.\n\nCredential to wire: In n8n go to project \u2192 Credentials tab \u2192 New, search for Twilio, and enter your Account SID and Auth Token from the Twilio Console. Save it as 'Twilio account'.\n\nIf the prospect did not provide a phone number, Twilio will return an error. The workflow handles this gracefully \u2014 it continues to the confirmation email rather than stopping. Both the text and the email are always attempted.",
      "onError": "continueRegularOutput",
      "position": [
        1792,
        0
      ],
      "parameters": {
        "to": "={{ $json.recipient }}",
        "from": "={{ $('Prepare Notification Messages').item.json.firm_twilio_number }}",
        "message": "={{ $json.message_out }}",
        "operation": "send"
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000010",
      "name": "Deliver Confirmation Email",
      "type": "n8n-nodes-base.emailSend",
      "notes": "Sends the booking confirmation email to the prospect.\n\nCredential to wire: In n8n go to project \u2192 Credentials tab \u2192 New, search for SMTP, and enter your email provider's settings. This works with Gmail, Outlook 365, Zoho Mail, or any SMTP-capable email service. Save it as 'Email account (SMTP)'.\n\nThe FROM_EMAIL you set in Set Up Messages must be an address your SMTP account is authorised to send from. For most firms this will be the same as FIRM_EMAIL (e.g. intake@smithlaw.com).\n\nAll contact and firm details are read from the Set Up Messages node \u2014 not from the immediate input \u2014 because the Twilio response (from the previous step) has a different data shape than the booking details.",
      "position": [
        2016,
        0
      ],
      "parameters": {
        "text": "=Hi {{ $('Prepare Notification Messages').item.json.first_name || 'there' }},\n\nYour {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }} is confirmed.\n\nDate: {{ $('Prepare Notification Messages').item.json.apt_date_str }}\nTime: {{ $('Prepare Notification Messages').item.json.apt_time_str }}\n\nWe look forward to speaking with you. If you need to reschedule, use this link:\n{{ $('Prepare Notification Messages').item.json.reschedule_url_final }}\n\nQuestions before your consult? Reply to this email and we'll be happy to help.\n\n\u2014 {{ $('Prepare Notification Messages').item.json.firm_name }}\n\n---\nTo stop receiving text reminders, reply STOP to any of our text messages.",
        "subject": "=Your {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }} is Confirmed",
        "toEmail": "={{ $('Prepare Notification Messages').item.json.contact_email }}",
        "emailType": "text",
        "fromEmail": "={{ $('Prepare Notification Messages').item.json.from_email }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000011",
      "name": "Wait for 24h Reminder",
      "type": "n8n-nodes-base.wait",
      "notes": "Pauses the workflow until 24 hours before the appointment, then automatically continues to send the reminder.\n\nThe workflow execution sits suspended in your n8n database during this wait \u2014 it does not consume server resources. When the scheduled time arrives, n8n wakes it up and continues from this point.\n\nFor same-day bookings (less than 24 hours until the appointment), this wait fires within 90 seconds so the '24-hour reminder' effectively acts as a second confirmation.\n\nImportant: self-hosted n8n must use Postgres as its database for these long waits to survive a restart. n8n Cloud handles this automatically. If you are self-hosted and see executions disappearing after restarts, check your database configuration.",
      "position": [
        2240,
        0
      ],
      "parameters": {
        "resume": "specificTime",
        "dateTime": "={{ $('Prepare Notification Messages').item.json.reminder_24h_at }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000012",
      "name": "Verify 24h Reminder Compliance",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Calls the Bar-Compliance Guardrail before sending the SMS.\n\nChecks whether the client has opted out. If not, confirms the TCPA opt-out instruction is present in the message and logs the send attempt to your audit sheet.\n\nThe guardrail workflow ID is read from the GUARDRAIL_WORKFLOW_ID n8n Variable (project \u2192 Variables tab). Set it to the numeric ID from the Bar-Compliance Guardrail workflow URL: .../workflow/WORKFLOW_ID.\n\nReturns: { approved, message_out, recipient, suppression_reason } plus all upstream fields.",
      "position": [
        2464,
        0
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "expression",
          "value": "={{ $vars.GUARDRAIL_WORKFLOW_ID }}"
        },
        "workflowInputs": {
          "value": {
            "channel": "sms",
            "message": "={{ $('Prepare Notification Messages').item.json.remind_24h_sms }}",
            "matter_id": "",
            "recipient": "={{ $('Prepare Notification Messages').item.json.contact_phone }}",
            "template_id": "NTC-15"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000013",
      "name": "Check 24h Reminder Approval",
      "type": "n8n-nodes-base.if",
      "notes": "Routes based on the compliance guardrail's decision.\n\nYes (approved) \u2192 sends the SMS.\nNo (opted out) \u2192 message suppressed; guardrail has logged the suppression.",
      "onError": "continueErrorOutput",
      "position": [
        2688,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c_approved_24h",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000014",
      "name": "Skip Opt-Out (24h Reminder)",
      "type": "n8n-nodes-base.noOp",
      "notes": "Client has opted out \u2014 24h reminder suppressed and the remaining reminders are stopped.",
      "position": [
        2912,
        144
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000015",
      "name": "Dispatch 24h Reminder SMS",
      "type": "n8n-nodes-base.twilio",
      "notes": "Sends the 24-hour reminder text to the prospect \u2014 the single most effective touchpoint for reducing no-shows.\n\nIf the text fails (e.g. the prospect's number is invalid), the workflow continues to the 24-hour reminder email rather than stopping.\n\nAll message data is read from the Set Up Messages node \u2014 not from the immediate input \u2014 because the Wait node's resume data has a different shape than the booking details.",
      "onError": "continueRegularOutput",
      "position": [
        2688,
        -368
      ],
      "parameters": {
        "to": "={{ $json.recipient }}",
        "from": "={{ $('Prepare Notification Messages').item.json.firm_twilio_number }}",
        "message": "={{ $json.message_out }}",
        "operation": "send"
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000016",
      "name": "Deliver 24h Reminder Email",
      "type": "n8n-nodes-base.emailSend",
      "notes": "Sends the 24-hour reminder email to the prospect.\n\nAll contact and firm details are read from the Set Up Messages node \u2014 not from the immediate input \u2014 because the Twilio response (from the previous step) has a different data shape than the booking details.",
      "position": [
        2912,
        -368
      ],
      "parameters": {
        "text": "=Hi {{ $('Prepare Notification Messages').item.json.first_name || 'there' }},\n\nJust a friendly reminder that your {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }} is tomorrow.\n\nTime: {{ $('Prepare Notification Messages').item.json.apt_time_str }}\n\nWe're looking forward to speaking with you. If you need to reschedule, please do so at least 1 hour in advance:\n{{ $('Prepare Notification Messages').item.json.reschedule_url_final }}\n\n\u2014 {{ $('Prepare Notification Messages').item.json.firm_name }}",
        "subject": "=Reminder: Your {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }} is Tomorrow",
        "toEmail": "={{ $('Prepare Notification Messages').item.json.contact_email }}",
        "emailType": "text",
        "fromEmail": "={{ $('Prepare Notification Messages').item.json.from_email }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000017",
      "name": "Wait for 1h Reminder",
      "type": "n8n-nodes-base.wait",
      "notes": "Pauses the workflow until 1 hour before the appointment, then continues to send the final pre-consult reminder.\n\nFor bookings made less than 1 hour before the appointment, this fires within 3 minutes of the previous step so the prospect still gets a final nudge.",
      "position": [
        3136,
        -368
      ],
      "parameters": {
        "resume": "specificTime",
        "dateTime": "={{ $('Prepare Notification Messages').item.json.reminder_1h_at }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000018",
      "name": "Verify 1h Reminder Compliance",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Calls the Bar-Compliance Guardrail before sending the SMS.\n\nChecks whether the client has opted out. If not, confirms the TCPA opt-out instruction is present in the message and logs the send attempt to your audit sheet.\n\nThe guardrail workflow ID is read from the GUARDRAIL_WORKFLOW_ID n8n Variable (project \u2192 Variables tab). Set it to the numeric ID from the Bar-Compliance Guardrail workflow URL: .../workflow/WORKFLOW_ID.\n\nReturns: { approved, message_out, recipient, suppression_reason } plus all upstream fields.",
      "position": [
        3360,
        0
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "expression",
          "value": "={{ $vars.GUARDRAIL_WORKFLOW_ID }}"
        },
        "workflowInputs": {
          "value": {
            "channel": "sms",
            "message": "={{ $('Prepare Notification Messages').item.json.remind_1h_sms }}",
            "matter_id": "",
            "recipient": "={{ $('Prepare Notification Messages').item.json.contact_phone }}",
            "template_id": "NTC-15"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000019",
      "name": "Check 1h Reminder Approval",
      "type": "n8n-nodes-base.if",
      "notes": "Routes based on the compliance guardrail's decision.\n\nYes (approved) \u2192 sends the SMS.\nNo (opted out) \u2192 message suppressed; guardrail has logged the suppression.",
      "onError": "continueErrorOutput",
      "position": [
        3584,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c_approved_1h",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000020",
      "name": "Skip Opt-Out (1h Reminder)",
      "type": "n8n-nodes-base.noOp",
      "notes": "Client has opted out \u2014 1h reminder suppressed and the no-show check is stopped.",
      "position": [
        3808,
        150
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000021",
      "name": "Dispatch 1h Reminder SMS",
      "type": "n8n-nodes-base.twilio",
      "notes": "Sends the 1-hour reminder text. This message includes the reschedule link so the prospect has a clear, easy option if they can't make it \u2014 reducing last-minute no-shows where the prospect simply doesn't show up because they didn't know how to cancel.\n\nIf the text fails, the workflow continues to the 1-hour reminder email rather than stopping.",
      "onError": "continueRegularOutput",
      "position": [
        4032,
        0
      ],
      "parameters": {
        "to": "={{ $json.recipient }}",
        "from": "={{ $('Prepare Notification Messages').item.json.firm_twilio_number }}",
        "message": "={{ $json.message_out }}",
        "operation": "send"
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000022",
      "name": "Deliver 1h Reminder Email",
      "type": "n8n-nodes-base.emailSend",
      "notes": "Sends the 1-hour reminder email with the reschedule link. All contact and firm details are read from the Set Up Messages node.",
      "position": [
        4256,
        0
      ],
      "parameters": {
        "text": "=Hi {{ $('Prepare Notification Messages').item.json.first_name || 'there' }},\n\nYour {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }} starts in 1 hour at {{ $('Prepare Notification Messages').item.json.apt_time_str }}.\n\nIf you need to reschedule, please do so now:\n{{ $('Prepare Notification Messages').item.json.reschedule_url_final }}\n\nOtherwise, we'll talk soon!\n\n\u2014 {{ $('Prepare Notification Messages').item.json.firm_name }}",
        "subject": "=Your {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }} Starts in 1 Hour",
        "toEmail": "={{ $('Prepare Notification Messages').item.json.contact_email }}",
        "emailType": "text",
        "fromEmail": "={{ $('Prepare Notification Messages').item.json.from_email }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000023",
      "name": "Wait for No-Show Check",
      "type": "n8n-nodes-base.wait",
      "notes": "Pauses the workflow until 15 minutes after the appointment was scheduled to start, then sends the reschedule nudge.\n\nThis step fires for all bookings \u2014 the free template does not connect to your practice management system to check whether the prospect actually attended. The message is written neutrally ('if anything came up') rather than assuming a no-show, so prospects who did attend are not alienated by an accusatory message.\n\nThe done-for-you upgrade adds Clio or MyCase integration to check attendance status before this step, and only sends the reschedule message for genuine no-shows.",
      "position": [
        4480,
        0
      ],
      "parameters": {
        "resume": "specificTime",
        "dateTime": "={{ $('Prepare Notification Messages').item.json.noshow_check_at }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000024",
      "name": "Verify No-Show Compliance",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Calls the Bar-Compliance Guardrail before sending the SMS.\n\nChecks whether the client has opted out. If not, confirms the TCPA opt-out instruction is present in the message and logs the send attempt to your audit sheet.\n\nThe guardrail workflow ID is read from the GUARDRAIL_WORKFLOW_ID n8n Variable (project \u2192 Variables tab). Set it to the numeric ID from the Bar-Compliance Guardrail workflow URL: .../workflow/WORKFLOW_ID.\n\nReturns: { approved, message_out, recipient, suppression_reason } plus all upstream fields.",
      "position": [
        4704,
        0
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "expression",
          "value": "={{ $vars.GUARDRAIL_WORKFLOW_ID }}"
        },
        "workflowInputs": {
          "value": {
            "channel": "sms",
            "message": "={{ $('Prepare Notification Messages').item.json.noshow_sms }}",
            "matter_id": "",
            "recipient": "={{ $('Prepare Notification Messages').item.json.contact_phone }}",
            "template_id": "NTC-15"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000025",
      "name": "Check No-Show Approval",
      "type": "n8n-nodes-base.if",
      "notes": "Routes based on the compliance guardrail's decision.\n\nYes (approved) \u2192 sends the SMS.\nNo (opted out) \u2192 message suppressed; guardrail has logged the suppression.",
      "onError": "continueErrorOutput",
      "position": [
        4928,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c_approved_noshow",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000026",
      "name": "Skip Opt-Out (No-Show)",
      "type": "n8n-nodes-base.noOp",
      "notes": "Client has opted out \u2014 no-show reschedule text not sent.",
      "position": [
        5152,
        144
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000027",
      "name": "Dispatch Reschedule SMS",
      "type": "n8n-nodes-base.twilio",
      "notes": "Sends a gentle reschedule nudge 15 minutes after the scheduled consult.\n\nThe message reads: 'If anything came up, no worries \u2014 reschedule here: [link]'. The neutral phrasing is deliberate \u2014 this fires regardless of whether the prospect attended, so a firm tone would damage the firm's relationship with clients who did show up.",
      "onError": "continueRegularOutput",
      "position": [
        4928,
        -320
      ],
      "parameters": {
        "to": "={{ $json.recipient }}",
        "from": "={{ $('Prepare Notification Messages').item.json.firm_twilio_number }}",
        "message": "={{ $json.message_out }}",
        "operation": "send"
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1500bb00-0002-4000-b000-000000000028",
      "name": "Deliver Reschedule Email",
      "type": "n8n-nodes-base.emailSend",
      "notes": "Sends the reschedule follow-up email 15 minutes after the scheduled consult.\n\nThe subject 'We Missed You' is accurate for genuine no-shows. For prospects who did attend, it reads as a minor curiosity rather than an accusation \u2014 acceptable for the free template. The done-for-you upgrade checks attendance status before sending so this only goes to actual no-shows.",
      "position": [
        5152,
        -320
      ],
      "parameters": {
        "text": "=Hi {{ $('Prepare Notification Messages').item.json.first_name || 'there' }},\n\nWe had your {{ $('Prepare Notification Messages').item.json.consult_type }} scheduled today at {{ $('Prepare Notification Messages').item.json.apt_time_str }} with {{ $('Prepare Notification Messages').item.json.firm_name }}. If anything came up, no worries \u2014 life happens!\n\nYou can easily book a new time here:\n{{ $('Prepare Notification Messages').item.json.reschedule_url_final }}\n\nWe're still here to help whenever you're ready.\n\n\u2014 {{ $('Prepare Notification Messages').item.json.firm_name }}\n\n---\nTo stop receiving text reminders, reply STOP to any of our text messages.",
        "subject": "=We Missed You \u2014 Reschedule Your {{ $('Prepare Notification Messages').item.json.consult_type }} with {{ $('Prepare Notification Messages').item.json.firm_name }}",
        "toEmail": "={{ $('Prepare Notification Messages').item.json.contact_email }}",
        "emailType": "text",
        "fromEmail": "={{ $('Prepare Notification Messages').item.json.from_email }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1",
    "executionTimeout": -1,
    "saveManualExecutions": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "nodeGroups": [],
  "staticData": null,
  "connections": {
    "Wait for 1h Reminder": {
      "main": [
        [
          {
            "node": "Verify 1h Reminder Compliance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for 24h Reminder": {
      "main": [
        [
          {
            "node": "Verify 24h Reminder Compliance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Booking Validity": {
      "main": [
        [
          {
            "node": "Prepare Notification Messages",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ignore Invalid Bookings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check No-Show Approval": {
      "main": [
        [
          {
            "node": "Dispatch Reschedule SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Opt-Out (No-Show)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Setup Booking Listener": {
      "main": [
        [
          {
            "node": "Normalize & Validate Booking Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for No-Show Check": {
      "main": [
        [
          {
            "node": "Verify No-Show Compliance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dispatch Reschedule SMS": {
      "main": [
        [
          {
            "node": "Deliver Reschedule Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dispatch 1h Reminder SMS": {
      "main": [
        [
          {
            "node": "Deliver 1h Reminder Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deliver 1h Reminder Email": {
      "main": [
        [
          {
            "node": "Wait for No-Show Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dispatch 24h Reminder SMS": {
      "main": [
        [
          {
            "node": "Deliver 24h Reminder Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dispatch Confirmation SMS": {
      "main": [
        [
          {
            "node": "Deliver Confirmation Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify No-Show Compliance": {
      "main": [
        [
          {
            "node": "Check No-Show Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1h Reminder Approval": {
      "main": [
        [
          {
            "node": "Dispatch 1h Reminder SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Opt-Out (1h Reminder)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deliver 24h Reminder Email": {
      "main": [
        [
          {
            "node": "Wait for 1h Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deliver Confirmation Email": {
      "main": [
        [
          {
            "node": "Wait for 24h Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 24h Reminder Approval": {
      "main": [
        [
          {
            "node": "Dispatch 24h Reminder SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Opt-Out (24h Reminder)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Confirmation Approval": {
      "main": [
        [
          {
            "node": "Dispatch Confirmation SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip Opt-Out (Confirmation)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Notification Messages": {
      "main": [
        [
          {
            "node": "Verify Confirmation Compliance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify 1h Reminder Compliance": {
      "main": [
        [
          {
            "node": "Check 1h Reminder Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify 24h Reminder Compliance": {
      "main": [
        [
          {
            "node": "Check 24h Reminder Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Confirmation Compliance": {
      "main": [
        [
          {
            "node": "Check Confirmation Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize & Validate Booking Data": {
      "main": [
        [
          {
            "node": "Check Booking Validity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "description": "Sends confirmation, 24-hour, and 1-hour reminders (SMS + email) for every booked consult, plus a reschedule nudge 15 minutes after the scheduled start. Replaces Lawmatics sequences and paid Calendly reminder tiers. Trigger: Calendly v2 invitee.created webhook or generic booking webhook."
}

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 workflow receives new consult bookings via webhook (Calendly v2 or a generic scheduling tool), generates timed confirmation and reminder messages, runs each SMS through a separate compliance workflow, and sends SMS via Twilio plus matching emails via SMTP, including a…

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

More General workflows → · Browse all categories →

Related workflows

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

General

This workflow uses webhooks, Twilio SMS, SMTP email, and a Bar-Compliance Guardrail sub-workflow to request client ratings 48 hours after matters close. It routes replies so 4–5 star clients get a Goo

Twilio, Email Send
General

This workflow receives missed-call webhooks from Twilio or CallRail, normalizes call details, and instantly sends a compliant text-back via Twilio when allowed, then emails your firm a delivery-status

Twilio, Email Send
General

This workflow automates flight price comparison across multiple booking platforms (Kayak, Skyscanner, Expedia, Google Flights). It accepts natural language queries, extracts flight details using NLP,

Ssh, Email Send
General

Automate the tracking of customer subscription expiry dates, create renewal tasks in ClickUp, and dispatch friendly email reminders before the due date. The workflow listens for incoming subscription

Email Send
General

[NooviChat] Onboarding Pós-Pagamento (Exp 6). Uses emailSend. Webhook trigger; 11 nodes.

Email Send