@elitedcs/ghl-mcp 3.96.0 → 3.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/guide/guide.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>GHL Command — User Guide</title>
7
- <!-- guides-hash: a0d59993c2403c19 -->
7
+ <!-- guides-hash: 78c482c74a246191 -->
8
8
  <style>
9
9
  /* Deliberately light in every environment, including a dark-mode OS. This is a
10
10
  reference document people read at length, print, and save to PDF, and a page
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.96.0",
3
+ "version": "3.97.0",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 250 tools for CRM, automation and marketing, an account-wide workflow audit, live funnel-capture verification and a workflow builder, across client accounts.",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,8 @@ function resolvePreset(preset) {
44
44
  // one "==" turned "!="), so exactly one reaches any plan: the texting-on and texting-off wording of the same job
45
45
  // (ask first, 2026-10-29). The maximal plan keeps the first; any other repeat is still a duplicate below.
46
46
  if (Array.isArray(sk.handoffs)) {
47
- const parts = (h) => (typeof h.conditionalOn === 'string' ? h.conditionalOn.split(/(==|!=)/) : null);
47
+ // rc.3 LOW-C: an "||" makes two conditions overlap however their operators read, so such a pair never collapses.
48
+ const parts = (h) => (typeof h.conditionalOn === 'string' && !h.conditionalOn.includes('||') ? h.conditionalOn.split(/(==|!=)/) : null);
48
49
  const opposite = (x, y) => { const a = parts(x), b = parts(y); return !!a && !!b && a.length === b.length && a.filter((t, i) => t !== b[i]).length === 1 && a.every((t, i) => t === b[i] || (/^[!=]=$/.test(t) && /^[!=]=$/.test(b[i]))); };
49
50
  const count = (ref) => sk.handoffs.filter((e) => e.ref === ref).length;
50
51
  sk.handoffs = sk.handoffs.filter((h, i) => !(count(h.ref) === 2 && sk.handoffs.slice(0, i).some((e) => e.ref === h.ref && opposite(e, h))));
@@ -1182,13 +1182,17 @@
1182
1182
  "callDirection": "inbound"
1183
1183
  },
1184
1184
  "stopOnResponse": true,
1185
- "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, tags the contact, alerts a real person and creates a call-back task, then one more text an hour later. Stops the moment they reply.",
1185
+ "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, takes the missed-call tag off and puts it back on (so Missed Call - Pipeline Card starts on every missed call, W47), tags the contact, alerts a real person and creates a call-back task. W48 (2026-10-29): allowed to run again on every missed call; the closing 10-minute wait keeps the contact inside it, and GoHighLevel did not start it again for a contact still inside it (live call proof 2026-10-29), so a redial within 10 minutes gets no second text. The hour-later text is its own workflow, Missed Call - Follow-Up.",
1186
1186
  "actions": [
1187
1187
  {
1188
1188
  "type": "send_sms",
1189
1189
  "smsRef": "sms.mc_sms_1",
1190
1190
  "conditionalOn": "channels.sms == true"
1191
1191
  },
1192
+ {
1193
+ "type": "remove_contact_tag",
1194
+ "tagRef": "tag.missed_call"
1195
+ },
1192
1196
  {
1193
1197
  "type": "add_contact_tag",
1194
1198
  "tagRef": "tag.missed_call"
@@ -1212,26 +1216,21 @@
1212
1216
  },
1213
1217
  {
1214
1218
  "type": "wait",
1215
- "value": 1,
1216
- "unit": "hours"
1217
- },
1218
- {
1219
- "type": "send_sms",
1220
- "smsRef": "sms.mc_sms_2",
1221
- "conditionalOn": "channels.sms == true"
1219
+ "value": 10,
1220
+ "unit": "minutes"
1222
1221
  }
1223
1222
  ]
1224
1223
  },
1225
1224
  {
1226
1225
  "ref": "workflow.missed_call_card",
1227
- "allowMultiple": false,
1226
+ "allowMultiple": true,
1228
1227
  "name": "Missed Call - Pipeline Card",
1229
1228
  "conditionalOn": "channels.sms == true",
1230
1229
  "trigger": {
1231
1230
  "type": "contact_tag",
1232
1231
  "tagRef": "tag.missed_call"
1233
1232
  },
1234
- "_design": "F6 (2026-10-29): a caller Missed Call Text-Back tagged missed-call, with no open card in this pipeline, gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card keeps it. Its own workflow because find_opportunity must be the last step and cannot sit inside a shared account's If/Else gate.",
1233
+ "_design": "W47 (2026-10-29 live call proof): started by the missed-call tag being added, and allowed to run again. The text-back takes the tag off and puts it straight back on every missed call, so the tag is newly added each time and a repeat caller is checked again (a contact who already had the tag, with re-entry off, never reached this workflow). A caller with no card in this pipeline gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card in this pipeline keeps it and is tagged returning-caller. Not yet proven live: whether a closed (won or lost) card counts as found; Find Opportunity reads the latest card in the pipeline and the build sets no status filter. Started by the tag, not the call, because a shared account gates call-started workflows with an If/Else, and find_opportunity must be the last step and cannot sit inside one.",
1235
1234
  "actions": [
1236
1235
  {
1237
1236
  "type": "find_opportunity",
@@ -1261,6 +1260,35 @@
1261
1260
  }
1262
1261
  ]
1263
1262
  },
1263
+ {
1264
+ "ref": "workflow.missed_call_follow_up",
1265
+ "allowMultiple": true,
1266
+ "name": "Missed Call - Follow-Up",
1267
+ "conditionalOn": "channels.sms == true",
1268
+ "trigger": {
1269
+ "type": "call_status",
1270
+ "callStatuses": [
1271
+ "no-answer",
1272
+ "busy",
1273
+ "voicemail"
1274
+ ],
1275
+ "callDirection": "inbound"
1276
+ },
1277
+ "stopOnResponse": true,
1278
+ "_design": "W48 (2026-10-29): the hour-later text, split from the instant text-back so a second missed call inside the hour still gets an instant reply. Started by the same missed call; a redial while a contact is still waiting here gets no second follow-up. Stops the moment they reply.",
1279
+ "actions": [
1280
+ {
1281
+ "type": "wait",
1282
+ "value": 1,
1283
+ "unit": "hours"
1284
+ },
1285
+ {
1286
+ "type": "send_sms",
1287
+ "smsRef": "sms.mc_sms_2",
1288
+ "conditionalOn": "channels.sms == true"
1289
+ }
1290
+ ]
1291
+ },
1264
1292
  {
1265
1293
  "ref": "workflow.lead_nurture",
1266
1294
  "allowMultiple": false,
@@ -1422,6 +1450,11 @@
1422
1450
  "workflowRef": "workflow.missed_call",
1423
1451
  "conditionalOn": "channels.sms == true"
1424
1452
  },
1453
+ {
1454
+ "type": "remove_from_workflow",
1455
+ "workflowRef": "workflow.missed_call_follow_up",
1456
+ "conditionalOn": "channels.sms == true"
1457
+ },
1425
1458
  {
1426
1459
  "type": "remove_from_workflow",
1427
1460
  "workflowRef": "workflow.lead_nurture"
@@ -1563,6 +1596,11 @@
1563
1596
  "workflowRef": "workflow.missed_call",
1564
1597
  "conditionalOn": "channels.sms == true"
1565
1598
  },
1599
+ {
1600
+ "type": "remove_from_workflow",
1601
+ "workflowRef": "workflow.missed_call_follow_up",
1602
+ "conditionalOn": "channels.sms == true"
1603
+ },
1566
1604
  {
1567
1605
  "type": "add_contact_tag",
1568
1606
  "tagRef": "tag.lc_booked"
@@ -1887,6 +1925,7 @@
1887
1925
  "blocks": [
1888
1926
  "workflow.speed_to_lead",
1889
1927
  "workflow.missed_call",
1928
+ "workflow.missed_call_follow_up",
1890
1929
  "workflow.lead_nurture",
1891
1930
  "workflow.win_back",
1892
1931
  "workflow.appointment_booked",
@@ -1946,6 +1985,7 @@
1946
1985
  "blocks": [
1947
1986
  "workflow.speed_to_lead",
1948
1987
  "workflow.missed_call",
1988
+ "workflow.missed_call_follow_up",
1949
1989
  "workflow.lead_nurture",
1950
1990
  "workflow.win_back",
1951
1991
  "workflow.appointment_booked",
@@ -1973,6 +2013,18 @@
1973
2013
  ],
1974
2014
  "_blocksNote": "Lists every SMS-bearing WORKFLOW ref (so they stay DRAFT) plus the sms.* asset refs. After conditional pruning, drop any entry whose workflow/asset was removed."
1975
2015
  },
2016
+ {
2017
+ "ref": "handoff.stop_line_check",
2018
+ "conditionalOn": "channels.sms == true",
2019
+ "owner": "OPERATOR-UI",
2020
+ "title": "Check whether the account adds its own Reply STOP line",
2021
+ "trigger": "channels.sms == true",
2022
+ "instruction": "Your GoHighLevel account may already add its own 'Reply STOP' line to the first text each contact gets. If it does, a contact may see the opt-out line twice on that first text. That is safe. Remove the build's own line from a text only if you are sure the account's own line is switched on.",
2023
+ "produces": null,
2024
+ "successCheck": "You know whether the account adds its own Reply STOP line to first texts",
2025
+ "blocks": [],
2026
+ "_design": "W49 (command-center ruling 2026-10-30): a doubled opt-out line is cosmetic, a missing one is a compliance failure, so the build keeps its own STOP line and says so. No settings path is named because none has been seen on screen; the setting is to be checked live later."
2027
+ },
1976
2028
  {
1977
2029
  "ref": "handoff.compliance_review",
1978
2030
  "owner": "TEAM",
@@ -1177,13 +1177,17 @@
1177
1177
  "callDirection": "inbound"
1178
1178
  },
1179
1179
  "stopOnResponse": true,
1180
- "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, tags the contact, alerts a real person and creates a call-back task, then one more text an hour later. Stops the moment they reply.",
1180
+ "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, takes the missed-call tag off and puts it back on (so Missed Call - Pipeline Card starts on every missed call, W47), tags the contact, alerts a real person and creates a call-back task. W48 (2026-10-29): allowed to run again on every missed call; the closing 10-minute wait keeps the contact inside it, and GoHighLevel did not start it again for a contact still inside it (live call proof 2026-10-29), so a redial within 10 minutes gets no second text. The hour-later text is its own workflow, Missed Call - Follow-Up.",
1181
1181
  "actions": [
1182
1182
  {
1183
1183
  "type": "send_sms",
1184
1184
  "smsRef": "sms.mc_sms_1",
1185
1185
  "conditionalOn": "channels.sms == true"
1186
1186
  },
1187
+ {
1188
+ "type": "remove_contact_tag",
1189
+ "tagRef": "tag.missed_call"
1190
+ },
1187
1191
  {
1188
1192
  "type": "add_contact_tag",
1189
1193
  "tagRef": "tag.missed_call"
@@ -1207,26 +1211,21 @@
1207
1211
  },
1208
1212
  {
1209
1213
  "type": "wait",
1210
- "value": 1,
1211
- "unit": "hours"
1212
- },
1213
- {
1214
- "type": "send_sms",
1215
- "smsRef": "sms.mc_sms_2",
1216
- "conditionalOn": "channels.sms == true"
1214
+ "value": 10,
1215
+ "unit": "minutes"
1217
1216
  }
1218
1217
  ]
1219
1218
  },
1220
1219
  {
1221
1220
  "ref": "workflow.missed_call_card",
1222
- "allowMultiple": false,
1221
+ "allowMultiple": true,
1223
1222
  "name": "Missed Call - Pipeline Card",
1224
1223
  "conditionalOn": "channels.sms == true",
1225
1224
  "trigger": {
1226
1225
  "type": "contact_tag",
1227
1226
  "tagRef": "tag.missed_call"
1228
1227
  },
1229
- "_design": "F6 (2026-10-29): a caller Missed Call Text-Back tagged missed-call, with no open card in this pipeline, gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card keeps it. Its own workflow because find_opportunity must be the last step and cannot sit inside a shared account's If/Else gate.",
1228
+ "_design": "W47 (2026-10-29 live call proof): started by the missed-call tag being added, and allowed to run again. The text-back takes the tag off and puts it straight back on every missed call, so the tag is newly added each time and a repeat caller is checked again (a contact who already had the tag, with re-entry off, never reached this workflow). A caller with no card in this pipeline gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card in this pipeline keeps it and is tagged returning-caller. Not yet proven live: whether a closed (won or lost) card counts as found; Find Opportunity reads the latest card in the pipeline and the build sets no status filter. Started by the tag, not the call, because a shared account gates call-started workflows with an If/Else, and find_opportunity must be the last step and cannot sit inside one.",
1230
1229
  "actions": [
1231
1230
  {
1232
1231
  "type": "find_opportunity",
@@ -1256,6 +1255,35 @@
1256
1255
  }
1257
1256
  ]
1258
1257
  },
1258
+ {
1259
+ "ref": "workflow.missed_call_follow_up",
1260
+ "allowMultiple": true,
1261
+ "name": "Missed Call - Follow-Up",
1262
+ "conditionalOn": "channels.sms == true",
1263
+ "trigger": {
1264
+ "type": "call_status",
1265
+ "callStatuses": [
1266
+ "no-answer",
1267
+ "busy",
1268
+ "voicemail"
1269
+ ],
1270
+ "callDirection": "inbound"
1271
+ },
1272
+ "stopOnResponse": true,
1273
+ "_design": "W48 (2026-10-29): the hour-later text, split from the instant text-back so a second missed call inside the hour still gets an instant reply. Started by the same missed call; a redial while a contact is still waiting here gets no second follow-up. Stops the moment they reply.",
1274
+ "actions": [
1275
+ {
1276
+ "type": "wait",
1277
+ "value": 1,
1278
+ "unit": "hours"
1279
+ },
1280
+ {
1281
+ "type": "send_sms",
1282
+ "smsRef": "sms.mc_sms_2",
1283
+ "conditionalOn": "channels.sms == true"
1284
+ }
1285
+ ]
1286
+ },
1259
1287
  {
1260
1288
  "ref": "workflow.lead_nurture",
1261
1289
  "allowMultiple": false,
@@ -1417,6 +1445,11 @@
1417
1445
  "workflowRef": "workflow.missed_call",
1418
1446
  "conditionalOn": "channels.sms == true"
1419
1447
  },
1448
+ {
1449
+ "type": "remove_from_workflow",
1450
+ "workflowRef": "workflow.missed_call_follow_up",
1451
+ "conditionalOn": "channels.sms == true"
1452
+ },
1420
1453
  {
1421
1454
  "type": "remove_from_workflow",
1422
1455
  "workflowRef": "workflow.lead_nurture"
@@ -1558,6 +1591,11 @@
1558
1591
  "workflowRef": "workflow.missed_call",
1559
1592
  "conditionalOn": "channels.sms == true"
1560
1593
  },
1594
+ {
1595
+ "type": "remove_from_workflow",
1596
+ "workflowRef": "workflow.missed_call_follow_up",
1597
+ "conditionalOn": "channels.sms == true"
1598
+ },
1561
1599
  {
1562
1600
  "type": "add_contact_tag",
1563
1601
  "tagRef": "tag.lc_booked"
@@ -1865,6 +1903,7 @@
1865
1903
  "blocks": [
1866
1904
  "workflow.speed_to_lead",
1867
1905
  "workflow.missed_call",
1906
+ "workflow.missed_call_follow_up",
1868
1907
  "workflow.lead_nurture",
1869
1908
  "workflow.win_back",
1870
1909
  "workflow.appointment_booked",
@@ -1924,6 +1963,7 @@
1924
1963
  "blocks": [
1925
1964
  "workflow.speed_to_lead",
1926
1965
  "workflow.missed_call",
1966
+ "workflow.missed_call_follow_up",
1927
1967
  "workflow.lead_nurture",
1928
1968
  "workflow.win_back",
1929
1969
  "workflow.appointment_booked",
@@ -1951,6 +1991,18 @@
1951
1991
  ],
1952
1992
  "_blocksNote": "Lists every SMS-bearing WORKFLOW ref (so they stay DRAFT) plus the sms.* asset refs. After conditional pruning, drop any entry whose workflow/asset was removed."
1953
1993
  },
1994
+ {
1995
+ "ref": "handoff.stop_line_check",
1996
+ "conditionalOn": "channels.sms == true",
1997
+ "owner": "OPERATOR-UI",
1998
+ "title": "Check whether the account adds its own Reply STOP line",
1999
+ "trigger": "channels.sms == true",
2000
+ "instruction": "Your GoHighLevel account may already add its own 'Reply STOP' line to the first text each contact gets. If it does, a contact may see the opt-out line twice on that first text. That is safe. Remove the build's own line from a text only if you are sure the account's own line is switched on.",
2001
+ "produces": null,
2002
+ "successCheck": "You know whether the account adds its own Reply STOP line to first texts",
2003
+ "blocks": [],
2004
+ "_design": "W49 (command-center ruling 2026-10-30): a doubled opt-out line is cosmetic, a missing one is a compliance failure, so the build keeps its own STOP line and says so. No settings path is named because none has been seen on screen; the setting is to be checked live later."
2005
+ },
1954
2006
  {
1955
2007
  "ref": "handoff.stripe",
1956
2008
  "conditionalOn": "flags includes stripe_not_connected",
@@ -918,13 +918,17 @@
918
918
  "callDirection": "inbound"
919
919
  },
920
920
  "stopOnResponse": true,
921
- "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, tags the contact, alerts a real person and creates a call-back task, then one more text an hour later. Stops the moment they reply.",
921
+ "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, takes the missed-call tag off and puts it back on (so Missed Call - Pipeline Card starts on every missed call, W47), tags the contact, alerts a real person and creates a call-back task. W48 (2026-10-29): allowed to run again on every missed call; the closing 10-minute wait keeps the contact inside it, and GoHighLevel did not start it again for a contact still inside it (live call proof 2026-10-29), so a redial within 10 minutes gets no second text. The hour-later text is its own workflow, Missed Call - Follow-Up.",
922
922
  "actions": [
923
923
  {
924
924
  "type": "send_sms",
925
925
  "smsRef": "sms.mc_sms_1",
926
926
  "conditionalOn": "channels.sms == true"
927
927
  },
928
+ {
929
+ "type": "remove_contact_tag",
930
+ "tagRef": "tag.missed_call"
931
+ },
928
932
  {
929
933
  "type": "add_contact_tag",
930
934
  "tagRef": "tag.missed_call"
@@ -948,26 +952,21 @@
948
952
  },
949
953
  {
950
954
  "type": "wait",
951
- "value": 1,
952
- "unit": "hours"
953
- },
954
- {
955
- "type": "send_sms",
956
- "smsRef": "sms.mc_sms_2",
957
- "conditionalOn": "channels.sms == true"
955
+ "value": 10,
956
+ "unit": "minutes"
958
957
  }
959
958
  ]
960
959
  },
961
960
  {
962
961
  "ref": "workflow.missed_call_card",
963
- "allowMultiple": false,
962
+ "allowMultiple": true,
964
963
  "name": "Missed Call - Pipeline Card",
965
964
  "conditionalOn": "channels.sms == true",
966
965
  "trigger": {
967
966
  "type": "contact_tag",
968
967
  "tagRef": "tag.missed_call"
969
968
  },
970
- "_design": "F6 (2026-10-29): a caller Missed Call Text-Back tagged missed-call, with no open card in this pipeline, gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card keeps it. Its own workflow because find_opportunity must be the last step and cannot sit inside a shared account's If/Else gate.",
969
+ "_design": "W47 (2026-10-29 live call proof): started by the missed-call tag being added, and allowed to run again. The text-back takes the tag off and puts it straight back on every missed call, so the tag is newly added each time and a repeat caller is checked again (a contact who already had the tag, with re-entry off, never reached this workflow). A caller with no card in this pipeline gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card in this pipeline keeps it and is tagged returning-caller. Not yet proven live: whether a closed (won or lost) card counts as found; Find Opportunity reads the latest card in the pipeline and the build sets no status filter. Started by the tag, not the call, because a shared account gates call-started workflows with an If/Else, and find_opportunity must be the last step and cannot sit inside one.",
971
970
  "actions": [
972
971
  {
973
972
  "type": "find_opportunity",
@@ -997,6 +996,35 @@
997
996
  }
998
997
  ]
999
998
  },
999
+ {
1000
+ "ref": "workflow.missed_call_follow_up",
1001
+ "allowMultiple": true,
1002
+ "name": "Missed Call - Follow-Up",
1003
+ "conditionalOn": "channels.sms == true",
1004
+ "trigger": {
1005
+ "type": "call_status",
1006
+ "callStatuses": [
1007
+ "no-answer",
1008
+ "busy",
1009
+ "voicemail"
1010
+ ],
1011
+ "callDirection": "inbound"
1012
+ },
1013
+ "stopOnResponse": true,
1014
+ "_design": "W48 (2026-10-29): the hour-later text, split from the instant text-back so a second missed call inside the hour still gets an instant reply. Started by the same missed call; a redial while a contact is still waiting here gets no second follow-up. Stops the moment they reply.",
1015
+ "actions": [
1016
+ {
1017
+ "type": "wait",
1018
+ "value": 1,
1019
+ "unit": "hours"
1020
+ },
1021
+ {
1022
+ "type": "send_sms",
1023
+ "smsRef": "sms.mc_sms_2",
1024
+ "conditionalOn": "channels.sms == true"
1025
+ }
1026
+ ]
1027
+ },
1000
1028
  {
1001
1029
  "ref": "workflow.lead_nurture",
1002
1030
  "allowMultiple": false,
@@ -1158,6 +1186,11 @@
1158
1186
  "workflowRef": "workflow.missed_call",
1159
1187
  "conditionalOn": "channels.sms == true"
1160
1188
  },
1189
+ {
1190
+ "type": "remove_from_workflow",
1191
+ "workflowRef": "workflow.missed_call_follow_up",
1192
+ "conditionalOn": "channels.sms == true"
1193
+ },
1161
1194
  {
1162
1195
  "type": "remove_from_workflow",
1163
1196
  "workflowRef": "workflow.lead_nurture"
@@ -1343,6 +1376,7 @@
1343
1376
  "blocks": [
1344
1377
  "workflow.speed_to_lead",
1345
1378
  "workflow.missed_call",
1379
+ "workflow.missed_call_follow_up",
1346
1380
  "workflow.lead_nurture",
1347
1381
  "workflow.win_back"
1348
1382
  ]
@@ -1371,6 +1405,7 @@
1371
1405
  "blocks": [
1372
1406
  "workflow.speed_to_lead",
1373
1407
  "workflow.missed_call",
1408
+ "workflow.missed_call_follow_up",
1374
1409
  "workflow.lead_nurture",
1375
1410
  "workflow.win_back",
1376
1411
  "sms.stl_sms_1",
@@ -1391,6 +1426,18 @@
1391
1426
  ],
1392
1427
  "_blocksNote": "Lists every SMS-bearing WORKFLOW ref (so they stay DRAFT) plus the sms.* asset refs. After conditional pruning, drop any entry whose workflow/asset was removed."
1393
1428
  },
1429
+ {
1430
+ "ref": "handoff.stop_line_check",
1431
+ "conditionalOn": "channels.sms == true",
1432
+ "owner": "OPERATOR-UI",
1433
+ "title": "Check whether the account adds its own Reply STOP line",
1434
+ "trigger": "channels.sms == true",
1435
+ "instruction": "Your GoHighLevel account may already add its own 'Reply STOP' line to the first text each contact gets. If it does, a contact may see the opt-out line twice on that first text. That is safe. Remove the build's own line from a text only if you are sure the account's own line is switched on.",
1436
+ "produces": null,
1437
+ "successCheck": "You know whether the account adds its own Reply STOP line to first texts",
1438
+ "blocks": [],
1439
+ "_design": "W49 (command-center ruling 2026-10-30): a doubled opt-out line is cosmetic, a missing one is a compliance failure, so the build keeps its own STOP line and says so. No settings path is named because none has been seen on screen; the setting is to be checked live later."
1440
+ },
1394
1441
  {
1395
1442
  "ref": "handoff.welcome_code",
1396
1443
  "owner": "OPERATOR-EXT",
@@ -1174,13 +1174,17 @@
1174
1174
  "callDirection": "inbound"
1175
1175
  },
1176
1176
  "stopOnResponse": true,
1177
- "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, tags the contact, alerts a real person and creates a call-back task, then one more text an hour later. Stops the moment they reply.",
1177
+ "_design": "Fires on an inbound call that was not answered (no-answer / busy / voicemail). Texts back within seconds, takes the missed-call tag off and puts it back on (so Missed Call - Pipeline Card starts on every missed call, W47), tags the contact, alerts a real person and creates a call-back task. W48 (2026-10-29): allowed to run again on every missed call; the closing 10-minute wait keeps the contact inside it, and GoHighLevel did not start it again for a contact still inside it (live call proof 2026-10-29), so a redial within 10 minutes gets no second text. The hour-later text is its own workflow, Missed Call - Follow-Up.",
1178
1178
  "actions": [
1179
1179
  {
1180
1180
  "type": "send_sms",
1181
1181
  "smsRef": "sms.mc_sms_1",
1182
1182
  "conditionalOn": "channels.sms == true"
1183
1183
  },
1184
+ {
1185
+ "type": "remove_contact_tag",
1186
+ "tagRef": "tag.missed_call"
1187
+ },
1184
1188
  {
1185
1189
  "type": "add_contact_tag",
1186
1190
  "tagRef": "tag.missed_call"
@@ -1204,26 +1208,21 @@
1204
1208
  },
1205
1209
  {
1206
1210
  "type": "wait",
1207
- "value": 1,
1208
- "unit": "hours"
1209
- },
1210
- {
1211
- "type": "send_sms",
1212
- "smsRef": "sms.mc_sms_2",
1213
- "conditionalOn": "channels.sms == true"
1211
+ "value": 10,
1212
+ "unit": "minutes"
1214
1213
  }
1215
1214
  ]
1216
1215
  },
1217
1216
  {
1218
1217
  "ref": "workflow.missed_call_card",
1219
- "allowMultiple": false,
1218
+ "allowMultiple": true,
1220
1219
  "name": "Missed Call - Pipeline Card",
1221
1220
  "conditionalOn": "channels.sms == true",
1222
1221
  "trigger": {
1223
1222
  "type": "contact_tag",
1224
1223
  "tagRef": "tag.missed_call"
1225
1224
  },
1226
- "_design": "F6 (2026-10-29): a caller Missed Call Text-Back tagged missed-call, with no open card in this pipeline, gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card keeps it. Its own workflow because find_opportunity must be the last step and cannot sit inside a shared account's If/Else gate.",
1225
+ "_design": "W47 (2026-10-29 live call proof): started by the missed-call tag being added, and allowed to run again. The text-back takes the tag off and puts it straight back on every missed call, so the tag is newly added each time and a repeat caller is checked again (a contact who already had the tag, with re-entry off, never reached this workflow). A caller with no card in this pipeline gets one at the first stage, an owner and the new-lead tag, as a form lead does in Speed to Lead. A caller who already has a card in this pipeline keeps it and is tagged returning-caller. Not yet proven live: whether a closed (won or lost) card counts as found; Find Opportunity reads the latest card in the pipeline and the build sets no status filter. Started by the tag, not the call, because a shared account gates call-started workflows with an If/Else, and find_opportunity must be the last step and cannot sit inside one.",
1227
1226
  "actions": [
1228
1227
  {
1229
1228
  "type": "find_opportunity",
@@ -1253,6 +1252,35 @@
1253
1252
  }
1254
1253
  ]
1255
1254
  },
1255
+ {
1256
+ "ref": "workflow.missed_call_follow_up",
1257
+ "allowMultiple": true,
1258
+ "name": "Missed Call - Follow-Up",
1259
+ "conditionalOn": "channels.sms == true",
1260
+ "trigger": {
1261
+ "type": "call_status",
1262
+ "callStatuses": [
1263
+ "no-answer",
1264
+ "busy",
1265
+ "voicemail"
1266
+ ],
1267
+ "callDirection": "inbound"
1268
+ },
1269
+ "stopOnResponse": true,
1270
+ "_design": "W48 (2026-10-29): the hour-later text, split from the instant text-back so a second missed call inside the hour still gets an instant reply. Started by the same missed call; a redial while a contact is still waiting here gets no second follow-up. Stops the moment they reply.",
1271
+ "actions": [
1272
+ {
1273
+ "type": "wait",
1274
+ "value": 1,
1275
+ "unit": "hours"
1276
+ },
1277
+ {
1278
+ "type": "send_sms",
1279
+ "smsRef": "sms.mc_sms_2",
1280
+ "conditionalOn": "channels.sms == true"
1281
+ }
1282
+ ]
1283
+ },
1256
1284
  {
1257
1285
  "ref": "workflow.lead_nurture",
1258
1286
  "allowMultiple": false,
@@ -1414,6 +1442,11 @@
1414
1442
  "workflowRef": "workflow.missed_call",
1415
1443
  "conditionalOn": "channels.sms == true"
1416
1444
  },
1445
+ {
1446
+ "type": "remove_from_workflow",
1447
+ "workflowRef": "workflow.missed_call_follow_up",
1448
+ "conditionalOn": "channels.sms == true"
1449
+ },
1417
1450
  {
1418
1451
  "type": "remove_from_workflow",
1419
1452
  "workflowRef": "workflow.lead_nurture"
@@ -1555,6 +1588,11 @@
1555
1588
  "workflowRef": "workflow.missed_call",
1556
1589
  "conditionalOn": "channels.sms == true"
1557
1590
  },
1591
+ {
1592
+ "type": "remove_from_workflow",
1593
+ "workflowRef": "workflow.missed_call_follow_up",
1594
+ "conditionalOn": "channels.sms == true"
1595
+ },
1558
1596
  {
1559
1597
  "type": "add_contact_tag",
1560
1598
  "tagRef": "tag.lc_booked"
@@ -1888,6 +1926,7 @@
1888
1926
  "blocks": [
1889
1927
  "workflow.speed_to_lead",
1890
1928
  "workflow.missed_call",
1929
+ "workflow.missed_call_follow_up",
1891
1930
  "workflow.lead_nurture",
1892
1931
  "workflow.win_back",
1893
1932
  "workflow.appointment_booked",
@@ -1945,6 +1984,7 @@
1945
1984
  "blocks": [
1946
1985
  "workflow.speed_to_lead",
1947
1986
  "workflow.missed_call",
1987
+ "workflow.missed_call_follow_up",
1948
1988
  "workflow.lead_nurture",
1949
1989
  "workflow.win_back",
1950
1990
  "workflow.appointment_booked",
@@ -1976,6 +2016,18 @@
1976
2016
  ],
1977
2017
  "_blocksNote": "Lists every SMS-bearing WORKFLOW ref (so they stay DRAFT) plus the sms.* asset refs. After conditional pruning, drop any entry whose workflow/asset was removed."
1978
2018
  },
2019
+ {
2020
+ "ref": "handoff.stop_line_check",
2021
+ "conditionalOn": "channels.sms == true",
2022
+ "owner": "OPERATOR-UI",
2023
+ "title": "Check whether the account adds its own Reply STOP line",
2024
+ "trigger": "channels.sms == true",
2025
+ "instruction": "Your GoHighLevel account may already add its own 'Reply STOP' line to the first text each contact gets. If it does, a contact may see the opt-out line twice on that first text. That is safe. Remove the build's own line from a text only if you are sure the account's own line is switched on.",
2026
+ "produces": null,
2027
+ "successCheck": "You know whether the account adds its own Reply STOP line to first texts",
2028
+ "blocks": [],
2029
+ "_design": "W49 (command-center ruling 2026-10-30): a doubled opt-out line is cosmetic, a missing one is a compliance failure, so the build keeps its own STOP line and says so. No settings path is named because none has been seen on screen; the setting is to be checked live later."
2030
+ },
1979
2031
  {
1980
2032
  "ref": "handoff.stripe",
1981
2033
  "conditionalOn": "flags includes stripe_not_connected",