@elitedcs/ghl-mcp 3.83.8 → 3.84.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.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "presetId": "local_service",
3
- "presetVersion": "2.0.0",
3
+ "presetVersion": "2.1.0",
4
4
  "schemaVersion": "0.1",
5
5
  "title": "Local Service",
6
- "description": "A local service business system at v2 depth (roofing, HVAC, plumbing, electrical, landscaping, cleaning, pest, restoration and similar). Jobs pipeline, service / urgency / address / quote fields, source + lifecycle + interest tags, a round-robin Estimate Visit calendar, an A2P-compliant quote-request form, 15 email + 17 SMS send-ready templates, and 8 workflows: Speed to Lead (15 actions, 5 touches in 24 h with an \"is this urgent?\" fork by reply, then a hand-off), Missed Call Text-Back (the single highest-value automation for a trade), Lead Nurture (31 days, 11 touches), Replied - Stop & Route, Win-back (31 days, 5 touches; seasonal), Estimate Booked - Confirm & Move, Estimate Reminders, No-Show Rescue.",
6
+ "description": "A local service business system at v2 depth (roofing, HVAC, plumbing, electrical, landscaping, cleaning, pest, restoration and similar). Jobs pipeline, service / urgency / address / quote fields, source + lifecycle + interest tags, a round-robin Estimate Visit calendar, an A2P-compliant quote-request form, 15 email + 17 SMS send-ready templates, and 10 workflows: Speed to Lead (15 actions, 5 touches in 24 h with an \"is this urgent?\" fork by reply, then a hand-off), Missed Call Text-Back (the single highest-value automation for a trade), Lead Nurture (31 days, 11 touches), Replied - Stop & Route, Win-back (31 days, 5 touches; seasonal), Estimate Booked - Confirm & Move, Estimate Reminders, No-Show Rescue, plus Quote Follow-up and Review Request (v2.1.0).",
7
7
  "default": false,
8
8
  "selectors": {
9
9
  "businessTypes": [
@@ -256,6 +256,26 @@
256
256
  {
257
257
  "ref": "tag.interest_emergency",
258
258
  "name": "interest-emergency"
259
+ },
260
+ {
261
+ "ref": "tag.quote_sent",
262
+ "name": "quote-sent",
263
+ "_note": "Added when a quote goes out. Starts Quote Follow-up; removed by it so a later quote can start it again."
264
+ },
265
+ {
266
+ "ref": "tag.quote_stale",
267
+ "name": "quote-stale",
268
+ "_note": "A quote that got no answer through the whole follow-up."
269
+ },
270
+ {
271
+ "ref": "tag.review_requested",
272
+ "name": "review-requested",
273
+ "_note": "A review was asked for after a completed appointment."
274
+ },
275
+ {
276
+ "ref": "tag.service_done",
277
+ "name": "service-complete",
278
+ "_note": "Added when the work is done and paid for. Starts Review Request; removed by it so the next job can start it again."
259
279
  }
260
280
  ],
261
281
  "_tagNote": "Interest tags mirror the Service Needed options. The skill derives trade-specific ones from offer.summary (e.g. interest-roof-replacement) at fill time.",
@@ -285,6 +305,13 @@
285
305
  "value": "",
286
306
  "filledBy": "handoff.calendar_oauth",
287
307
  "conditionalOn": "goal.bookingNeeded == true"
308
+ },
309
+ {
310
+ "ref": "cv.review_link",
311
+ "name": "review_link",
312
+ "value": "",
313
+ "filledBy": "handoff.review_link",
314
+ "_fill": "the Google review link; blank until the operator pastes it"
288
315
  }
289
316
  ],
290
317
  "calendars": [
@@ -293,7 +320,7 @@
293
320
  "conditionalOn": "goal.bookingNeeded == true",
294
321
  "name": "{{business.name}} Estimate Visit || Estimate Visit",
295
322
  "calendarType": "round_robin",
296
- "_slotDuration": "Carry the appointment length from the brief (brief.calendars[].durationMinutes, or the words in the calendar answer: \"15 minutes\" \u2192 15) onto slotDuration (minutes, slotDurationUnit \"mins\"). This preset value applies ONLY when the brief gives no length; without any slotDuration GoHighLevel builds 30-minute slots (finding 25).",
323
+ "_slotDuration": "Carry the appointment length from the brief (brief.calendars[].durationMinutes, or the words in the calendar answer: \"15 minutes\" → 15) onto slotDuration (minutes, slotDurationUnit \"mins\"). This preset value applies ONLY when the brief gives no length; without any slotDuration GoHighLevel builds 30-minute slots (finding 25).",
297
324
  "slotDuration": 60,
298
325
  "slotDurationUnit": "mins",
299
326
  "teamMemberRefs": [
@@ -319,7 +346,6 @@
319
346
  }
320
347
  ],
