{
  "nodes": [
    {
      "id": "sticky-d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1180,
        0
      ],
      "parameters": {
        "content": "## 4. Estimate follow-ups\n- When an estimate goes out, send this URL the customer's name, phone, and email. A JobTread automation or the office can do it\n- Day 2: a check-in text. Day 5: a final email\n- Each touch checks first whether the customer already responded, declined, or signed\n- Add more touches by copying a Wait + check + send block",
        "height": 280,
        "width": 460,
        "color": 6
      }
    },
    {
      "id": "sticky-e",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1180,
        460
      ],
      "parameters": {
        "content": "## 5. Stop switch\n- Anything that hits this URL turns off follow-ups for that customer\n- Point Twilio inbound SMS here so a reply stops the sequence on its own\n- JobTread status changes (declined or signed) can call it too\n- Works once the workflow is active; it remembers who to skip",
        "height": 260,
        "width": 460,
        "color": 3
      }
    },
    {
      "id": "webhook-estimate",
      "name": "Estimate was sent",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -660,
        100
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "stonebriar-estimate-sent",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "code-tidy-estimate",
      "name": "Tidy up the estimate details",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -440,
        100
      ],
      "parameters": {
        "jsCode": "const raw = $json.body && typeof $json.body === 'object' ? $json.body : $json;\n\nconst pick = (...keys) => {\n  for (const k of keys) {\n    const v = raw[k];\n    if (v !== undefined && v !== null && String(v).trim() !== '') return String(v).trim();\n  }\n  return '';\n};\n\nconst name = pick('name', 'fullName', 'customerName') || 'there';\nconst email = pick('email', 'customerEmail').toLowerCase();\n\nlet phone = pick('phone', 'customerPhone').replace(/[^\\d+]/g, '');\nif (phone && !phone.startsWith('+')) {\n  phone = phone.length === 10 ? `+1${phone}` : `+${phone}`;\n}\n\nreturn {\n  name,\n  firstName: name.split(' ')[0],\n  email,\n  phone,\n};"
      }
    },
    {
      "id": "respond-estimate",
      "name": "Tell JobTread we got it",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -220,
        100
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\n  \"status\": \"received\"\n}",
        "options": {}
      }
    },
    {
      "id": "wait-2d",
      "name": "Wait 2 days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        0,
        100
      ],
      "parameters": {
        "amount": 2,
        "unit": "days"
      }
    },
    {
      "id": "code-check-1",
      "name": "Did they already respond or sign?",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        100
      ],
      "parameters": {
        "jsCode": "// The stop switch (section 5) writes names into this list. If the customer\n// already replied, declined, or signed, the sequence ends quietly.\nconst stop = $getWorkflowStaticData('global').stopList || {};\nconst phoneKey = ($json.phone || '').replace(/\\D/g, '');\nconst emailKey = ($json.email || '').toLowerCase();\nconst stopped = Boolean((phoneKey && stop[phoneKey]) || (emailKey && stop[emailKey]));\nreturn { ...$json, stopped };"
      }
    },
    {
      "id": "if-still-waiting",
      "name": "Still waiting on them?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "not-stopped-1",
              "leftValue": "={{ $json.stopped }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "twilio-fu1",
      "name": "Text the first follow-up",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        660,
        100
      ],
      "parameters": {
        "resource": "sms",
        "operation": "send",
        "from": "+15550149000",
        "to": "={{ $json.phone }}",
        "message": "=Hi {{ $json.firstName }}, it's Stonebriar Custom Builders. Wanted to make sure the estimate made it to you. Any questions, just text back.",
        "options": {}
      },
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "wait-3d",
      "name": "Wait 3 more days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        880,
        100
      ],
      "parameters": {
        "amount": 3,
        "unit": "days"
      }
    },
    {
      "id": "code-check-2",
      "name": "Check again before the last touch",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        100
      ],
      "parameters": {
        "jsCode": "const stop = $getWorkflowStaticData('global').stopList || {};\nconst phoneKey = ($json.phone || '').replace(/\\D/g, '');\nconst emailKey = ($json.email || '').toLowerCase();\nconst stopped = Boolean((phoneKey && stop[phoneKey]) || (emailKey && stop[emailKey]));\nreturn { ...$json, stopped };"
      }
    },
    {
      "id": "if-no-answer",
      "name": "Still no answer?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1320,
        100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "not-stopped-2",
              "leftValue": "={{ $json.stopped }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "gmail-fu2",
      "name": "Email the final follow-up",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1540,
        100
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "Checking in on your estimate",
        "emailType": "text",
        "message": "=Hi {{ $json.firstName }},\n\nChecking in one more time on the estimate we sent over.\n\nIf the timing isn't right, no problem. Just let us know where you stand and we'll go from there.\n\nThanks,\nStonebriar Custom Builders",
        "options": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "webhook-stop",
      "name": "Customer replied, declined, or signed",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -660,
        560
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "stonebriar-stop-followups",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "code-stop",
      "name": "Turn off their follow-ups",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -440,
        560
      ],
      "parameters": {
        "jsCode": "// Accepts a plain {phone, email} post, a Twilio inbound SMS (From field),\n// or a JobTread automation call. Whoever it is, their follow-ups stop.\nconst raw = $json.body && typeof $json.body === 'object' ? $json.body : $json;\n\nconst phone = String(raw.phone || raw.From || raw.from || '').replace(/\\D/g, '');\nconst email = String(raw.email || raw.customerEmail || '').toLowerCase();\n\nconst data = $getWorkflowStaticData('global');\ndata.stopList = data.stopList || {};\nif (phone) data.stopList[phone] = true;\nif (email) data.stopList[email] = true;\n\nreturn { status: 'stopped', phone, email };"
      }
    },
    {
      "id": "respond-stop",
      "name": "Confirm it is handled",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        -220,
        560
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\n  \"status\": \"stopped\"\n}",
        "options": {}
      }
    }
  ],
  "connections": {
    "Estimate was sent": {
      "main": [
        [
          {
            "node": "Tidy up the estimate details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tidy up the estimate details": {
      "main": [
        [
          {
            "node": "Tell JobTread we got it",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tell JobTread we got it": {
      "main": [
        [
          {
            "node": "Wait 2 days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 2 days": {
      "main": [
        [
          {
            "node": "Did they already respond or sign?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Did they already respond or sign?": {
      "main": [
        [
          {
            "node": "Still waiting on them?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Still waiting on them?": {
      "main": [
        [
          {
            "node": "Text the first follow-up",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Text the first follow-up": {
      "main": [
        [
          {
            "node": "Wait 3 more days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 more days": {
      "main": [
        [
          {
            "node": "Check again before the last touch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check again before the last touch": {
      "main": [
        [
          {
            "node": "Still no answer?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Still no answer?": {
      "main": [
        [
          {
            "node": "Email the final follow-up",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Customer replied, declined, or signed": {
      "main": [
        [
          {
            "node": "Turn off their follow-ups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Turn off their follow-ups": {
      "main": [
        [
          {
            "node": "Confirm it is handled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}