{
  "name": "Dead Lead Reactivation",
  "nodes": [
    {
      "parameters": {
        "content": "## Dead lead reactivation\n\nTurns an old list of leads and past clients into booked calls with personalised SMS. Run it in four stages.\n\n**Setup**\n1. Import your export into a sheet tab **Contacts**: first_name, phone, email, last_interaction (date), service, lifetime_value, consent (yes/no), opted_out, segment, status.\n2. Connect Google Sheets, OpenAI and Twilio. Paste a Slack webhook into **Alert Sales**.\n3. **Stage 1** cleans and segments. **Stage 2** drafts one opener per segment into the **Templates** tab. Read them and type yes in the approved column for the ones you're happy with.\n4. **Stage 3** sends 50 an hour to protect your number. **Stage 4** handles replies: point Twilio's messaging webhook at **Reply Received**.\n\nOnly message people with documented consent. Every message includes STOP.",
        "height": 520,
        "width": 460
      },
      "id": "ce3dccb7-94b8-4849-b1c6-8e4596ae2b91",
      "name": "Note 0",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        -320
      ]
    },
    {
      "parameters": {},
      "id": "ab898c52-8755-417d-b262-dfe448db2006",
      "name": "Stage 1: Clean and Segment",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "options": {}
      },
      "id": "25d492e9-8274-4632-9cdc-c37a97ec4d30",
      "name": "Get Contacts",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normalise an Australian or international number to E.164 (+61...). Change DEFAULT_CC for your country.\nconst DEFAULT_CC = '61';\nfunction e164(raw) {\n  let d = String(raw || '').replace(/[^\\d+]/g, '');\n  if (d.startsWith('+')) return d;\n  if (d.startsWith('00')) return '+' + d.slice(2);\n  if (d.startsWith('0')) return '+' + DEFAULT_CC + d.slice(1);\n  return d ? '+' + d : '';\n}\nconst seen = new Set(), out = [];\nfor (const { json: c } of $input.all()) {\n  const phone = e164(c.phone);\n  if (!phone || seen.has(phone)) continue;            // drop blanks and duplicates\n  seen.add(phone);\n  if (c.opted_out === 'yes' || c.consent !== 'yes') continue;   // no consent, no message\n  const months = c.last_interaction ? (Date.now() - Date.parse(c.last_interaction)) / 2.63e9 : 99;\n  const recency = months < 6 ? 'recent' : months < 18 ? 'lapsed' : 'cold';\n  const value = Number(c.lifetime_value) >= 1000 ? 'high-value' : 'standard';\n  out.push({ json: { phone, segment: `${recency} | ${c.service || 'general'} | ${value}`, status: 'ready' } });\n}\nreturn out;"
      },
      "id": "1087793b-1e7e-42ee-b889-d1220bb47d5a",
      "name": "Clean and Segment",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "phone": "={{ $json.phone }}",
            "segment": "={{ $json.segment }}",
            "status": "={{ $json.status }}"
          },
          "matchingColumns": [
            "phone"
          ],
          "schema": [
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "segment",
              "displayName": "segment",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "c205dad4-6a3b-4b31-91a7-28b847c9dd4a",
      "name": "Save Segments",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {},
      "id": "0cc0b169-7aa4-482f-8b78-20fff6d06f6a",
      "name": "Stage 2: Draft Openers",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "options": {}
      },
      "id": "3ce3ef34-7c8e-43d4-b89c-d43d80d833bb",
      "name": "Get Segmented Contacts",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        220,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "const segs = [...new Set($input.all().map(i => i.json.segment).filter(Boolean))];\nreturn segs.map(segment => ({ json: { segment } }));"
      },
      "id": "1ca25eeb-8bbb-425c-9418-ca329c141061",
      "name": "Unique Segments",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        260
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'gpt-4.1', messages: [ { role: 'system', content: 'You write short, warm SMS openers to re-engage past leads and clients of YOUR BUSINESS NAME (YOUR ONE-LINE DESCRIPTION). Under 240 characters, sound like a real person, use {first_name} as a placeholder, ask one easy question, no links, no pressure. End with: Reply STOP to opt out.' }, { role: 'user', content: 'Segment (recency | service | value): ' + $json.segment } ] }) }}",
        "options": {}
      },
      "id": "09c81a18-a529-4617-bd9b-f048ba2c6563",
      "name": "Write Opener",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        260
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Templates"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "segment": "={{ $('Unique Segments').item.json.segment }}",
            "message": "={{ $json.choices[0].message.content }}",
            "approved": ""
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "segment",
              "displayName": "segment",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "message",
              "displayName": "message",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "approved",
              "displayName": "approved",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "ce34efc6-43f5-47e6-a6f3-bbd7330a8adc",
      "name": "Save Draft for Approval",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        880,
        260
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 1
            }
          ]
        }
      },
      "id": "4fa28bca-4bb2-44f7-8b0c-14cd0b067f77",
      "name": "Stage 3: Send Hourly",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        520
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Templates"
        },
        "options": {}
      },
      "id": "5ed9b9cf-8c88-4f4e-ba65-f46bf748f291",
      "name": "Get Approved Templates",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        220,
        520
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "status",
              "lookupValue": "ready"
            }
          ]
        },
        "options": {}
      },
      "id": "fcbd66ff-3410-4e6c-a3f0-b816a563ec64",
      "name": "Get Ready Contacts",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        440,
        520
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "const BATCH = 50;   // per hour, protects your number's reputation\nconst templates = {};\nfor (const { json: t } of $('Get Approved Templates').all()) if (String(t.approved).toLowerCase() === 'yes') templates[t.segment] = t.message;\nreturn $input.all().map(i => i.json).filter(c => c.opted_out !== 'yes' && templates[c.segment]).slice(0, BATCH)\n  .map(c => ({ json: { phone: c.phone, message: templates[c.segment].replaceAll('{first_name}', c.first_name || 'there') } }));"
      },
      "id": "0a7e8508-56c3-4b83-b715-ac19d8925eb9",
      "name": "Match Template (max 50)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        520
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "from": "YOUR_TWILIO_NUMBER",
        "to": "={{ $json.phone }}",
        "message": "={{ $json.message }}",
        "options": {}
      },
      "id": "e0913daa-f6f8-4d6e-bf94-df5b8f56a3e4",
      "name": "Send SMS",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        880,
        520
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "phone": "={{ $('Match Template (max 50)').item.json.phone }}",
            "status": "sent"
          },
          "matchingColumns": [
            "phone"
          ],
          "schema": [
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "adbbe743-e8fd-492c-92e2-2d516156020a",
      "name": "Mark Sent",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1100,
        520
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "reactivation-reply",
        "options": {},
        "responseMode": "responseNode"
      },
      "id": "70af04d8-57cc-44ca-a0e2-ab6aae367915",
      "name": "Reply Received",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        820
      ],
      "webhookId": "f67ef0bd-bcd0-4a5f-9442-63bb18b38580"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<Response></Response>",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        }
      },
      "id": "92e86196-61cc-47b4-9b37-d41b3352301d",
      "name": "Empty Reply",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        220,
        720
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "91da5f56-35b8-4797-a55b-8a5f3500c5c2",
              "leftValue": "={{ $json.body.Body.trim() }}",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "rightValue": "/^(stop|stopall|unsubscribe|cancel|end|quit)$/i"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d9f7a136-0dce-40bb-a12a-192e539707ec",
      "name": "Is STOP?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        220,
        900
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "phone": "={{ $json.body.From }}",
            "opted_out": "yes",
            "status": "opted out"
          },
          "matchingColumns": [
            "phone"
          ],
          "schema": [
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "opted_out",
              "displayName": "opted_out",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "f1ae0878-c0c4-4a2a-9533-f0f79017ee64",
      "name": "Mark Opted Out",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        440,
        820
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'gpt-4.1-mini', response_format: { type: 'json_object' }, messages: [ { role: 'system', content: 'Classify this SMS reply to a re-engagement message. Return JSON {\"intent\":\"hot|warm|not_interested|question\",\"reply\":\"a short, friendly response under 240 characters\"}. hot = wants to book or buy now.' }, { role: 'user', content: $json.body.Body } ] }) }}",
        "options": {}
      },
      "id": "3c3f674c-053e-411b-b92c-c07ffdc4c6db",
      "name": "Read Reply",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        1000
      ]
    },
    {
      "parameters": {
        "jsCode": "const r = JSON.parse($json.choices[0].message.content);\nreturn [{ json: { ...r, phone: $('Reply Received').item.json.body.From, text: $('Reply Received').item.json.body.Body } }];",
        "mode": "runOnceForEachItem"
      },
      "id": "12c773be-1c11-48a4-a58b-f468054c522b",
      "name": "Parse Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        1000
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "5deac922-e04f-40a3-a379-38a881dc8a38",
              "leftValue": "={{ $json.intent }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "hot"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3f5da8f6-6779-459d-a3c7-741dcb586262",
      "name": "Hot?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        880,
        1000
      ]
    },
    {
      "parameters": {
        "from": "YOUR_TWILIO_NUMBER",
        "to": "={{ $json.phone }}",
        "message": "=Great to hear! Grab a time that suits here: YOUR_BOOKING_LINK",
        "options": {}
      },
      "id": "2f1bd752-ab53-41f2-97d0-2e570b7f5a5b",
      "name": "Send Booking Link",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        1100,
        920
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "YOUR_SLACK_WEBHOOK_URL",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: `🔥 Reactivated lead ${$('Parse Reply').item.json.phone} replied: \"${$('Parse Reply').item.json.text}\"` }) }}",
        "options": {}
      },
      "id": "9aac3c65-2781-41db-9492-53e53aefde30",
      "name": "Alert Sales",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        920
      ]
    },
    {
      "parameters": {
        "from": "YOUR_TWILIO_NUMBER",
        "to": "={{ $json.phone }}",
        "message": "={{ $json.reply }}",
        "options": {}
      },
      "id": "58210422-c5d8-4baa-acb4-bc4fe85131a6",
      "name": "Send AI Reply",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        1100,
        1100
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "phone": "={{ $('Parse Reply').item.json.phone }}",
            "status": "=replied: {{ $('Parse Reply').item.json.intent }}"
          },
          "matchingColumns": [
            "phone"
          ],
          "schema": [
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "e7231283-2018-484d-a21b-2edfbd5fa985",
      "name": "Log Reply",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1540,
        1000
      ]
    }
  ],
  "connections": {
    "Stage 1: Clean and Segment": {
      "main": [
        [
          {
            "node": "Get Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Contacts": {
      "main": [
        [
          {
            "node": "Clean and Segment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean and Segment": {
      "main": [
        [
          {
            "node": "Save Segments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stage 2: Draft Openers": {
      "main": [
        [
          {
            "node": "Get Segmented Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Segmented Contacts": {
      "main": [
        [
          {
            "node": "Unique Segments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unique Segments": {
      "main": [
        [
          {
            "node": "Write Opener",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Opener": {
      "main": [
        [
          {
            "node": "Save Draft for Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stage 3: Send Hourly": {
      "main": [
        [
          {
            "node": "Get Approved Templates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Approved Templates": {
      "main": [
        [
          {
            "node": "Get Ready Contacts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Ready Contacts": {
      "main": [
        [
          {
            "node": "Match Template (max 50)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match Template (max 50)": {
      "main": [
        [
          {
            "node": "Send SMS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send SMS": {
      "main": [
        [
          {
            "node": "Mark Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply Received": {
      "main": [
        [
          {
            "node": "Empty Reply",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is STOP?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is STOP?": {
      "main": [
        [
          {
            "node": "Mark Opted Out",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Read Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Reply": {
      "main": [
        [
          {
            "node": "Parse Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Reply": {
      "main": [
        [
          {
            "node": "Hot?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hot?": {
      "main": [
        [
          {
            "node": "Send Booking Link",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send AI Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Booking Link": {
      "main": [
        [
          {
            "node": "Alert Sales",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert Sales": {
      "main": [
        [
          {
            "node": "Log Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send AI Reply": {
      "main": [
        [
          {
            "node": "Log Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}