321
348
  "availabilityType": 0,
322
- "slotDuration": 60,
323
349
  "slotBuffer": 30,
324
350
  "requiresStaff": true,
325
351
  "notes": "Default Mon-Fri 8-6, 60-minute estimate slots with 30 minutes drive time; operator edits after build. teamMemberRefs points at the estimators in the plan users list."
@@ -460,6 +486,18 @@
460
486
  "name": "No-Show 2 - Still Want This?",
461
487
  "subject": "Still want the quote?",
462
488
  "html": "<div style=\"font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1f2937;max-width:600px;\"><p style=\"margin:0 0 14px;\">Hi {{contact.first_name}},</p><p style=\"margin:0 0 14px;\">Quick and honest: if the timing is wrong, say so and I'll check back next season. If you still want a written quote, the calendar is open and the visit is still free.</p><p style=\"margin:0 0 14px;\"><a href=\"{{custom_values.booking_link}}\" style=\"display:inline-block;background:#1a56db;color:#ffffff;padding:12px 20px;border-radius:6px;text-decoration:none;font-weight:600;\">Pick a new time</a></p><p style=\"margin:0 0 14px;\">Either way, thanks for considering {{custom_values.business_name}}.</p><p style=\"margin:0;\">{{custom_values.owner_first_name}}<br>{{custom_values.business_name}} · {{custom_values.business_phone}}</p></div>"
489
+ },
490
+ {
491
+ "ref": "email_template.quote_email_1",
492
+ "name": "Quote Follow-up - Day 3 Email",
493
+ "subject": "Any questions on your quote, {{contact.first_name}}?",
494
+ "html": "<div style=\"font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1f2937;max-width:600px;\"><p style=\"margin:0 0 14px;\">Hi {{contact.first_name}},</p><p style=\"margin:0 0 14px;\">You have had the quote from {{custom_values.business_name}} for a few days, so this is a short check-in rather than a push.</p><p style=\"margin:0 0 14px;\">Three things people usually ask at this point: what is included, what can wait, and how payment works. Reply with any of those and I will answer plainly.</p><p style=\"margin:0 0 14px;\">If you would rather talk it through, pick a time here:</p><p style=\"margin:0 0 14px;\"><a href=\"{{custom_values.booking_link}}\" style=\"display:inline-block;background:#1a56db;color:#ffffff;padding:12px 20px;border-radius:6px;text-decoration:none;font-weight:600;\">Book a quick call</a></p><p style=\"margin:0 0 14px;\">No pressure either way. The numbers stand for 30 days.</p><p style=\"margin:0;\">{{custom_values.owner_first_name}}<br>{{custom_values.business_name}} · {{custom_values.business_phone}}</p></div>"
495
+ },
496
+ {
497
+ "ref": "email_template.review_email_1",
498
+ "name": "Review Request - Day 3 Email",
499
+ "subject": "One minute for a review, {{contact.first_name}}?",
500
+ "html": "<div style=\"font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.5;color:#1f2937;max-width:600px;\"><p style=\"margin:0 0 14px;\">Hi {{contact.first_name}},</p><p style=\"margin:0 0 14px;\">Thank you again for your appointment with {{custom_values.business_name}} this week.</p><p style=\"margin:0 0 14px;\">If it went well, a short review is the most useful thing you could do for us. It takes about a minute and it is how the next person decides to trust us.</p><p style=\"margin:0 0 14px;\"><a href=\"{{custom_values.review_link}}\" style=\"display:inline-block;background:#1a56db;color:#ffffff;padding:12px 20px;border-radius:6px;text-decoration:none;font-weight:600;\">Leave a review</a></p><p style=\"margin:0 0 14px;\">If anything was not right, reply to this email instead and I will fix it personally.</p><p style=\"margin:0;\">{{custom_values.owner_first_name}}<br>{{custom_values.business_name}} · {{custom_values.business_phone}}</p></div>"
463
501
  }
464
502
  ],
