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 →
{
"name": "WF-NW-03, Followup SMS Only (touch 2-4)",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9-20 * * *"
}
]
}
},
"id": "trg-9et",
"name": "Every Hour 9 AM - 8 PM ET",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-208,
-304
]
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 10
}
]
}
},
"id": "trg-10et",
"name": "Send 10 AM ET (CT zone)",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-208,
-96
],
"disabled": true
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 11
}
]
}
},
"id": "trg-11et",
"name": "Send 11 AM ET (MT zone)",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-208,
112
],
"disabled": true
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 12
}
]
}
},
"id": "trg-12et",
"name": "Send 12 PM ET (PT/AZ zone)",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-208,
304
],
"disabled": true
},
{
"parameters": {
"url": "https://YOUR_CONVEX_DEPLOYMENT/sms-batch?limit=100",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"timeout": 60000
}
},
"id": "http-get-sms-batch",
"name": "GET Convex /sms-batch",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
112,
-304
],
"retryOnFail": true,
"maxTries": 2,
"waitBetweenTries": 3000
},
{
"parameters": {
"jsCode": "// WF-NW-03 gate + STICKY sender. FOLLOWUPS ONLY (touch 2,3,4).\n// Original v1 templates (reverted from v2.1).\nconst SEND_REAL = true;\nconst MAX_TOUCHES = 4;\nconst BATCH_LIMIT = 5;\nconst RECENT_SEND_WINDOW_MS = 5 * 60 * 1000;\nconst GAP_DAYS = [0, 1, 3, 2];\nconst QUIET_START = 8, QUIET_END = 21;\nconst NPA_TZ = {\"405\":\"America/Chicago\",\"410\":\"America/New_York\",\"901\":\"America/Chicago\",\"520\":\"America/Phoenix\",\"502\":\"America/New_York\",\"915\":\"America/Chicago\",\"989\":\"America/New_York\",\"313\":\"America/New_York\",\"480\":\"America/Phoenix\",\"720\":\"America/Denver\",\"702\":\"America/Los_Angeles\",\"303\":\"America/Denver\",\"404\":\"America/New_York\",\"678\":\"America/New_York\",\"414\":\"America/Chicago\",\"612\":\"America/Chicago\",\"443\":\"America/New_York\",\"763\":\"America/Chicago\",\"812\":\"America/Chicago\",\"256\":\"America/Chicago\",\"617\":\"America/New_York\",\"623\":\"America/Phoenix\",\"539\":\"America/Chicago\",\"505\":\"America/Los_Angeles\",\"330\":\"America/New_York\",\"903\":\"America/New_York\",\"918\":\"America/Chicago\",\"205\":\"America/Chicago\",\"615\":\"America/Chicago\",\"704\":\"America/Denver\",\"983\":\"America/New_York\",\"770\":\"America/New_York\",\"618\":\"America/Chicago\",\"857\":\"America/New_York\",\"806\":\"America/Chicago\",\"508\":\"America/New_York\",\"239\":\"America/New_York\",\"952\":\"America/Chicago\",\"229\":\"America/Chicago\",\"301\":\"America/New_York\",\"402\":\"America/New_York\",\"725\":\"America/Los_Angeles\",\"765\":\"America/Chicago\",\"223\":\"America/Chicago\",\"662\":\"America/New_York\",\"706\":\"America/New_York\",\"240\":\"America/New_York\",\"347\":\"America/New_York\",\"212\":\"America/New_York\",\"646\":\"America/New_York\",\"516\":\"America/New_York\",\"332\":\"America/New_York\",\"929\":\"America/New_York\",\"917\":\"America/New_York\",\"718\":\"America/New_York\",\"252\":\"America/New_York\",\"507\":\"America/Chicago\",\"512\":\"America/Chicago\",\"501\":\"America/Chicago\",\"737\":\"America/Chicago\",\"517\":\"America/New_York\",\"614\":\"America/New_York\",\"406\":\"America/New_York\",\"979\":\"America/New_York\",\"828\":\"America/New_York\",\"920\":\"America/New_York\",\"609\":\"America/New_York\",\"380\":\"America/New_York\",\"323\":\"America/New_York\",\"972\":\"America/Chicago\",\"214\":\"America/Chicago\",\"570\":\"America/Chicago\",\"469\":\"America/Chicago\",\"503\":\"America/Chicago\",\"817\":\"America/Chicago\",\"945\":\"America/Chicago\",\"682\":\"America/Chicago\",\"317\":\"America/New_York\",\"424\":\"America/New_York\",\"215\":\"America/New_York\",\"315\":\"America/New_York\",\"267\":\"America/New_York\",\"610\":\"America/New_York\",\"272\":\"America/New_York\",\"602\":\"America/Phoenix\",\"760\":\"America/Phoenix\",\"970\":\"America/Phoenix\",\"210\":\"America/Chicago\",\"726\":\"America/Chicago\",\"346\":\"America/Chicago\",\"858\":\"America/Chicago\",\"281\":\"America/Chicago\",\"832\":\"America/Chicago\",\"713\":\"America/Chicago\",\"254\":\"America/Chicago\"};\nfunction npaTz(p){ const d=String(p||'').replace(/\\D/g,'').slice(-10); return NPA_TZ[d.slice(0,3)] || 'America/Los_Angeles'; }\nfunction localHour(tz){ try { return Number(new Intl.DateTimeFormat('en-US',{hour:'numeric',hour12:false,timeZone:tz||'America/New_York'}).format(new Date())); } catch(e){ return new Date().getHours(); } }\nfunction e164(p){ const d=String(p||'').replace(/\\D/g,''); return d.length===10?'+1'+d:(d.length===11&&d[0]==='1')?'+'+d:null; }\n\nconst FOLLOWUPS = {\n 1: \"Hey, still want me to send that site I made for {biz}? It's ready whenever.\",\n 2: \"last try I promise, want me to drop the link for {biz}? Takes 10 sec to glance at.\",\n 3: \"alright I'll assume you're booked solid, going with someone else. cya later alligator \ud83d\udc0a\"\n};\n\nconst now = Date.now();\nconst out = [];\nfor (const item of $input.all()) {\n const l = item.json;\n if (l.status && l.status !== 'in_close') continue;\n const n = l.sms_count == null ? 0 : l.sms_count;\n if (n < 1) continue;\n if (n >= MAX_TOUCHES) continue;\n const gap = GAP_DAYS[n] == null ? 2 : GAP_DAYS[n];\n if (l.last_sms_at) {\n const msSinceLast = now - l.last_sms_at;\n if (msSinceLast < gap * 86400000) continue;\n if (msSinceLast < RECENT_SEND_WINDOW_MS) continue;\n }\n const h = localHour(l.timezone || npaTz(l.phone));\n if (h < QUIET_START || h >= QUIET_END) continue;\n const remote = e164(l.phone);\n if (!remote || !l.close_lead_id || !l.close_contact_id) continue;\n const senderPhone = l.assigned_sender_phone || null;\n const senderLabel = l.assigned_sender_label || null;\n const text = (FOLLOWUPS[n] || '').replace(/\\{biz\\}/g, l.business_name || 'your shop');\n if (!text) continue;\n out.push({ json: {\n id: l.id,\n lead_id: l.close_lead_id,\n contact_id: l.close_contact_id,\n local_phone: senderPhone,\n sender_label: senderLabel,\n sender_phone: senderPhone,\n needs_sender_backfill: !senderPhone,\n remote_phone: remote,\n text: text,\n sms_count: n,\n send_real: SEND_REAL,\n }});\n if (out.length >= BATCH_LIMIT) break;\n}\nreturn out;\n"
},
"id": "code-gate",
"name": "Gate + build SMS",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
288,
-304
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-sendreal",
"leftValue": "={{ $json.send_real }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-sendreal",
"name": "SEND_REAL?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
672,
0
]
},
{
"parameters": {
"options": {}
},
"id": "split-batches",
"name": "Loop Batches (10)",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
656,
-288
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.close.com/api/v1/activity/sms/",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ lead_id: $json.lead_id, contact_id: $json.contact_id, direction: 'outbound', status: 'outbox', local_phone: $json.sticky_sender_phone, remote_phone: $json.remote_phone, text: $json.text }) }}",
"options": {}
},
"id": "http-close-sms",
"name": "Close SMS, Followup",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1632,
32
],
"retryOnFail": false,
"maxTries": 1,
"waitBetweenTries": 2000,
"onError": "continueRegularOutput"
},
{
"parameters": {
"method": "POST",
"url": "https://YOUR_CONVEX_DEPLOYMENT/record-sms",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ id: $('Gate + build SMS').item.json.id }) }}",
"options": {}
},
"id": "http-record-sms",
"name": "Convex /record-sms",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2208,
-256
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"method": "PUT",
"url": "=https://api.close.com/api/v1/lead/{{ $('Gate + build SMS').item.json.lead_id }}/",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ 'custom.cf_PcdRHPUTgoPxgqczBOFs5eDrhVFAqYcez63lQgCJyJK': ($('Gate + build SMS').item.json.sms_count || 0) + 1 }) }}",
"options": {}
},
"id": "http-close-smscount",
"name": "Close PUT cf SMS Count",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2368,
-256
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"unit": "seconds"
},
"id": "wait-throttle",
"name": "Wait 1.5s (rate-limit)",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
1472,
-288
]
},
{
"parameters": {
"jsCode": "const sent = $input.all().length;\nconst gate = $('Gate + build SMS').all();\nif (sent > 0) {\n const lines = gate.slice(0, 15).map(g => '\u2022 ' + (g.json.remote_phone || '') + ' (touch ' + (g.json.sms_count + 1) + ')' + (g.json.text ? (', ' + String(g.json.text).slice(0, 50)) : '')).join('\\n');\n await this.helpers.httpRequest({\n method: 'POST',\n url: 'YOUR_SLACK_WEBHOOK',\n body: { text: '\ud83d\udce4 *WF-NW-03 Followup* sent ' + sent + ' SMS:\\n' + lines + (gate.length > 15 ? ('\\n\u2026and ' + (gate.length - 15) + ' more') : '') },\n json: true\n });\n}\nreturn $input.all();"
},
"id": "code-slack",
"name": "Slack: send summary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
608,
-464
]
},
{
"parameters": {
"jsCode": "return $input.all().map(i => ({ json: { dry_run: true, would_send_to: i.json.remote_phone, touch: i.json.sms_count + 1, text: i.json.text, lead_id: i.json.lead_id } }));"
},
"id": "code-dryrun",
"name": "No-Op (dry-run log)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
880,
192
]
},
{
"parameters": {
"method": "POST",
"url": "https://YOUR_CONVEX_DEPLOYMENT/sms-activities/log",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ (function(){ var lead = $('Gate + build SMS').item.json; var closeResp = $json; return JSON.stringify({ lead_id: lead.id, close_lead_id: lead.close_lead_id, stage: Number(lead.sms_count) || 0, direction: 'outbound', text: closeResp.text || '', sent_at: Date.now(), close_activity_id: closeResp.id, status: closeResp.status || 'outbox', sender_label: lead.sender_label, sender_phone: lead.sender_phone }); })() }}",
"options": {}
},
"id": "http-log-sms-followup",
"name": "Convex /sms-activities/log (followup)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2048,
-256
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"url": "=https://api.close.com/api/v1/lead/{{ $json.lead_id }}/?_fields=id,status_id,status_label,display_name,custom.cf_CpsUzGm98lRUY7ah9LUhfFGGC6xwYE7ZYsBj1DcPgTf",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {
"timeout": 15000
}
},
"id": "http-close-get-lead",
"name": "Close GET lead status",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
848,
-272
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c-hh",
"leftValue": "={{ $json.status_id }}",
"rightValue": "stat_AVA3WALf67nRg7DZXeNckA5RS0ifVRtcBJqeReNs9D6",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "c-oo",
"leftValue": "={{ $json.status_id }}",
"rightValue": "stat_KztVZXjQ5BW8OzP9Mk4hSzvAvuLilm0ClxMlaOQwR9R",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "c-rp",
"leftValue": "={{ $json.status_id }}",
"rightValue": "stat_0QPQkN2ZspV6SMnlH4B0ySCbE10waV9tWBbdCXrQ43m",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "c-ds",
"leftValue": "={{ $json.status_id }}",
"rightValue": "stat_JkhhQYXcbMQXXixqSBlmq35po9YWwgD0cn2XnNXP8z2",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "c-landline",
"leftValue": "={{ ($json['custom.cf_CpsUzGm98lRUY7ah9LUhfFGGC6xwYE7ZYsBj1DcPgTf'] || '').toLowerCase() }}",
"rightValue": "landline",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "or"
},
"options": {}
},
"id": "if-status-skip",
"name": "Status in skip list?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1024,
-272
]
},
{
"parameters": {
"method": "POST",
"url": "YOUR_SLACK_WEBHOOK",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ text: '\ud83d\uded1 *FOLLOWUP SKIPPED* \u00b7 ' + ($json.display_name || $('Gate + build SMS').item.json.lead_id || '?') + ' \u00b7 status: ' + ($json.status_label || '?') + ' \u00b7 no more automated SMS' }) }}",
"options": {}
},
"id": "slack-status-skip",
"name": "Slack: Followup Skipped (Status)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1216,
-288
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "c-sent",
"leftValue": "={{ !!$json.id }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-followup-sent",
"name": "Followup Sent OK?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1840,
-240
]
},
{
"parameters": {
"method": "POST",
"url": "YOUR_SLACK_WEBHOOK",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ text: '\u274c *FOLLOWUP SMS FAILED* \u00b7 lead ' + ($('Gate + build SMS').item.json.lead_id || '?') + ' \u00b7 touch ' + (($('Gate + build SMS').item.json.sms_count || 0) + 1) + ' \u00b7 error: ' + (($json.error && $json.error.message) || 'unknown') }) }}",
"options": {}
},
"id": "slack-followup-fail",
"name": "Slack: Followup Failed",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1680,
-288
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"url": "=https://api.close.com/api/v1/activity/sms/?lead_id={{ $('Gate + build SMS').item.json.lead_id }}&_order_by=date_created&_limit=50&_fields=id,direction,status,text,date_created,local_phone",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {
"timeout": 15000
}
},
"id": "http-close-sms-hist",
"name": "Close GET SMS history (sticky fallback)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1168,
32
],
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// Derive sticky sender AND correct next-touch from Close's real SMS history.\n// Close is source of truth: sticky sender = OLDEST outbound local_phone (opener's phone).\n// This ensures leads always see the SAME number for every follow-up.\n\nconst FOLLOWUPS = {\n 2: \"Are you interested in seeing The Website I made your business? If you have one and I missed it let me know\",\n 3: \"I'll keep following up until I get a No. Can I send it over?\",\n 4: \"I'm going to keep following up lol just say cya later alligator \ud83d\udc0a and I'll STOP over just say send Website\"\n};\n\nfunction touchOf(text) {\n const t = (text || '').toLowerCase();\n if (t.indexOf('cya later alligator') >= 0) return 4;\n if (t.indexOf(\"i'll keep following up until i get a no\") >= 0) return 3;\n if (t.indexOf('are you interested in seeing the website') >= 0) return 2;\n return 1;\n}\n\nconst out = [];\nfor (const item of $input.all()) {\n const gate = $('Gate + build SMS').item.json;\n const smsList = (item.json && item.json.data) ? item.json.data : [];\n\n // Sticky sender: Close history WINS (opener's phone is what lead saw).\n // Convex assigned_sender_phone is only used if no history exists (shouldn't happen for followups).\n const outbound = smsList.filter(m => m && m.direction === 'outbound' && m.local_phone);\n outbound.sort((a, b) => new Date(a.date_created || 0) - new Date(b.date_created || 0));\n const openerPhone = (outbound[0] && outbound[0].local_phone) || null;\n const sticky_sender_phone = openerPhone || gate.sender_phone || '+15555550100';\n const sender_source = openerPhone ? 'close_opener' : (gate.sender_phone ? 'convex_assigned' : 'hardcoded_fallback');\n\n // Determine correct next touch from Close's actual delivered outbound\n const delivered = smsList.filter(m => m && m.direction === 'outbound' && m.status === 'sent');\n let maxTouch = 0;\n for (const m of delivered) {\n const t = touchOf(m.text);\n if (t > maxTouch) maxTouch = t;\n }\n\n // Already at Touch 4 \u2192 skip\n if (maxTouch >= 4) {\n console.log(`Skipping ${gate.lead_id} \u2014 already delivered Touch 4`);\n continue;\n }\n\n // No delivered SMS at all \u2192 skip (opener should come from WF-NW-01)\n if (maxTouch < 1) {\n console.log(`Skipping ${gate.lead_id} \u2014 no delivered SMS yet`);\n continue;\n }\n\n const nextTouch = maxTouch + 1;\n const correctText = FOLLOWUPS[nextTouch];\n\n out.push({\n json: Object.assign({}, gate, {\n sticky_sender_phone,\n sender_source,\n close_history_size: outbound.length,\n close_max_touch_delivered: maxTouch,\n correct_next_touch: nextTouch,\n text: correctText,\n sms_count: maxTouch,\n })\n });\n}\nreturn out;"
},
"id": "code-derive-sender",
"name": "Derive Sticky Sender",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1392,
32
]
},
{
"parameters": {
"url": "=https://api.close.com/api/v1/activity/sms/?lead_id={{ $('Gate + build SMS').item.json.lead_id }}&_limit=50&_fields=id,direction",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {
"timeout": 15000
}
},
"id": "http-close-check-inbound",
"name": "Close GET inbound check",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
992,
-32
]
},
{
"parameters": {
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "c-inbound-exists",
"leftValue": "={{ ($json.data || []).filter(x => x.direction === 'inbound').length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-has-inbound",
"name": "Has inbound?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1264,
16
]
}
],
"connections": {
"Every Hour 9 AM - 8 PM ET": {
"main": [
[
{
"node": "GET Convex /sms-batch",
"type": "main",
"index": 0
}
]
]
},
"Send 10 AM ET (CT zone)": {
"main": [
[
{
"node": "GET Convex /sms-batch",
"type": "main",
"index": 0
}
]
]
},
"Send 11 AM ET (MT zone)": {
"main": [
[
{
"node": "GET Convex /sms-batch",
"type": "main",
"index": 0
}
]
]
},
"Send 12 PM ET (PT/AZ zone)": {
"main": [
[
{
"node": "GET Convex /sms-batch",
"type": "main",
"index": 0
}
]
]
},
"GET Convex /sms-batch": {
"main": [
[
{
"node": "Gate + build SMS",
"type": "main",
"index": 0
}
]
]
},
"Gate + build SMS": {
"main": [
[
{
"node": "SEND_REAL?",
"type": "main",
"index": 0
}
]
]
},
"SEND_REAL?": {
"main": [
[
{
"node": "Loop Batches (10)",
"type": "main",
"index": 0
}
],
[
{
"node": "No-Op (dry-run log)",
"type": "main",
"index": 0
}
]
]
},
"Loop Batches (10)": {
"main": [
[
{
"node": "Slack: send summary",
"type": "main",
"index": 0
}
],
[
{
"node": "Close GET lead status",
"type": "main",
"index": 0
}
]
]
},
"Close GET lead status": {
"main": [
[
{
"node": "Status in skip list?",
"type": "main",
"index": 0
}
]
]
},
"Status in skip list?": {
"main": [
[
{
"node": "Slack: Followup Skipped (Status)",
"type": "main",
"index": 0
}
],
[
{
"node": "Close GET inbound check",
"type": "main",
"index": 0
}
]
]
},
"Slack: Followup Skipped (Status)": {
"main": [
[
{
"node": "Wait 1.5s (rate-limit)",
"type": "main",
"index": 0
}
]
]
},
"Close GET SMS history (sticky fallback)": {
"main": [
[
{
"node": "Derive Sticky Sender",
"type": "main",
"index": 0
}
]
]
},
"Derive Sticky Sender": {
"main": [
[
{
"node": "Close SMS, Followup",
"type": "main",
"index": 0
}
]
]
},
"Close SMS, Followup": {
"main": [
[
{
"node": "Followup Sent OK?",
"type": "main",
"index": 0
}
]
]
},
"Followup Sent OK?": {
"main": [
[
{
"node": "Convex /sms-activities/log (followup)",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Followup Failed",
"type": "main",
"index": 0
}
]
]
},
"Slack: Followup Failed": {
"main": [
[
{
"node": "Wait 1.5s (rate-limit)",
"type": "main",
"index": 0
}
]
]
},
"Convex /sms-activities/log (followup)": {
"main": [
[
{
"node": "Convex /record-sms",
"type": "main",
"index": 0
}
]
]
},
"Convex /record-sms": {
"main": [
[
{
"node": "Close PUT cf SMS Count",
"type": "main",
"index": 0
}
]
]
},
"Close PUT cf SMS Count": {
"main": [
[
{
"node": "Wait 1.5s (rate-limit)",
"type": "main",
"index": 0
}
]
]
},
"Wait 1.5s (rate-limit)": {
"main": [
[
{
"node": "Loop Batches (10)",
"type": "main",
"index": 0
}
]
]
},
"Close GET inbound check": {
"main": [
[
{
"node": "Has inbound?",
"type": "main",
"index": 0
}
]
]
},
"Has inbound?": {
"main": [
[
{
"node": "Wait 1.5s (rate-limit)",
"type": "main",
"index": 0
}
],
[
{
"node": "Close GET SMS history (sticky fallback)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"timezone": "America/New_York",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false,
"binaryMode": "separate",
"saveDataSuccessExecution": "all",
"saveDataErrorExecution": "all",
"saveManualExecutions": true
},
"active": false
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WF-NW-03, Followup SMS Only (touch 2-4). Uses httpRequest. Scheduled trigger; 24 nodes.
Source: https://github.com/rafiulislam4246/auto-website-sell-workflows/blob/main/workflows/03-followup-sms-touch-2-4.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.
This template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n
Jira-Allure-Auto-Qa. Uses httpRequest, jira. Scheduled trigger; 68 nodes.
Spotify-Sync-Surrealdb-V1. Uses httpRequest, n8n-nodes-surrealdb, spotify. Scheduled trigger; 62 nodes.
As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o