@elitedcs/ghl-mcp 3.89.0-beta.3 → 3.89.0-beta.5
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/CHANGELOG.md +128 -0
- package/README.md +1 -1
- package/dist/index.js +9154 -4374
- package/package.json +1 -1
- package/skills/blueprint/examples/medspa-build-plan.json +4 -4
- package/skills/blueprint/examples/sample-build-plan.json +4 -4
- package/skills/blueprint/examples/validate-plan.cjs +15 -9
- package/skills/blueprint/presets/clinic.preset.json +66 -13
- package/skills/blueprint/presets/coach.preset.json +26 -7
- package/skills/blueprint/presets/ecommerce.preset.json +10 -4
- package/skills/blueprint/presets/generic-client.preset.json +24 -5
- package/skills/blueprint/presets/local-service.preset.json +24 -5
- package/skills/blueprint/presets/med-spa.preset.json +26 -7
- package/skills/blueprint/presets/wellness-clinic.md +55 -0
- package/skills/blueprint/presets/wellness-clinic.preset.json +3906 -0
- package/skills/blueprint/references/build-plan-schema.md +1 -1
- package/skills/blueprint/references/preset-format.md +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.89.0-beta.
|
|
3
|
+
"version": "3.89.0-beta.5",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
5
|
"description": "GoHighLevel MCP Server for Claude. 250 tools — full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -435,7 +435,7 @@
|
|
|
435
435
|
{
|
|
436
436
|
"ref": "sms_template.n_sms_1",
|
|
437
437
|
"name": "Nurture Text 1 - One Word",
|
|
438
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: is it lines, volume, or skin texture you'd most like to fix first? One word is enough
|
|
438
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: is it lines, volume, or skin texture you'd most like to fix first? One word is enough.\nReply STOP to opt out."
|
|
439
439
|
},
|
|
440
440
|
{
|
|
441
441
|
"ref": "sms_template.n_sms_2",
|
|
@@ -460,7 +460,7 @@
|
|
|
460
460
|
{
|
|
461
461
|
"ref": "sms_template.w_sms_1",
|
|
462
462
|
"name": "Win-back Text 1",
|
|
463
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is your skin still on the list this season, or should I close your file for now
|
|
463
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is your skin still on the list this season, or should I close your file for now?\nReply STOP to opt out."
|
|
464
464
|
},
|
|
465
465
|
{
|
|
466
466
|
"ref": "sms_template.w_sms_2",
|
|
@@ -767,7 +767,7 @@
|
|
|
767
767
|
"custom_values.owner_first_name",
|
|
768
768
|
"custom_values.business_name"
|
|
769
769
|
],
|
|
770
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: is it lines, volume, or skin texture you'd most like to fix first? One word is enough
|
|
770
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: is it lines, volume, or skin texture you'd most like to fix first? One word is enough.\nReply STOP to opt out."
|
|
771
771
|
},
|
|
772
772
|
{
|
|
773
773
|
"ref": "sms.n_sms_2",
|
|
@@ -817,7 +817,7 @@
|
|
|
817
817
|
"custom_values.owner_first_name",
|
|
818
818
|
"custom_values.business_name"
|
|
819
819
|
],
|
|
820
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is your skin still on the list this season, or should I close your file for now
|
|
820
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is your skin still on the list this season, or should I close your file for now?\nReply STOP to opt out."
|
|
821
821
|
},
|
|
822
822
|
{
|
|
823
823
|
"ref": "sms.w_sms_2",
|
|
@@ -402,7 +402,7 @@
|
|
|
402
402
|
{
|
|
403
403
|
"ref": "sms_template.n_sms_1",
|
|
404
404
|
"name": "Nurture Text 1 - One Word",
|
|
405
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: what matters most to you here, speed, price, or getting it right first time? One word is enough
|
|
405
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: what matters most to you here, speed, price, or getting it right first time? One word is enough.\nReply STOP to opt out."
|
|
406
406
|
},
|
|
407
407
|
{
|
|
408
408
|
"ref": "sms_template.n_sms_2",
|
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
{
|
|
428
428
|
"ref": "sms_template.w_sms_1",
|
|
429
429
|
"name": "Win-back Text 1",
|
|
430
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is this still on your list this quarter, or should I close your file for now
|
|
430
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is this still on your list this quarter, or should I close your file for now?\nReply STOP to opt out."
|
|
431
431
|
},
|
|
432
432
|
{
|
|
433
433
|
"ref": "sms_template.w_sms_2",
|
|
@@ -734,7 +734,7 @@
|
|
|
734
734
|
"custom_values.owner_first_name",
|
|
735
735
|
"custom_values.business_name"
|
|
736
736
|
],
|
|
737
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: what matters most to you here, speed, price, or getting it right first time? One word is enough
|
|
737
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: what matters most to you here, speed, price, or getting it right first time? One word is enough.\nReply STOP to opt out."
|
|
738
738
|
},
|
|
739
739
|
{
|
|
740
740
|
"ref": "sms.n_sms_2",
|
|
@@ -784,7 +784,7 @@
|
|
|
784
784
|
"custom_values.owner_first_name",
|
|
785
785
|
"custom_values.business_name"
|
|
786
786
|
],
|
|
787
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is this still on your list this quarter, or should I close your file for now
|
|
787
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is this still on your list this quarter, or should I close your file for now?\nReply STOP to opt out."
|
|
788
788
|
},
|
|
789
789
|
{
|
|
790
790
|
"ref": "sms.w_sms_2",
|
|
@@ -175,15 +175,21 @@ const stageRefs = new Set();
|
|
|
175
175
|
const days = (acts) => (acts || []).reduce((d, a) => a.type === 'wait' ? d + a.value * ({ minutes: 1 / 1440, hours: 1 / 24, days: 1 }[a.unit] || 0) : d, 0);
|
|
176
176
|
const spanDays = (w) => { let d = days(w.actions); (w.actions || []).forEach(a => { if (a.type === 'find_opportunity') d += Math.max(days(a.found), days(a.notFound)); }); return d; };
|
|
177
177
|
const nodeCount = (w) => (w.actions || []).reduce((n, a) => n + (a.type === 'find_opportunity' ? 3 + (a.found || []).length + (a.notFound || []).length : 1), 0);
|
|
178
|
-
const nurtureTags = new Set((plan.workflows || []).filter(x => /nurture/i.test(x.name || '') && x.trigger && x.trigger.tagRef).map(x => x.trigger.tagRef));
|
|
178
|
+
const nurtureTags = new Set((plan.workflows || []).filter(x => /nurture/i.test(x.name || '') && x.trigger && !Array.isArray(x.trigger) && x.trigger.tagRef).map(x => x.trigger.tagRef));
|
|
179
179
|
(plan.workflows || []).forEach(w => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
180
|
+
// A preset may list trigger variants, each with its own conditionalOn (the
|
|
181
|
+
// composer keeps the first that holds, e.g. the intake's review timing): each
|
|
182
|
+
// variant is checked as a trigger; the last must be the unconditional default.
|
|
183
|
+
const variants = Array.isArray(w.trigger) ? w.trigger : (w.trigger ? [w.trigger] : []);
|
|
184
|
+
if (Array.isArray(w.trigger) && (!variants.length || variants[variants.length - 1].conditionalOn)) E(`workflow ${w.ref}: a trigger list must end with a variant that has no conditionalOn (the default)`);
|
|
185
|
+
if (!variants.length || variants.some(t => !t || !t.type)) E(`workflow ${w.ref}: missing trigger`);
|
|
186
|
+
for (const t of variants) {
|
|
187
|
+
if (!t) continue;
|
|
188
|
+
need(t.formRef, `wf ${w.ref} trigger.formRef`); need(t.tagRef, `wf ${w.ref} trigger.tagRef`); need(t.calendarRef, `wf ${w.ref} trigger.calendarRef`);
|
|
189
|
+
need(t.pipelineRef, `wf ${w.ref} trigger.pipelineRef`); need(t.hasTagRef, `wf ${w.ref} trigger.hasTagRef`);
|
|
190
|
+
if (t.stageRef && !stageRefs.has(t.stageRef)) E(`wf ${w.ref} trigger.stageRef ${t.stageRef} not a defined stage`);
|
|
191
|
+
if (t.type === 'call_status' && !(t.callStatuses && t.callStatuses.length)) E(`workflow ${w.ref}: a call_status trigger needs callStatuses (else it fires on EVERY call)`);
|
|
192
|
+
if (['tag_added', 'appointment_status', 'appointment_booked'].includes(t.type)) E(`workflow ${w.ref}: trigger type "${t.type}" is not native — use contact_tag / appointment`);
|
|
187
193
|
}
|
|
188
194
|
if (!Array.isArray(w.actions) || w.actions.length === 0) E(`workflow ${w.ref}: no actions`);
|
|
189
195
|
if ((w.actions || []).length > 40) E(`workflow ${w.ref}: >40 actions (split required)`);
|
|
@@ -200,7 +206,7 @@ const nurtureTags = new Set((plan.workflows || []).filter(x => /nurture/i.test(x
|
|
|
200
206
|
if (a.type === 'assign_user' && !a.userRef) E(`${at}: needs userRef`);
|
|
201
207
|
if (a.stageRef && !stageRefs.has(a.stageRef)) E(`${at}: stageRef ${a.stageRef} not a defined stage`);
|
|
202
208
|
if (a.type === 'wait' && !(a.value > 0 && ['minutes', 'hours', 'days'].includes(a.unit))) E(`${at}: wait needs value > 0 and unit minutes|hours|days`);
|
|
203
|
-
if (a.type === 'wait_appointment' &&
|
|
209
|
+
if (a.type === 'wait_appointment' && !variants.some(t => t && t.type === 'appointment')) W(`${at}: wait_appointment outside an appointment-triggered workflow`);
|
|
204
210
|
};
|
|
205
211
|
let seenFind = false;
|
|
206
212
|
(w.actions || []).forEach((a, i) => {
|
|
@@ -58,18 +58,18 @@
|
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
"ref": "stage.contacted",
|
|
61
|
-
"aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up"],
|
|
61
|
+
"aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up", "working"],
|
|
62
62
|
"name": "Contacted"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"ref": "stage.visit_booked",
|
|
66
|
-
"aliases": ["visit booked", "appointment booked", "consult booked", "consultation booked", "exam booked", "booked", "scheduled"],
|
|
66
|
+
"aliases": ["visit booked", "appointment booked", "consult booked", "consultation booked", "exam booked", "booked", "scheduled", "=consultation"],
|
|
67
67
|
"name": "Visit Booked",
|
|
68
68
|
"conditionalOn": "goal.bookingNeeded == true"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"ref": "stage.showed",
|
|
72
|
-
"aliases": ["showed", "attended", "visit completed", "exam completed"],
|
|
72
|
+
"aliases": ["showed", "attended", "visit completed", "exam completed", "consultation completed", "consultation done", "consultation attended", "consultation showed", "consultation complete", "consultation held"],
|
|
73
73
|
"name": "Showed",
|
|
74
74
|
"conditionalOn": "goal.bookingNeeded == true"
|
|
75
75
|
},
|
|
@@ -274,6 +274,12 @@
|
|
|
274
274
|
"name": "review-requested",
|
|
275
275
|
"_note": "A review was asked for after a completed visit.",
|
|
276
276
|
"conditionalOn": "goal.bookingNeeded == true"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"ref": "tag.service_done",
|
|
280
|
+
"name": "service-complete",
|
|
281
|
+
"_note": "Only when the intake chose to ask for a review when the work is done: the team adds it, Review Request starts from it and removes it.",
|
|
282
|
+
"conditionalOn": "goal.bookingNeeded == true && goal.reviewTiming == 'When you mark the work done'"
|
|
277
283
|
}
|
|
278
284
|
],
|
|
279
285
|
"_tagNote": "Interest tags mirror the What Brings You In options and are intentionally non-clinical. The skill may add service tags from offer.summary only if compliance_notes allow it.",
|
|
@@ -524,7 +530,7 @@
|
|
|
524
530
|
{
|
|
525
531
|
"ref": "sms_template.n_sms_1",
|
|
526
532
|
"name": "Nurture Text 1 - Insurance or Cash",
|
|
527
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: will this be insurance or cash-pay? One word and we'll check your benefits before you come in
|
|
533
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: will this be insurance or cash-pay? One word and we'll check your benefits before you come in.\nReply STOP to opt out."
|
|
528
534
|
},
|
|
529
535
|
{
|
|
530
536
|
"ref": "sms_template.n_sms_2",
|
|
@@ -549,7 +555,7 @@
|
|
|
549
555
|
{
|
|
550
556
|
"ref": "sms_template.w_sms_1",
|
|
551
557
|
"name": "Win-back Text 1",
|
|
552
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Are you still planning a first visit this season, or should I close your file for now
|
|
558
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Are you still planning a first visit this season, or should I close your file for now?\nReply STOP to opt out."
|
|
553
559
|
},
|
|
554
560
|
{
|
|
555
561
|
"ref": "sms_template.w_sms_2",
|
|
@@ -1072,6 +1078,7 @@
|
|
|
1072
1078
|
"workflows": [
|
|
1073
1079
|
{
|
|
1074
1080
|
"ref": "workflow.speed_to_lead",
|
|
1081
|
+
"allowMultiple": false,
|
|
1075
1082
|
"name": "Speed to Lead",
|
|
1076
1083
|
"trigger": {
|
|
1077
1084
|
"type": "form_submission",
|
|
@@ -1157,6 +1164,7 @@
|
|
|
1157
1164
|
},
|
|
1158
1165
|
{
|
|
1159
1166
|
"ref": "workflow.missed_call",
|
|
1167
|
+
"allowMultiple": true,
|
|
1160
1168
|
"name": "Missed Call Text-Back",
|
|
1161
1169
|
"conditionalOn": "channels.sms == true",
|
|
1162
1170
|
"trigger": {
|
|
@@ -1211,13 +1219,14 @@
|
|
|
1211
1219
|
},
|
|
1212
1220
|
{
|
|
1213
1221
|
"ref": "workflow.lead_nurture",
|
|
1222
|
+
"allowMultiple": false,
|
|
1214
1223
|
"name": "Lead Nurture (30 days)",
|
|
1215
1224
|
"trigger": {
|
|
1216
1225
|
"type": "contact_tag",
|
|
1217
1226
|
"tagRef": "tag.nurture_start"
|
|
1218
1227
|
},
|
|
1219
1228
|
"stopOnResponse": true,
|
|
1220
|
-
"_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first
|
|
1229
|
+
"_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
|
|
1221
1230
|
"actions": [
|
|
1222
1231
|
{
|
|
1223
1232
|
"type": "remove_contact_tag",
|
|
@@ -1352,6 +1361,7 @@
|
|
|
1352
1361
|
},
|
|
1353
1362
|
{
|
|
1354
1363
|
"ref": "workflow.reply_route",
|
|
1364
|
+
"allowMultiple": true,
|
|
1355
1365
|
"name": "Replied - Stop & Route",
|
|
1356
1366
|
"trigger": {
|
|
1357
1367
|
"type": "customer_reply"
|
|
@@ -1406,6 +1416,7 @@
|
|
|
1406
1416
|
},
|
|
1407
1417
|
{
|
|
1408
1418
|
"ref": "workflow.win_back",
|
|
1419
|
+
"allowMultiple": false,
|
|
1409
1420
|
"name": "Win-back (30 days)",
|
|
1410
1421
|
"trigger": {
|
|
1411
1422
|
"type": "contact_tag",
|
|
@@ -1477,6 +1488,7 @@
|
|
|
1477
1488
|
},
|
|
1478
1489
|
{
|
|
1479
1490
|
"ref": "workflow.appointment_booked",
|
|
1491
|
+
"allowMultiple": true,
|
|
1480
1492
|
"name": "Visit Booked - Confirm & Move",
|
|
1481
1493
|
"trigger": {
|
|
1482
1494
|
"type": "appointment",
|
|
@@ -1498,6 +1510,15 @@
|
|
|
1498
1510
|
"type": "remove_from_workflow",
|
|
1499
1511
|
"workflowRef": "workflow.win_back"
|
|
1500
1512
|
},
|
|
1513
|
+
{
|
|
1514
|
+
"type": "remove_from_workflow",
|
|
1515
|
+
"workflowRef": "workflow.no_show"
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
"type": "remove_from_workflow",
|
|
1519
|
+
"workflowRef": "workflow.missed_call",
|
|
1520
|
+
"conditionalOn": "channels.sms == true"
|
|
1521
|
+
},
|
|
1501
1522
|
{
|
|
1502
1523
|
"type": "add_contact_tag",
|
|
1503
1524
|
"tagRef": "tag.lc_booked"
|
|
@@ -1541,6 +1562,7 @@
|
|
|
1541
1562
|
},
|
|
1542
1563
|
{
|
|
1543
1564
|
"ref": "workflow.appointment_reminders",
|
|
1565
|
+
"allowMultiple": true,
|
|
1544
1566
|
"name": "Visit Reminders",
|
|
1545
1567
|
"trigger": {
|
|
1546
1568
|
"type": "appointment",
|
|
@@ -1579,6 +1601,7 @@
|
|
|
1579
1601
|
},
|
|
1580
1602
|
{
|
|
1581
1603
|
"ref": "workflow.no_show",
|
|
1604
|
+
"allowMultiple": true,
|
|
1582
1605
|
"stagePreference": { "stage.contacted": ["no show", "noshow", "missed appointment", "did not show"] },
|
|
1583
1606
|
"name": "No-Show Rescue",
|
|
1584
1607
|
"trigger": {
|
|
@@ -1668,6 +1691,7 @@
|
|
|
1668
1691
|
},
|
|
1669
1692
|
{
|
|
1670
1693
|
"ref": "workflow.quote_follow_up",
|
|
1694
|
+
"allowMultiple": true,
|
|
1671
1695
|
"name": "Quote Follow-up",
|
|
1672
1696
|
"trigger": {
|
|
1673
1697
|
"type": "contact_tag",
|
|
@@ -1729,24 +1753,44 @@
|
|
|
1729
1753
|
},
|
|
1730
1754
|
{
|
|
1731
1755
|
"ref": "workflow.review_request",
|
|
1756
|
+
"allowMultiple": false,
|
|
1732
1757
|
"name": "Review Request",
|
|
1733
|
-
"trigger":
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1758
|
+
"trigger": [
|
|
1759
|
+
{
|
|
1760
|
+
"conditionalOn": "goal.reviewTiming == 'When you mark the work done'",
|
|
1761
|
+
"type": "contact_tag",
|
|
1762
|
+
"tagRef": "tag.service_done"
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
"type": "appointment",
|
|
1766
|
+
"appointmentStatus": "showed",
|
|
1767
|
+
"calendarRef": "calendar.new_patient"
|
|
1768
|
+
}
|
|
1769
|
+
],
|
|
1738
1770
|
"stopOnResponse": true,
|
|
1739
1771
|
"conditionalOn": "goal.bookingNeeded == true",
|
|
1740
|
-
"_design": "On a completed visit (status showed): tag, wait two hours, text the review link, then an email three days later if nothing came back. Any reply stops it. The review link is a custom value the operator pastes once (handoff.review_link). Closes the Layer 4 gap named on the hub: review requests had no build feature (2026-09-14).",
|
|
1772
|
+
"_design": "On a completed visit (status showed): tag, wait two hours, text the review link, then an email three days later if nothing came back. Any reply stops it. The review link is a custom value the operator pastes once (handoff.review_link). Closes the Layer 4 gap named on the hub: review requests had no build feature (2026-09-14). The intake's review timing question (goal.reviewTiming, 2026-09-29) can make it the day after the visit instead, or start it from the service-complete tag the team adds when the work is done.",
|
|
1741
1773
|
"actions": [
|
|
1774
|
+
{
|
|
1775
|
+
"type": "remove_contact_tag",
|
|
1776
|
+
"tagRef": "tag.service_done",
|
|
1777
|
+
"conditionalOn": "goal.reviewTiming == 'When you mark the work done'"
|
|
1778
|
+
},
|
|
1742
1779
|
{
|
|
1743
1780
|
"type": "add_contact_tag",
|
|
1744
1781
|
"tagRef": "tag.review_requested"
|
|
1745
1782
|
},
|
|
1783
|
+
{
|
|
1784
|
+
"type": "wait",
|
|
1785
|
+
"value": 1,
|
|
1786
|
+
"unit": "days",
|
|
1787
|
+
"conditionalOn": "goal.reviewTiming == 'The day after a visit marked showed'"
|
|
1788
|
+
},
|
|
1746
1789
|
{
|
|
1747
1790
|
"type": "wait",
|
|
1748
1791
|
"value": 2,
|
|
1749
|
-
"unit": "hours"
|
|
1792
|
+
"unit": "hours",
|
|
1793
|
+
"conditionalOn": "goal.reviewTiming != 'The day after a visit marked showed'"
|
|
1750
1794
|
},
|
|
1751
1795
|
{
|
|
1752
1796
|
"type": "send_sms",
|
|
@@ -1950,6 +1994,15 @@
|
|
|
1950
1994
|
"cv.review_link"
|
|
1951
1995
|
]
|
|
1952
1996
|
},
|
|
1997
|
+
{
|
|
1998
|
+
"ref": "handoff.review_tag",
|
|
1999
|
+
"conditionalOn": "goal.bookingNeeded == true && goal.reviewTiming == 'When you mark the work done'",
|
|
2000
|
+
"owner": "OPERATOR-UI",
|
|
2001
|
+
"title": "Start Review Request by tagging service-complete when the work is done",
|
|
2002
|
+
"trigger": "always (1 minute to read)",
|
|
2003
|
+
"instruction": "The intake chose to ask for a review when the work is done, so Review Request starts from the tag service-complete, not from a visit. Add that tag on the contact when their care is finished. Each patient is asked for a review once.",
|
|
2004
|
+
"successCheck": "A contact tagged service-complete receives the review text two hours later"
|
|
2005
|
+
},
|
|
1953
2006
|
{
|
|
1954
2007
|
"ref": "handoff.quote_tag",
|
|
1955
2008
|
"owner": "OPERATOR-UI",
|
|
@@ -55,18 +55,18 @@
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"ref": "stage.contacted",
|
|
58
|
-
"aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up"],
|
|
58
|
+
"aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up", "working"],
|
|
59
59
|
"name": "Contacted"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"ref": "stage.call_booked",
|
|
63
|
-
"aliases": ["call booked", "discovery call booked", "strategy call booked", "consult booked", "appointment booked", "booked", "scheduled"],
|
|
63
|
+
"aliases": ["call booked", "discovery call booked", "strategy call booked", "consult booked", "appointment booked", "booked", "scheduled", "=consultation"],
|
|
64
64
|
"name": "Call Booked",
|
|
65
65
|
"conditionalOn": "goal.bookingNeeded == true"
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
"ref": "stage.call_held",
|
|
69
|
-
"aliases": ["call held", "call completed", "showed", "attended"],
|
|
69
|
+
"aliases": ["call held", "call completed", "showed", "attended", "consultation completed", "consultation done", "consultation attended", "consultation showed", "consultation complete", "consultation held"],
|
|
70
70
|
"name": "Call Held",
|
|
71
71
|
"conditionalOn": "goal.bookingNeeded == true"
|
|
72
72
|
},
|
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
{
|
|
275
275
|
"ref": "tag.service_done",
|
|
276
276
|
"name": "service-complete",
|
|
277
|
-
"_note": "Added when the work is done and paid for. Starts Review Request; removed by it
|
|
277
|
+
"_note": "Added when the work is done and paid for. Starts Review Request; removed by it. Each contact is asked for a review once."
|
|
278
278
|
}
|
|
279
279
|
],
|
|
280
280
|
"_tagNote": "Interest tags mirror the Program Interest options; the skill renames them to the real program names from the offer at fill time.",
|
|
@@ -528,7 +528,7 @@
|
|
|
528
528
|
{
|
|
529
529
|
"ref": "sms_template.n_sms_1",
|
|
530
530
|
"name": "Nurture Text 1 - One Word",
|
|
531
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: what's the bigger gap right now, clarity on what to do, or structure to actually do it? One word is enough
|
|
531
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: what's the bigger gap right now, clarity on what to do, or structure to actually do it? One word is enough.\nReply STOP to opt out."
|
|
532
532
|
},
|
|
533
533
|
{
|
|
534
534
|
"ref": "sms_template.n_sms_2",
|
|
@@ -553,7 +553,7 @@
|
|
|
553
553
|
{
|
|
554
554
|
"ref": "sms_template.w_sms_1",
|
|
555
555
|
"name": "Win-back Text 1",
|
|
556
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is {{audience.painPoints[0] || the thing you want to change}} still on your list this quarter, or should I close your application for now
|
|
556
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Is {{audience.painPoints[0] || the thing you want to change}} still on your list this quarter, or should I close your application for now?\nReply STOP to opt out."
|
|
557
557
|
},
|
|
558
558
|
{
|
|
559
559
|
"ref": "sms_template.w_sms_2",
|
|
@@ -1073,6 +1073,7 @@
|
|
|
1073
1073
|
"workflows": [
|
|
1074
1074
|
{
|
|
1075
1075
|
"ref": "workflow.speed_to_lead",
|
|
1076
|
+
"allowMultiple": false,
|
|
1076
1077
|
"name": "Speed to Lead",
|
|
1077
1078
|
"trigger": {
|
|
1078
1079
|
"type": "form_submission",
|
|
@@ -1158,6 +1159,7 @@
|
|
|
1158
1159
|
},
|
|
1159
1160
|
{
|
|
1160
1161
|
"ref": "workflow.missed_call",
|
|
1162
|
+
"allowMultiple": true,
|
|
1161
1163
|
"name": "Missed Call Text-Back",
|
|
1162
1164
|
"conditionalOn": "channels.sms == true",
|
|
1163
1165
|
"trigger": {
|
|
@@ -1212,13 +1214,14 @@
|
|
|
1212
1214
|
},
|
|
1213
1215
|
{
|
|
1214
1216
|
"ref": "workflow.lead_nurture",
|
|
1217
|
+
"allowMultiple": false,
|
|
1215
1218
|
"name": "Lead Nurture (30 days)",
|
|
1216
1219
|
"trigger": {
|
|
1217
1220
|
"type": "contact_tag",
|
|
1218
1221
|
"tagRef": "tag.nurture_start"
|
|
1219
1222
|
},
|
|
1220
1223
|
"stopOnResponse": true,
|
|
1221
|
-
"_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first
|
|
1224
|
+
"_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
|
|
1222
1225
|
"actions": [
|
|
1223
1226
|
{
|
|
1224
1227
|
"type": "remove_contact_tag",
|
|
@@ -1353,6 +1356,7 @@
|
|
|
1353
1356
|
},
|
|
1354
1357
|
{
|
|
1355
1358
|
"ref": "workflow.reply_route",
|
|
1359
|
+
"allowMultiple": true,
|
|
1356
1360
|
"name": "Replied - Stop & Route",
|
|
1357
1361
|
"trigger": {
|
|
1358
1362
|
"type": "customer_reply"
|
|
@@ -1407,6 +1411,7 @@
|
|
|
1407
1411
|
},
|
|
1408
1412
|
{
|
|
1409
1413
|
"ref": "workflow.win_back",
|
|
1414
|
+
"allowMultiple": false,
|
|
1410
1415
|
"name": "Win-back (30 days)",
|
|
1411
1416
|
"trigger": {
|
|
1412
1417
|
"type": "contact_tag",
|
|
@@ -1478,6 +1483,7 @@
|
|
|
1478
1483
|
},
|
|
1479
1484
|
{
|
|
1480
1485
|
"ref": "workflow.appointment_booked",
|
|
1486
|
+
"allowMultiple": true,
|
|
1481
1487
|
"name": "Call Booked - Confirm & Move",
|
|
1482
1488
|
"trigger": {
|
|
1483
1489
|
"type": "appointment",
|
|
@@ -1499,6 +1505,15 @@
|
|
|
1499
1505
|
"type": "remove_from_workflow",
|
|
1500
1506
|
"workflowRef": "workflow.win_back"
|
|
1501
1507
|
},
|
|
1508
|
+
{
|
|
1509
|
+
"type": "remove_from_workflow",
|
|
1510
|
+
"workflowRef": "workflow.no_show"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
"type": "remove_from_workflow",
|
|
1514
|
+
"workflowRef": "workflow.missed_call",
|
|
1515
|
+
"conditionalOn": "channels.sms == true"
|
|
1516
|
+
},
|
|
1502
1517
|
{
|
|
1503
1518
|
"type": "add_contact_tag",
|
|
1504
1519
|
"tagRef": "tag.lc_booked"
|
|
@@ -1542,6 +1557,7 @@
|
|
|
1542
1557
|
},
|
|
1543
1558
|
{
|
|
1544
1559
|
"ref": "workflow.appointment_reminders",
|
|
1560
|
+
"allowMultiple": true,
|
|
1545
1561
|
"name": "Call Reminders",
|
|
1546
1562
|
"trigger": {
|
|
1547
1563
|
"type": "appointment",
|
|
@@ -1580,6 +1596,7 @@
|
|
|
1580
1596
|
},
|
|
1581
1597
|
{
|
|
1582
1598
|
"ref": "workflow.no_show",
|
|
1599
|
+
"allowMultiple": true,
|
|
1583
1600
|
"stagePreference": { "stage.contacted": ["no show", "noshow", "missed appointment", "did not show"] },
|
|
1584
1601
|
"name": "No-Show Rescue",
|
|
1585
1602
|
"trigger": {
|
|
@@ -1669,6 +1686,7 @@
|
|
|
1669
1686
|
},
|
|
1670
1687
|
{
|
|
1671
1688
|
"ref": "workflow.quote_follow_up",
|
|
1689
|
+
"allowMultiple": true,
|
|
1672
1690
|
"name": "Quote Follow-up",
|
|
1673
1691
|
"trigger": {
|
|
1674
1692
|
"type": "contact_tag",
|
|
@@ -1730,6 +1748,7 @@
|
|
|
1730
1748
|
},
|
|
1731
1749
|
{
|
|
1732
1750
|
"ref": "workflow.review_request",
|
|
1751
|
+
"allowMultiple": false,
|
|
1733
1752
|
"name": "Review Request",
|
|
1734
1753
|
"trigger": {
|
|
1735
1754
|
"type": "contact_tag",
|
|
@@ -420,7 +420,7 @@
|
|
|
420
420
|
{
|
|
421
421
|
"ref": "sms_template.n_sms_1",
|
|
422
422
|
"name": "Nurture Text 1 - One Word",
|
|
423
|
-
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: best sellers, new arrivals, or a gift? One word and I'll send you the right page
|
|
423
|
+
"body": "{{contact.first_name}}, quick one from {{custom_values.owner_first_name}} at {{custom_values.business_name}}: best sellers, new arrivals, or a gift? One word and I'll send you the right page.\nReply STOP to opt out."
|
|
424
424
|
},
|
|
425
425
|
{
|
|
426
426
|
"ref": "sms_template.n_sms_2",
|
|
@@ -445,7 +445,7 @@
|
|
|
445
445
|
{
|
|
446
446
|
"ref": "sms_template.w_sms_1",
|
|
447
447
|
"name": "Win-back Text 1",
|
|
448
|
-
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Still interested, or would you rather only hear about new arrivals? Reply 1 for still interested, 2 for new arrivals only
|
|
448
|
+
"body": "Hi {{contact.first_name}}, it's {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Still interested, or would you rather only hear about new arrivals? Reply 1 for still interested, 2 for new arrivals only.\nReply STOP to opt out."
|
|
449
449
|
},
|
|
450
450
|
{
|
|
451
451
|
"ref": "sms_template.w_sms_2",
|
|
@@ -814,6 +814,7 @@
|
|
|
814
814
|
"workflows": [
|
|
815
815
|
{
|
|
816
816
|
"ref": "workflow.speed_to_lead",
|
|
817
|
+
"allowMultiple": false,
|
|
817
818
|
"name": "Speed to Lead",
|
|
818
819
|
"trigger": {
|
|
819
820
|
"type": "form_submission",
|
|
@@ -899,6 +900,7 @@
|
|
|
899
900
|
},
|
|
900
901
|
{
|
|
901
902
|
"ref": "workflow.missed_call",
|
|
903
|
+
"allowMultiple": true,
|
|
902
904
|
"name": "Missed Call Text-Back",
|
|
903
905
|
"conditionalOn": "channels.sms == true",
|
|
904
906
|
"trigger": {
|
|
@@ -953,13 +955,14 @@
|
|
|
953
955
|
},
|
|
954
956
|
{
|
|
955
957
|
"ref": "workflow.lead_nurture",
|
|
958
|
+
"allowMultiple": false,
|
|
956
959
|
"name": "Lead Nurture (30 days)",
|
|
957
960
|
"trigger": {
|
|
958
961
|
"type": "contact_tag",
|
|
959
962
|
"tagRef": "tag.nurture_start"
|
|
960
963
|
},
|
|
961
964
|
"stopOnResponse": true,
|
|
962
|
-
"_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first
|
|
965
|
+
"_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
|
|
963
966
|
"actions": [
|
|
964
967
|
{
|
|
965
968
|
"type": "remove_contact_tag",
|
|
@@ -1094,6 +1097,7 @@
|
|
|
1094
1097
|
},
|
|
1095
1098
|
{
|
|
1096
1099
|
"ref": "workflow.reply_route",
|
|
1100
|
+
"allowMultiple": true,
|
|
1097
1101
|
"name": "Replied - Stop & Route",
|
|
1098
1102
|
"trigger": {
|
|
1099
1103
|
"type": "customer_reply"
|
|
@@ -1143,6 +1147,7 @@
|
|
|
1143
1147
|
},
|
|
1144
1148
|
{
|
|
1145
1149
|
"ref": "workflow.win_back",
|
|
1150
|
+
"allowMultiple": false,
|
|
1146
1151
|
"name": "Win-back (30 days)",
|
|
1147
1152
|
"trigger": {
|
|
1148
1153
|
"type": "contact_tag",
|
|
@@ -1214,6 +1219,7 @@
|
|
|
1214
1219
|
},
|
|
1215
1220
|
{
|
|
1216
1221
|
"ref": "workflow.review_request",
|
|
1222
|
+
"allowMultiple": false,
|
|
1217
1223
|
"name": "Review Requests",
|
|
1218
1224
|
"trigger": {
|
|
1219
1225
|
"type": "contact_tag",
|
|
@@ -1359,7 +1365,7 @@
|
|
|
1359
1365
|
"owner": "OPERATOR-EXT",
|
|
1360
1366
|
"title": "Connect store orders to GHL",
|
|
1361
1367
|
"trigger": "always",
|
|
1362
|
-
"instruction": "Connect your store so orders reach GHL (Shopify: the native integration under Integrations; other platforms: an order webhook to an inbound webhook trigger). Then add a small workflow: on order → tag lifecycle-first-order, remove from Lead Nurture and Win-back, move the pipeline card to
|
|
1368
|
+
"instruction": "Connect your store so orders reach GHL (Shopify: the native integration under Integrations; other platforms: an order webhook to an inbound webhook trigger). Then add a small workflow: on order → tag lifecycle-first-order, remove from Lead Nurture and Win-back, move the pipeline card to {{stage.first_order}}. Until this exists, staff move cards by hand.",
|
|
1363
1369
|
"produces": null,
|
|
1364
1370
|
"successCheck": "A test order appears on the contact in GHL within a minute",
|
|
1365
1371
|
"blocks": []
|