465
503
  "sms": [
@@ -547,6 +585,21 @@
547
585
  "ref": "sms_template.ns_sms_2",
548
586
  "name": "No-Show Text 2",
549
587
  "body": "{{contact.first_name}}, {{custom_values.owner_first_name}} here. I kept a couple of estimate slots open for you this week. Want one? Reply with a day that works."
588
+ },
589
+ {
590
+ "ref": "sms_template.quote_sms_1",
591
+ "name": "Quote Follow-up - Next Day Text",
592
+ "body": "Hi {{contact.first_name}}, {{custom_values.owner_first_name}} at {{custom_values.business_name}}. Did the quote make sense? Happy to walk through any part of it. Reply here or call {{custom_values.business_phone}}. Reply STOP to opt out."
593
+ },
594
+ {
595
+ "ref": "sms_template.quote_sms_2",
596
+ "name": "Quote Follow-up - Last Text",
597
+ "body": "Hi {{contact.first_name}}, last note from {{custom_values.business_name}} about your quote. If the timing is off, one word back and I will check in when it suits you. Reply STOP to opt out."
598
+ },
599
+ {
600
+ "ref": "sms_template.review_sms_1",
601
+ "name": "Review Request - Same Day Text",
602
+ "body": "Thanks for having us out, {{contact.first_name}}. If we did right by you, a quick review helps the next person find us: {{custom_values.review_link}} Reply STOP to opt out."
550
603
  }
551
604
  ]
552
605
  },
@@ -761,6 +814,34 @@
761
814
  "custom_values.owner_first_name",
762
815
  "custom_values.business_phone"
763
816
  ]
817
+ },
818
+ {
819
+ "ref": "email.quote_email_1",
820
+ "name": "Quote Follow-up - Day 3 Email",
821
+ "subject": "Any questions on your quote, {{contact.first_name}}?",
822
+ "templateRef": "email_template.quote_email_1",
823
+ "copyDirection": "Day three after the quote. Check-in, not a push. Name the three questions people ask (what is included, what can wait, how payment works), invite a reply, booking CTA for a quick call. Tone: brief.voice.threeWords.",
824
+ "mergeTags": [
825
+ "contact.first_name",
826
+ "custom_values.business_name",
827
+ "custom_values.booking_link",
828
+ "custom_values.owner_first_name",
829
+ "custom_values.business_phone"
830
+ ]
831
+ },
832
+ {
833
+ "ref": "email.review_email_1",
834
+ "name": "Review Request - Day 3 Email",
835
+ "subject": "One minute for a review, {{contact.first_name}}?",
836
+ "templateRef": "email_template.review_email_1",
837
+ "copyDirection": "Three days after a completed appointment with no review yet. Thank them, why it matters, the review button, and an honest line: if anything was not right, reply instead. Tone: brief.voice.threeWords.",
838
+ "mergeTags": [
839
+ "contact.first_name",
840
+ "custom_values.business_name",
841
+ "custom_values.review_link",
842
+ "custom_values.owner_first_name",
843
+ "custom_values.business_phone"
844
+ ]
764
845
  }
765
846
  ],
766
847
  "sms": [
@@ -954,6 +1035,41 @@
954
1035
  "custom_values.owner_first_name"
955
1036
  ],
956
1037
  "conditionalOn": "channels.sms == true"
1038
+ },
1039
+ {
1040
+ "ref": "sms.quote_sms_1",
1041
+ "name": "Quote Follow-up - Next Day Text",
1042
+ "templateRef": "sms_template.quote_sms_1",
1043
+ "copyDirection": "One day after the quote went out. Owner's first name and business, one question: did it make sense. Offer to walk through it, reply or call. STOP line.",
1044
+ "mergeTags": [
1045
+ "contact.first_name",
1046
+ "custom_values.owner_first_name",
1047
+ "custom_values.business_name",
1048
+ "custom_values.business_phone"
1049
+ ],
1050
+ "conditionalOn": "channels.sms == true"
1051
+ },
1052
+ {
1053
+ "ref": "sms.quote_sms_2",
1054
+ "name": "Quote Follow-up - Last Text",
1055
+ "templateRef": "sms_template.quote_sms_2",
1056
+ "copyDirection": "Last text of the follow-up, five days in. No guilt: if the timing is off, one word back and we check in later. STOP line.",
1057
+ "mergeTags": [
1058
+ "contact.first_name",
1059
+ "custom_values.business_name"
1060
+ ],
1061
+ "conditionalOn": "channels.sms == true"
1062
+ },
1063
+ {
1064
+ "ref": "sms.review_sms_1",
1065
+ "name": "Review Request - Same Day Text",
1066
+ "templateRef": "sms_template.review_sms_1",
1067
+ "copyDirection": "Two hours after the service-complete tag, i.e. a finished and paid appointment. Thank them, one sentence on why a review helps, the review link. STOP line.",
1068
+ "mergeTags": [
1069
+ "contact.first_name",
1070
+ "custom_values.review_link"
1071
+ ],
1072
+ "conditionalOn": "channels.sms == true"
957
1073
  }
