{
  "name": "[PROD] sms / sarah-tool",
  "description": null,
  "active": true,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "sarah-send-sms-v3",
        "responseMode": "responseNode",
        "options": {},
        "authentication": "headerAuth"
      },
      "id": "webhook-trigger",
      "name": "ElevenLabs Tool Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        208,
        952
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "request_id",
              "name": "request_id",
              "value": "={{ $now.toMillis() + '-' + Math.random().toString(36).substring(7) }}",
              "type": "string"
            },
            {
              "id": "phone_raw",
              "name": "phone_raw",
              "value": "={{ ($json.body?.phone_number || $json.phone_number || '').toString() }}",
              "type": "string"
            },
            {
              "id": "phone_clean",
              "name": "phone_number",
              "value": "={{ ($json.body?.phone_number || $json.phone_number || '').toString().replace(/[^0-9+]/g, '') }}",
              "type": "string"
            },
            {
              "id": "first_name",
              "name": "first_name",
              "value": "={{ $json.body?.caller_name || $json.body?.first_name || $json.caller_name || $json.first_name || 'there' }}",
              "type": "string"
            },
            {
              "id": "industry",
              "name": "industry",
              "value": "={{ $json.body?.industry || $json.industry || 'service business' }}",
              "type": "string"
            },
            {
              "id": "company_name",
              "name": "company_name",
              "value": "={{ $json.body?.company_name || $json.company_name || '' }}",
              "type": "string"
            },
            {
              "id": "email",
              "name": "email",
              "value": "={{ $json.body?.email || $json.email || '' }}",
              "type": "string"
            },
            {
              "id": "template",
              "name": "template",
              "value": "={{ $json.body?.template || $json.template || 'demo' }}",
              "type": "string"
            },
            {
              "id": "timestamp",
              "name": "timestamp",
              "value": "={{ $now.toISO() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "extract-params",
      "name": "Extract & Normalize Parameters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        656,
        832
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "us-ca-format",
                    "leftValue": "={{ $json.phone_number }}",
                    "rightValue": "^\\+1[2-9][0-9]{9}$",
                    "operator": {
                      "type": "string",
                      "operation": "regex"
                    }
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "intl-format",
                    "leftValue": "={{ $json.phone_number }}",
                    "rightValue": "^\\+[1-9][0-9]{6,14}$",
                    "operator": {
                      "type": "string",
                      "operation": "regex"
                    }
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "empty-phone",
                    "leftValue": "={{ $json.phone_number }}",
                    "rightValue": "",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "validate-phone",
      "name": "Validate Phone Format",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        880,
        800
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: false, error: 'MISSING_PHONE_NUMBER', message: 'No phone number provided', request_id: $json.request_id, hint: 'Provide phone_number in E.164 format: +1XXXXXXXXXX' } }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "respond-missing-phone",
      "name": "Error: Missing Phone",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1104,
        736
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: false, error: 'INVALID_PHONE_FORMAT', message: 'Phone number format invalid', provided: $json.phone_raw, cleaned: $json.phone_number, request_id: $json.request_id, expected: '+1XXXXXXXXXX or international E.164' } }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "respond-invalid-phone",
      "name": "Error: Invalid Phone",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1104,
        928
      ]
    },
    {
      "parameters": {},
      "id": "merge-valid-phones",
      "name": "Merge Valid Paths",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1104,
        544
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "phone_number",
              "name": "phone_number",
              "value": "={{ $json.phone_number }}",
              "type": "string"
            },
            {
              "id": "first_name",
              "name": "first_name",
              "value": "={{ $json.first_name }}",
              "type": "string"
            },
            {
              "id": "company_name",
              "name": "company_name",
              "value": "={{ $json.company_name }}",
              "type": "string"
            },
            {
              "id": "request_id",
              "name": "request_id",
              "value": "={{ $json.request_id }}",
              "type": "string"
            },
            {
              "id": "sms_message",
              "name": "sms_message",
              "value": "={{ (() => { const templates = { 'demo': `Hi ${$json.first_name}! Try Wranngle's AI demo - call (888) 266-2193 and pretend you're a customer with an emergency. Then call back with a routine request and see the difference! Questions? wranngle.com`, 'recap': `Hi ${$json.first_name}! Thanks for chatting about ${$json.company_name || 'your business'}. Demo: (888) 266-2193 - try calling as if you're a customer. More info: wranngle.com`, 'followup': `Hi ${$json.first_name}! Following up on Wranngle for ${$json.industry}. Demo: (888) 266-2193. Questions? Reply here or call us.` }; return templates[$json.template] || templates['demo']; })() }}",
              "type": "string"
            },
            {
              "id": "attempt",
              "name": "attempt_number",
              "value": "=1",
              "type": "number"
            }
          ]
        },
        "options": {
          "dotNotation": true
        }
      },
      "id": "compose-message",
      "name": "Compose SMS Message",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1328,
        544
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.twilio.com/2010-04-01/Accounts/<REDACTED:twilio-account-sid>/Messages.json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "To",
              "value": "={{ $json.phone_number }}"
            },
            {
              "name": "From",
              "value": "<REDACTED:phone>"
            },
            {
              "name": "Body",
              "value": "={{ $json.sms_message }}"
            }
          ]
        },
        "options": {}
      },
      "id": "send-sms-twilio",
      "name": "Send SMS via Twilio",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1552,
        544
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "has-sid",
                    "leftValue": "={{ $json.sid }}",
                    "rightValue": "",
                    "operator": {
                      "type": "string",
                      "operation": "notEmpty"
                    }
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "is-retryable",
                    "leftValue": "={{ [30003, 30005, 30008, 30009, 30010].includes($json.code || $json.error_code) || ($json.message || '').toLowerCase().includes('temporarily') }}",
                    "rightValue": true,
                    "operator": {
                      "type": "boolean",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "check-send-result",
      "name": "Check Send Result",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1776,
        280
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "leftValue": ""
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "under-max",
              "leftValue": "={{ $('Compose SMS Message').first().json.attempt_number || 1 }}",
              "rightValue": 3,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "retry-logic",
      "name": "Retry: Check Attempts",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2000,
        496
      ]
    },
    {
      "parameters": {
        "amount": "={{ Math.pow(2, $('Compose SMS Message').first().json.attempt_number || 1) }}"
      },
      "id": "wait-before-retry",
      "name": "Wait Before Retry",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2224,
        400
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "attempt",
              "name": "attempt_number",
              "value": "={{ ($('Compose SMS Message').first().json.attempt_number || 1) + 1 }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "increment-attempt",
      "name": "Increment Attempt Counter",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2448,
        664
      ]
    },
    {
      "parameters": {
        "url": "=https://api.twilio.com/2010-04-01/Accounts/<REDACTED:twilio-account-sid>/Messages/{{ $json.sid }}.json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "options": {
          "timeout": 10000
        }
      },
      "id": "verify-delivery",
      "name": "Verify: Fetch Status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2000,
        -32
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "or",
                "conditions": [
                  {
                    "id": "is-delivered",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "delivered",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  },
                  {
                    "id": "is-sent",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "sent",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "or",
                "conditions": [
                  {
                    "id": "is-failed",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "failed",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  },
                  {
                    "id": "is-undelivered",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "undelivered",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "route-by-delivery",
      "name": "Route: Delivery Status",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2224,
        -48
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: true, status: 'delivered', message_sid: $('Send SMS via Twilio').first().json.sid, recipient: $('Extract & Normalize Parameters').first().json.phone_number, first_name: $('Extract & Normalize Parameters').first().json.first_name, company_name: $('Extract & Normalize Parameters').first().json.company_name, request_id: $('Extract & Normalize Parameters').first().json.request_id, delivery_status: $json.status } }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "respond-success",
      "name": "Success: Delivered",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2448,
        -128
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: true, status: 'pending', note: 'Message accepted, delivery not yet confirmed', message_sid: $('Send SMS via Twilio').first().json.sid, recipient: $('Extract & Normalize Parameters').first().json.phone_number, request_id: $('Extract & Normalize Parameters').first().json.request_id, delivery_status: $json.status || 'unknown' } }}",
        "options": {
          "responseCode": 202
        }
      },
      "id": "respond-pending",
      "name": "Warning: Pending",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2448,
        256
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "troubleshoot",
              "name": "troubleshooting",
              "value": "={{ (() => { const code = $json.error_code; const map = { 30032: 'CRITICAL: Toll-free number not verified for A2P. Complete verification at Twilio console.', 21211: 'Invalid phone format - needs E.164 (+1XXXXXXXXXX)', 21614: 'Landline or VoIP number - not SMS-capable', 30003: 'Carrier unreachable - temporary, retry later', 30004: 'Blocked by carrier spam filter', 30005: 'Number does not exist', 30007: 'Carrier violation - check message content' }; return map[code] || 'Check https://twilio.com/docs/api/errors/' + code; })() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "build-error-response",
      "name": "Build: Error Details",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2448,
        64
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: false, error: 'DELIVERY_FAILED', message_sid: $('Send SMS via Twilio').first().json.sid, recipient: $('Extract & Normalize Parameters').first().json.phone_number, error_code: $json.error_code, error_message: $json.error_message, request_id: $('Extract & Normalize Parameters').first().json.request_id, troubleshooting: $json.troubleshooting } }}",
        "options": {
          "responseCode": 500
        }
      },
      "id": "respond-delivery-failed",
      "name": "Error: Delivery Failed",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2672,
        64
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: false, error: 'MAX_RETRIES_EXCEEDED', message: 'Failed after 3 attempts', last_error_code: $json.code || $json.error_code, last_error_message: $json.message || $json.error_message, recipient: $('Extract & Normalize Parameters').first().json.phone_number, request_id: $('Extract & Normalize Parameters').first().json.request_id } }}",
        "options": {
          "responseCode": 500
        }
      },
      "id": "respond-max-retries",
      "name": "Error: Max Retries",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2224,
        592
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { success: false, error: 'TWILIO_PERMANENT_ERROR', error_code: $json.code || $json.error_code || 'UNKNOWN', error_message: $json.message || $json.error_message || 'Permanent failure - do not retry', recipient: $('Extract & Normalize Parameters').first().json.phone_number, request_id: $('Extract & Normalize Parameters').first().json.request_id, troubleshooting: (() => { const code = $json.code || $json.error_code; const map = { 30032: 'Toll-free not A2P verified - complete at Twilio console', 21211: 'Invalid phone format', 21614: 'Not SMS-capable (landline)', 21610: 'Unsubscribed recipient' }; return map[code] || 'Check Twilio error docs'; })() } }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "respond-permanent-error",
      "name": "Error: Permanent Failure",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2000,
        688
      ]
    }
  ],
  "connections": {
    "ElevenLabs Tool Webhook": {
      "main": [
        [
          {
            "node": "Extract & Normalize Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract & Normalize Parameters": {
      "main": [
        [
          {
            "node": "Validate Phone Format",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Phone Format": {
      "main": [
        [
          {
            "node": "Merge Valid Paths",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Valid Paths",
            "type": "main",
            "index": 1
          }
        ],
        [
          {
            "node": "Error: Missing Phone",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error: Invalid Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Valid Paths": {
      "main": [
        [
          {
            "node": "Compose SMS Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compose SMS Message": {
      "main": [
        [
          {
            "node": "Send SMS via Twilio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send SMS via Twilio": {
      "main": [
        [
          {
            "node": "Check Send Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check Send Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Send Result": {
      "main": [
        [
          {
            "node": "Verify: Fetch Status",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Retry: Check Attempts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error: Permanent Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry: Check Attempts": {
      "main": [
        [
          {
            "node": "Wait Before Retry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error: Max Retries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before Retry": {
      "main": [
        [
          {
            "node": "Increment Attempt Counter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increment Attempt Counter": {
      "main": [
        [
          {
            "node": "Send SMS via Twilio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify: Fetch Status": {
      "main": [
        [
          {
            "node": "Route: Delivery Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route: Delivery Status": {
      "main": [
        [
          {
            "node": "Success: Delivered",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build: Error Details",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Warning: Pending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build: Error Details": {
      "main": [
        [
          {
            "node": "Error: Delivery Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false
  },
  "tags": [
    "ALPHA"
  ]
}