{
  "name": "Lumeor | Appointment reminder reply handler (consent-gated SMS)",
  "nodes": [
    {
      "parameters": {
        "content": "## Appointment reminder reply handler\n\nHandles what happens after a patient replies to a reminder text. Opt-out words are caught by a fixed rule before anything else and recorded in your consent table. Replies are only answered for numbers with recorded SMS consent. Everything else is classified by Claude (confirm, cancel, reschedule, question, opt-out in plain words, unclear), applied through your scheduling system's webhook, and confirmed by text. Questions, unclear or clinical-sounding replies and anything that fails go to a staff queue table. Phrases on your clinician-written urgent list get a fixed \"call 911 or the office\" reply and an urgent staff task.\n\n### Compliance (read first)\n- **TCPA:** automated texts need the patient's prior consent, and opt-outs must be honored promptly. This workflow never texts a number without a consent row and records every STOP. It is not legal advice; have counsel review your consent language and message content.\n- Twilio already blocks texts to numbers that replied STOP, CANCEL, END, QUIT, UNSUBSCRIBE and similar words, and sends its own confirmation. Do not use YES or CANCEL as reply codes; use C to confirm and R to reschedule.\n- **HIPAA:** use a Twilio account covered by a BAA, a BAA-covered Anthropic API arrangement, and self-hosted n8n on HIPAA-eligible infrastructure. Keep clinical details out of texts.\n\n### Setup\n1. Create the Postgres tables `sms_consent(phone, consent, source, consented_at, opted_out_at, opt_out_keyword)`, `appointments(id, patient_phone, starts_at, provider, status)` and `staff_queue(phone, appointment_id, priority, reason, message, created_at)`. Phones in E.164.\n2. In Twilio, set the messaging number's incoming message webhook to the **Twilio inbound SMS** URL (HTTP POST).\n3. In **Normalize and screen**, review `URGENT_PHRASES` with a clinician.\n4. Replace `REPLACE_WITH_SCHEDULING_WEBHOOK_URL`, `REPLACE_WITH_TWILIO_NUMBER` and `REPLACE_WITH_PRACTICE_NAME`.\n5. Credentials: Postgres, Anthropic API, Header Auth for the scheduling webhook, Twilio.\n\nClassification runs at low effort on `claude-opus-5-5`. `claude-haiku-5-5` is a cheaper option for high-volume, simple classification like this.\n\nTemplate by Lumeor Studio, https://www.lumeor.studio/guides/ai-appointment-reminders-and-no-show-reduction/",
        "height": 1000,
        "width": 580,
        "color": 7
      },
      "id": "8044f197-75fa-436d-9456-e8925002d5e8",
      "name": "About this template",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -600,
        -300
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "sms-reply",
        "options": {},
        "responseMode": "responseNode"
      },
      "id": "a9d0571c-b331-4114-8f3a-94b64fcc95dc",
      "name": "Twilio inbound SMS",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "e113106b-f8fa-45a0-a8eb-a261ab031c59"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<Response></Response>",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        }
      },
      "id": "7b8efc5d-1314-4801-9066-83eb4719525b",
      "name": "Reply to Twilio (empty TwiML)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        220,
        120
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Carrier and Twilio opt-out keywords (single-word replies). Checked before anything else.\nconst OPT_OUT = ['STOP', 'STOPALL', 'UNSUBSCRIBE', 'CANCEL', 'END', 'QUIT', 'REVOKE', 'OPTOUT'];\nconst HELP = ['HELP', 'INFO'];\n// Clinician-written list. Replace with yours.\nconst URGENT_PHRASES = ['chest pain', \"can't breathe\", 'cant breathe', 'trouble breathing', 'suicid', 'kill myself', 'overdose', 'bleeding a lot', 'stroke', 'unconscious'];\n\nconst toE164 = (p) => { const d = String(p || '').replace(/[^\\d+]/g, ''); if (d.startsWith('+')) return d; if (d.length === 10) return '+1' + d; if (d.length === 11 && d.startsWith('1')) return '+' + d; return d; };\n\nreturn $input.all().map((item, i) => {\n  const b = item.json.body || {};\n  const text = String(b.Body || '').trim();\n  const word = text.toUpperCase().replace(/[^A-Z]/g, '');\n  const lower = text.toLowerCase();\n  return {\n    json: {\n      phone: toE164(b.From),\n      messageSid: b.MessageSid || null,\n      text,\n      isOptOut: OPT_OUT.includes(word),\n      optOutKeyword: OPT_OUT.includes(word) ? word : null,\n      isHelp: HELP.includes(word),\n      isUrgent: URGENT_PHRASES.some((p) => lower.includes(p)),\n    },\n    pairedItem: { item: i },\n  };\n});\n"
      },
      "id": "ed51026c-32e9-45c8-8060-2a60e28a95f2",
      "name": "Normalize and screen",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT c.consent, c.opted_out_at, a.id AS appointment_id, a.starts_at, a.provider, a.status\nFROM (SELECT $1::text AS phone) p\nLEFT JOIN sms_consent c ON c.phone = p.phone\nLEFT JOIN LATERAL (\n  SELECT id, starts_at, provider, status FROM appointments\n  WHERE patient_phone = p.phone AND starts_at > now() AND status IN ('scheduled', 'confirmed')\n  ORDER BY starts_at LIMIT 1\n) a ON true;",
        "options": {
          "queryReplacement": "={{ [ $json.phone ] }}"
        }
      },
      "id": "6a2baa9b-d141-4fc4-ab09-76435ca38c91",
      "name": "Postgres: look up consent and appointment",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const schema = {\n  type: 'object',\n  additionalProperties: false,\n  required: ['intent', 'confidence', 'clinical_concern', 'preferred_times'],\n  properties: {\n    intent: { type: 'string', enum: ['confirm', 'cancel', 'reschedule', 'question', 'opt_out_request', 'unclear'] },\n    confidence: { type: 'string', enum: ['high', 'medium', 'low'] },\n    clinical_concern: { type: 'boolean', description: 'True if the reply mentions symptoms, medication or anything a clinician should see.' },\n    preferred_times: { anyOf: [{ type: 'string' }, { type: 'null' }], description: 'Any days or times the patient asked for, else null.' },\n  },\n};\n\nreturn $input.all().map((item, i) => {\n  const msg = $('Normalize and screen').itemMatching(i).json;\n  const row = item.json;\n  const hasConsent = row.consent === true && !row.opted_out_at;\n  const base = { phone: msg.phone, text: msg.text, appointmentId: row.appointment_id || null, startsAt: row.starts_at || null, provider: row.provider || null, hasConsent };\n\n  // Order matters: opt-out first (always honored), then consent, then urgent, then everything else.\n  if (msg.isOptOut) return { json: { ...base, route: 'opt-out', optOutKeyword: msg.optOutKeyword }, pairedItem: { item: i } };\n  if (msg.isHelp) return { json: { ...base, route: 'none' }, pairedItem: { item: i } }; // Twilio answers HELP itself\n  if (!hasConsent) return { json: { ...base, route: 'staff', priority: msg.isUrgent ? 'urgent' : 'normal', reason: 'No recorded SMS consent. Do not text; call if appropriate.' }, pairedItem: { item: i } };\n  if (msg.isUrgent) return { json: { ...base, route: 'urgent', priority: 'urgent', reason: 'Urgent phrase in reply', replyText: 'If this is an emergency, call 911 now. Otherwise our office will call you as soon as possible. - REPLACE_WITH_PRACTICE_NAME' }, pairedItem: { item: i } };\n  if (!base.appointmentId) return { json: { ...base, route: 'staff', priority: 'normal', reason: 'No upcoming appointment found for this number' }, pairedItem: { item: i } };\n\n  return {\n    json: {\n      ...base,\n      route: 'classify',\n      claudeRequest: {\n        model: 'claude-opus-5-5',\n        max_tokens: 500,\n        system: 'You classify patient replies to an appointment reminder that said: reply C to confirm or R to reschedule. Choose one intent. If the patient asks to stop receiving texts in any wording, use opt_out_request. Mark clinical_concern true for any mention of symptoms, medications, test results or pain. Never give medical advice.',\n        messages: [{ role: 'user', content: '<reply>' + msg.text + '</reply>' }],\n        output_config: { effort: 'low', format: { type: 'json_schema', schema } },\n      },\n    },\n    pairedItem: { item: i },\n  };\n});\n"
      },
      "id": "33467769-9192-487e-ae8e-ea3926022ace",
      "name": "Gate on consent",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "3715d0b7-c42c-4b8d-acdc-e7ccbdc604e2",
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "opt-out",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "opt-out"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "88c78195-d258-447a-9228-e7ed1df60ffa",
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "urgent",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "urgent"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "aae11a38-c7f0-4d2c-b909-ab06955cd47d",
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "classify",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "classify"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "3af27224-d843-411a-a7db-20005ad70be4",
                    "leftValue": "={{ $json.route }}",
                    "rightValue": "staff",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "staff"
            }
          ]
        },
        "options": {}
      },
      "id": "60b7eec2-ce94-4a5e-a15b-bb9462264cfe",
      "name": "Route reply",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        880,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO sms_consent (phone, consent, source, opted_out_at, opt_out_keyword)\nVALUES ($1, false, 'sms_reply', now(), $2)\nON CONFLICT (phone) DO UPDATE SET consent = false, opted_out_at = now(), opt_out_keyword = EXCLUDED.opt_out_keyword;",
        "options": {
          "queryReplacement": "={{ [ $json.phone, $json.optOutKeyword || 'PLAIN_WORDS' ] }}"
        }
      },
      "id": "2d806ac5-e145-4345-a220-e29ef540e574",
      "name": "Postgres: record opt-out",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1100,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.claudeRequest) }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "bb556c87-3860-4836-b740-063a9342d213",
      "name": "Claude: classify reply",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        300
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Pull the structured JSON out of a Messages API response.\n// Opus 5.5 always thinks, so a (possibly empty) thinking block can come before the text block.\nfunction parseClaude(res) {\n  if (!res || typeof res !== 'object') return { error: 'Empty response from the Messages API' };\n  if (res.stop_reason === 'refusal') {\n    return { error: 'Model declined the request (refusal' + (res.stop_details?.category ? ': ' + res.stop_details.category : '') + ')' };\n  }\n  if (res.stop_reason === 'max_tokens') return { error: 'Response hit max_tokens before the JSON was complete' };\n  const block = (res.content || []).find((b) => b.type === 'text');\n  if (!block) return { error: 'No text block in the response' };\n  try {\n    return { data: JSON.parse(block.text) };\n  } catch (e) {\n    return { error: 'Could not parse model JSON: ' + e.message };\n  }\n}\n\nreturn $input.all().map((item, i) => {\n  const g = $('Gate on consent').itemMatching(i).json;\n  const { claudeRequest, ...ctx } = g;\n  const { data, error } = parseClaude(item.json.error ? null : item.json);\n  if (!data) return { json: { ...ctx, decision: 'staff', priority: 'normal', reason: 'Could not classify reply: ' + (item.json.error?.message || error) }, pairedItem: { item: i } };\n  if (data.intent === 'opt_out_request') return { json: { ...ctx, decision: 'opt-out', optOutKeyword: null }, pairedItem: { item: i } };\n  if (data.clinical_concern) return { json: { ...ctx, decision: 'staff', priority: 'high', reason: 'Reply mentions a clinical concern' }, pairedItem: { item: i } };\n  if (['confirm', 'cancel', 'reschedule'].includes(data.intent) && data.confidence !== 'low') {\n    return { json: { ...ctx, decision: 'schedule', action: data.intent, preferredTimes: data.preferred_times }, pairedItem: { item: i } };\n  }\n  return { json: { ...ctx, decision: 'staff', priority: 'normal', reason: 'Intent ' + data.intent + ' (' + data.confidence + ' confidence)' }, pairedItem: { item: i } };\n});\n"
      },
      "id": "90e631d0-d1e7-4065-bb25-89bc5fb22f00",
      "name": "Parse intent and decide",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "1578bf68-ce25-4292-97b1-9f28e1b503b2",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "schedule",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "update schedule"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "8cfdba22-ba36-4b7c-b4a0-c9c77ecb55e4",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "opt-out",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "opt-out"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "53d96832-29a9-4eae-b8a2-fcc8190106bf",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "staff",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "staff"
            }
          ]
        },
        "options": {}
      },
      "id": "6d17d161-ce44-4452-9f8c-adf327ac1939",
      "name": "Next step",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1540,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "REPLACE_WITH_SCHEDULING_WEBHOOK_URL",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ appointmentId: $json.appointmentId, action: $json.action, preferredTimes: $json.preferredTimes, source: \"sms_reply\" }) }}",
        "options": {
          "timeout": 20000
        }
      },
      "id": "ba75d75f-0756-4f97-8ca5-16979ac06daa",
      "name": "Scheduling system: apply action",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        200
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Expected scheduling webhook response: { ok: true, openSlots: [{ label: 'Tue Oct 20, 9:30am' }] }\nconst PRACTICE_TZ = 'America/New_York';\nreturn $input.all().map((item, i) => {\n  const d = $('Parse intent and decide').itemMatching(i).json;\n  const r = item.json;\n  const when = d.startsAt ? new Date(d.startsAt).toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: PRACTICE_TZ }) : 'your appointment';\n  let replyText;\n  if (r.ok === false) replyText = 'Thanks, we received your message. Our office will follow up to finish this.';\n  else if (d.action === 'confirm') replyText = 'Thanks, you are confirmed for ' + when + '.';\n  else if (d.action === 'cancel') replyText = 'Your appointment on ' + when + ' is cancelled. Reply R if you would like a new time.';\n  else {\n    const slots = (r.openSlots || []).slice(0, 3);\n    replyText = slots.length\n      ? 'We can offer: ' + slots.map((s, n) => (n + 1) + ') ' + s.label).join(' ') + '. Reply 1, 2 or 3.'\n      : 'Our office will call you to find a new time.';\n  }\n  return { json: { phone: d.phone, appointmentId: d.appointmentId, replyText: replyText + ' - REPLACE_WITH_PRACTICE_NAME. Reply STOP to opt out.' }, pairedItem: { item: i } };\n});\n"
      },
      "id": "3edf88bd-2ab1-4b73-bd1c-39ab4ad4d964",
      "name": "Compose reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1980,
        200
      ]
    },
    {
      "parameters": {
        "resource": "sms",
        "operation": "send",
        "from": "REPLACE_WITH_TWILIO_NUMBER",
        "to": "={{ $json.phone }}",
        "toWhatsapp": false,
        "message": "={{ $json.replyText }}",
        "options": {}
      },
      "id": "e72ba869-49d4-43de-ab5e-d587a9bd978d",
      "name": "Twilio: send reply",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        2200,
        120
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO staff_queue (phone, appointment_id, priority, reason, message, created_at)\nVALUES ($1, $2, $3, $4, $5, now());",
        "options": {
          "queryReplacement": "={{ [ $('Normalize and screen').item.json.phone, $('Gate on consent').item.json.appointmentId, $json.priority || 'normal', $json.reason || ($json.error ? 'Failed: ' + ($json.error.message || 'unknown error') : 'Needs review'), $('Normalize and screen').item.json.text ] }}"
        }
      },
      "id": "67b7b8d2-a298-40db-8f4e-7908ecda3d00",
      "name": "Postgres: add to staff queue",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2200,
        420
      ]
    }
  ],
  "connections": {
    "Twilio inbound SMS": {
      "main": [
        [
          {
            "node": "Reply to Twilio (empty TwiML)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Normalize and screen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize and screen": {
      "main": [
        [
          {
            "node": "Postgres: look up consent and appointment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Postgres: look up consent and appointment": {
      "main": [
        [
          {
            "node": "Gate on consent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gate on consent": {
      "main": [
        [
          {
            "node": "Route reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route reply": {
      "main": [
        [
          {
            "node": "Postgres: record opt-out",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Twilio: send reply",
            "type": "main",
            "index": 0
          },
          {
            "node": "Postgres: add to staff queue",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Claude: classify reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Postgres: add to staff queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude: classify reply": {
      "main": [
        [
          {
            "node": "Parse intent and decide",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Parse intent and decide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse intent and decide": {
      "main": [
        [
          {
            "node": "Next step",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Next step": {
      "main": [
        [
          {
            "node": "Scheduling system: apply action",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Postgres: record opt-out",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Postgres: add to staff queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scheduling system: apply action": {
      "main": [
        [
          {
            "node": "Compose reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Postgres: add to staff queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compose reply": {
      "main": [
        [
          {
            "node": "Twilio: send reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Twilio: send reply": {
      "main": [
        [],
        [
          {
            "node": "Postgres: add to staff queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