958
1074
  ],
959
1075
  "workflows": [
@@ -1551,6 +1667,106 @@
1551
1667
  }
1552
1668
  ],
1553
1669
  "conditionalOn": "goal.bookingNeeded == true"
1670
+ },
1671
+ {
1672
+ "ref": "workflow.quote_follow_up",
1673
+ "name": "Quote Follow-up",
1674
+ "trigger": {
1675
+ "type": "contact_tag",
1676
+ "tagRef": "tag.quote_sent"
1677
+ },
1678
+ "stopOnResponse": true,
1679
+ "_design": "Starts when the quote-sent tag is added (by hand, or by the estimate workflow the operator builds in the UI). Removes its own trigger tag first so the next quote can start it again. Text next day, email on day 3, last text on day 5, then a call task for the owner and the quote-stale tag. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over. Closes the Layer 4 gap named on the hub: the assessment measured quote follow-up and nothing built it (2026-09-14).",
1680
+ "actions": [
1681
+ {
1682
+ "type": "remove_contact_tag",
1683
+ "tagRef": "tag.quote_sent"
1684
+ },
1685
+ {
1686
+ "type": "wait",
1687
+ "value": 1,
1688
+ "unit": "days"
1689
+ },
1690
+ {
1691
+ "type": "send_sms",
1692
+ "smsRef": "sms.quote_sms_1",
1693
+ "conditionalOn": "channels.sms == true"
1694
+ },
1695
+ {
1696
+ "type": "wait",
1697
+ "value": 2,
1698
+ "unit": "days"
1699
+ },
1700
+ {
1701
+ "type": "send_email",
1702
+ "emailRef": "email.quote_email_1"
1703
+ },
1704
+ {
1705
+ "type": "wait",
1706
+ "value": 2,
1707
+ "unit": "days"
1708
+ },
1709
+ {
1710
+ "type": "send_sms",
1711
+ "smsRef": "sms.quote_sms_2",
1712
+ "conditionalOn": "channels.sms == true"
1713
+ },
1714
+ {
1715
+ "type": "wait",
1716
+ "value": 2,
1717
+ "unit": "days"
1718
+ },
1719
+ {
1720
+ "type": "task_notification",
1721
+ "userRef": "user.owner",
1722
+ "title": "Call {{contact.first_name}} about their quote",
1723
+ "body": "No reply to the quote in a week. One call, no pressure: what would make this a yes, or when should we check back.",
1724
+ "dueDate": "1"
1725
+ },
1726
+ {
1727
+ "type": "add_contact_tag",
1728
+ "tagRef": "tag.quote_stale"
1729
+ }
1730
+ ]
1731
+ },
1732
+ {
1733
+ "ref": "workflow.review_request",
1734
+ "name": "Review Request",
1735
+ "trigger": {
1736
+ "type": "contact_tag",
1737
+ "tagRef": "tag.service_done"
1738
+ },
1739
+ "stopOnResponse": true,
1740
+ "_design": "Starts from the service-complete tag, added when the work is done and paid for (by hand, or from the won stage of the pipeline). Never from a consult, estimate or discovery call: a prospect who did not buy is not asked for a review. On a completed appointment (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).",
1741
+ "actions": [
1742
+ {
1743
+ "type": "remove_contact_tag",
1744
+ "tagRef": "tag.service_done"
1745
+ },
1746
+ {
1747
+ "type": "add_contact_tag",
1748
+ "tagRef": "tag.review_requested"
1749
+ },
1750
+ {
1751
+ "type": "wait",
1752
+ "value": 2,
1753
+ "unit": "hours"
1754
+ },
1755
+ {
1756
+ "type": "send_sms",
1757
+ "smsRef": "sms.review_sms_1",
1758
+ "conditionalOn": "channels.sms == true"
1759
+ },
1760
+ {
1761
+ "type": "wait",
1762
+ "value": 3,
1763
+ "unit": "days"
1764
+ },
1765
+ {
1766
+ "type": "send_email",
1767
+ "emailRef": "email.review_email_1"
1768
+ }
1769
+ ]
1554
1770
  }
1555
1771
  ],
1556
1772
  "handoffs": [
@@ -1568,7 +1784,8 @@
1568
1784
  "workflow.reply_route",
1569
1785
  "workflow.appointment_booked",
1570
1786
  "workflow.no_show",
1571
- "workflow.missed_call"
1787
+ "workflow.missed_call",
1788
+ "workflow.quote_follow_up"
1572
1789
  ],
1573
1790
  "_blocksNote": "Workflows with a notification or task pointed at user.__pending__ stay DRAFT until a real user is assigned. Prune entries whose workflow was dropped by conditional pruning."
1574
1791
  },
@@ -1588,7 +1805,9 @@
1588
1805
  "workflow.win_back",
1589
1806
  "workflow.appointment_booked",
1590
1807
  "workflow.appointment_reminders",
1591
- "workflow.no_show"
1808
+ "workflow.no_show",
1809
+ "workflow.quote_follow_up",
1810
+ "workflow.review_request"
1592
1811
  ]
1593
1812
  },
1594
1813
  {
@@ -1662,7 +1881,9 @@
1662
1881
  "sms.appt_24h_sms",
1663
1882
  "sms.appt_2h_sms",
1664
1883
  "sms.ns_sms_1",
1665
- "sms.ns_sms_2"
1884
+ "sms.ns_sms_2",
1885
+ "workflow.quote_follow_up",
1886
+ "workflow.review_request"
1666
1887
  ],
1667
1888
  "_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."
1668
1889
  },
@@ -1701,8 +1922,38 @@
1701
1922
  "email.appt_confirm_email",
1702
1923
  "email.appt_prep_email",
1703
1924
  "email.ns_email_1",
1704
- "email.ns_email_2"
1925
+ "email.ns_email_2",
1926
+ "email.quote_email_1",
1927
+ "email.review_email_1"
1705
1928
  ]
1929
+ },
1930
+ {
1931
+ "ref": "handoff.review_link",
1932
+ "owner": "OPERATOR-UI",
1933
+ "title": "Paste the Google review link so review requests point somewhere",
1934
+ "trigger": "always (2 minutes)",
1935
+ "instruction": "In Google Business Profile, open the profile, choose Ask for reviews and copy the short link. Then in the sub-account: Settings > Custom Values: set review_link to that link. Until it is set, the Review Request workflow sends a text with no link, so do this before the first appointment is marked showed.",
1936
+ "produces": "cv.review_link",
1937
+ "successCheck": "review_link shows a real https link, not a blank",
1938
+ "blocks": [
1939
+ "cv.review_link"
1940
+ ]
1941
+ },
1942
+ {
1943
+ "ref": "handoff.quote_tag",
1944
+ "owner": "OPERATOR-UI",
1945
+ "title": "Start Quote Follow-up by tagging quote-sent when a quote goes out",
1946
+ "trigger": "always (1 minute to read)",
1947
+ "instruction": "Quote Follow-up starts from the tag quote-sent. Add that tag to the contact whenever a quote goes out: by hand on the contact, or, if the quotes are sent as GoHighLevel estimates, build one workflow in the UI with the Estimate trigger (status sent) whose only step adds the tag quote-sent. The follow-up removes the tag itself, so the next quote starts it again.",
1948
+ "successCheck": "A contact tagged quote-sent receives the next-day text"
1949
+ },
1950
+ {
1951
+ "ref": "handoff.review_tag",
1952
+ "owner": "OPERATOR-UI",
1953
+ "title": "Start Review Request by tagging service-complete when the work is done",
1954
+ "trigger": "always (1 minute to read)",
1955
+ "instruction": "Review Request starts from the tag service-complete. Add that tag when the job is finished and paid for: by hand on the contact, or from the pipeline's won stage with one small workflow whose only step adds the tag. It never starts from a consult, estimate or discovery call, so nobody who did not buy is asked for a review.",
1956
+ "successCheck": "A contact tagged service-complete receives the review text two hours later"
1706
1957
  }
1707
1958
  ]
1708
1959
  }
@@ -1,6 +1,6 @@
1
1
  # Preset: Med Spa (default)
2
2
 
3
- **File:** `med-spa.preset.json` · **id:** `med_spa` · **version:** 2.0.0 · **default:** yes
3
+ **File:** `med-spa.preset.json` · **id:** `med_spa` · **version:** 2.1.0 · **default:** yes
4
4
  **Source:** ghl-command showcase Modules 0-7 + proven med-spa builds; v2 depth per the owner review of 2026-08-26 (every workflow must do real work; nurture never under 30 days; copy to the Gary V / Hormozi standard in references/copy-guide.md).
5
5
 
6
6
  ## When it is selected
@@ -28,6 +28,8 @@ The default reference preset, and the routing target for med-spa / aesthetics bu
28
28
  | Lead Nurture (30 days) | contact_tag (tag.nurture_start) | 28 | 11 | 31 days | yes |
29
29
  | Replied - Stop & Route | customer_reply | 9 | 0 | instant | no |
30
30
  | Win-back (30 days) | contact_tag (tag.winback_start) | 13 | 5 | 31 days | yes |
31
+ | Treatment plan Follow-up | contact_tag (tag.quote_sent) | 14 | 4 + a call task | 10 days | yes |
32
+ | Review Requests | contact_tag (tag.review_ask) | 8 | 3 | 7 days | yes |
31
33
  | Consult Booked - Confirm & Move | appointment (confirmed) | 8 | 2 | instant | no |
32
34
  | Consult Reminders | appointment (confirmed) | 5 | 3 | instant | no |
33
35
  | No-Show Rescue | appointment (noshow) | 14 | 4 | 7 days | yes |
@@ -39,6 +41,13 @@ The default reference preset, and the routing target for med-spa / aesthetics bu
39
41
  - **Missed Call Text-Back**: `call_status` trigger scoped to no-answer / busy / voicemail, inbound. Text within seconds, tag, alert, call-back task, one more text an hour later.
40
42
  - **Consultation → Confirm & Move, Reminders, No-Show Rescue**: booking stops every sequence and moves (or creates) the opportunity; reminders at 24 h and 2 h; a no-show gets a same-day call task, text and email, two more touches, then hands off to Win-back and moves the card back to Contacted.
41
43
 
44
+ ### Added in v2.1.0 (2026-09-06): the two features the Assessment prices
45
+
46
+ - **Treatment plan Follow-up** (feature `quote_followup`): starts from the `quote-sent` tag the operator applies when a treatment plan goes out (by hand, from a quoting tool, or from a stage). Text on day 1, email on day 3, text on day 6 plus a call task, last email on day 10; any reply stops it; ends by tagging `quote-no-reply`. Hand-off `handoff.quote_tag` explains the tag.
47
+ - **Review Requests** (feature `review_requests`): starts from the `review-ask` tag applied when the work is done. Text two hours later with the Google link from the `review_link` custom value, email on day 3, last text on day 7; any reply stops it (a complaint reaches a person, not a second nudge); ends by tagging `review-asked`. Hand-off `handoff.review_link` collects the link.
48
+
49
+ Every workflow now belongs to a feature (`src/intake-to-build/features.ts`) so a client's Blueprint builds what they bought; the reply router is the foundation every messaging feature stands on.
50
+
42
51
  ### Why sequences hand off with a tag, not add_to_workflow
43
52
  Every hand-off is `add_contact_tag` + a `contact_tag` trigger. Both halves are live-proven shapes captured from working GHL workflows (`templates/action-schemas.json`). `add_to_workflow` has no captured native shape there and has not been proven at runtime, so a preset may not use it. Each sequence removes its own trigger tag as its first action so a later hand-off can start it again.
44
53
 
@@ -56,3 +65,7 @@ Every hand-off is `add_contact_tag` + a `contact_tag` trigger. Both halves are l
56
65
 
57
66
  ## Copy notes
58
67
  Written for the fear that drives every med-spa lead: looking "done". The through-line is "refreshed, not overdone": treat less first, adjust at two weeks, written plan with prices before anything happens. Texts ask one-word questions (lines / volume / texture; mornings / afternoons; YES / later). Price is handled head-on on day 14, cost of waiting on day 20, and the decision sheet on day 27. No client results are claimed; the build stage adds real ones only if the brief supplies them.
68
+
69
+ ## v2.1.0 (2026-09-15)
70
+
71
+ Two workflows the Assessment prices and no preset built: **Quote Follow-up** (starts from the `quote-sent` tag; text next day, email day 3, last text day 5, then a call task for the owner and the `quote-stale` tag) and **Review Request** (starts from the `service-complete` tag, never from a consult or estimate; text at two hours with the `review_link` custom value, email three days later). Held DRAFT behind the A2P and phone-number handoffs like every other text-bearing workflow (Quote Follow-up also behind staff, for its call task); their emails wait on the sending-domain handoff.