TL;DR
Every finished job triggers one short, personal text with your Google review link, sent when satisfaction peaks. An LLM reads whatever the customer texts back: happy replies get a thank-you, complaints get caught privately and escalated to you before they become 1-star reviews. The asking runs itself; you keep the judgment.
The problem this solves
Reviews are the local-search ranking factor you control most directly, and the trust signal customers check first. But asking is awkward, easy to forget, and always loses to the next job. So the businesses with the best work often have the thinnest review profiles — and lose map-pack position to mediocre competitors who simply ask.
The fix: take asking off your plate entirely, and put an AI safety net under the replies.
The full picture
THE END-TO-END FLOW
═══════════════════════════════════════════════════════════════════════
┌──────────────┐ marked ┌──────────────────┐ wait ┌─────────────────┐
│ JOB DONE │ ────────▶ │ TRIGGER FIRES │ ────────▶ │ REVIEW ASK SMS │
│ (invoice paid│ │ (webhook from │ 1–3 hrs │ named, personal,│
│ or calendar │ │ invoice/CRM/cal)│ │ one link │
│ event ends) │ └──────────────────┘ └────────┬────────┘
└──────────────┘ │ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ SENT-LIST CHECK │ │ CUSTOMER │
│ one ask per │ │ taps link → ⭐⭐⭐⭐⭐ │
│ customer, EVER │ │ or texts back │
└──────────────────┘ └────────┬─────────┘
│ reply
▼
┌──────────────────────┐
│ LLM TRIAGE │
│ reads the reply: │
│ happy? → thank them │
│ problem? → ALERT │
│ question? → forward │
└──────────┬───────────┘
│
┌───────────────────┴──────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ YOUR PHONE │ │ REVIEW LOG │
│ "Dana has a leak │ │ asks, replies, │
│ complaint — │ │ reviews, saves │
│ call her first" │ │ (the ROI table) │
└──────────────────┘ └──────────────────┘
═══════════════════════════════════════════════════════════════════════
The quiet genius: the reply-to-fix line in your ask message is a satisfaction check and a review ask in one — and the LLM is the net that catches problems privately, before they go public.
What you need
| Piece | What it does | Pick |
|---|---|---|
| Direct review link | Opens the Google review box in one tap | Google Business Profile → "Ask for reviews" → copy the g.page short link |
| Completion trigger | Tells the system a job finished | Invoice marked paid, calendar event ended, or CRM status change — whatever already exists |
| SMS sender | Delivers the ask | Twilio, or the texting in tools you already pay for |
| LLM | Triages replies, personalizes asks | Claude (Haiku tier — pennies per reply) |
| Review log | Tracks asks → reviews → saves | One table or sheet |
Phase 1 — The automated ask
JOB CLOSES ──▶ wait 1–3 hrs ──▶ checks ──▶ personalized SMS ──▶ log row
│
├─ already asked this customer, ever? → skip
├─ job flagged "do not ask"? → skip
└─ customer opted out? → skip
- Pick the trigger that already exists. Don't add a crew step. Invoice-paid is best — nobody forgets to invoice.
- Wait 1–3 hours after completion. Fresh enough to remember, settled enough to not feel robotic.
- Send one short, named, personal text (template below).
- Suppress repeats forever. One ask per customer, ever. The sent-list check runs before every send.
- Route replies into the LLM triage (Phase 2).
The ask (template):
Hi {{first_name}}, thanks for having {{business_name}} out for the {{job_type}} today. If you were happy with the work, a quick Google review helps us a ton: {{review_link}} — and if anything's not right, just reply here and we'll fix it.
LLM-personalized variant: feed the job notes to the LLM and let it name something real:
You write SMS review requests for {{business_name}}. Using the job notes below, write ONE text under 300 characters that: greets {{first_name}} by name, references one specific detail of the work (from the notes), includes {{review_link}}, and ends with "if anything's not right, just reply here and we'll fix it." Warm, plain, no exclamation overload, no emojis.
JOB NOTES: {{job_notes}}
"Thanks for letting us replace the hot-water tank in the Larch St basement" out-converts "thanks for your business" every single week.
Phase 2 — The LLM reply net
Some customers reply to the text instead of (or before) leaving a review. Every one of those replies is gold — route them well.
CUSTOMER REPLY arrives
│
▼
┌──────────────────────────────────────────┐
│ LLM TRIAGE — returns strict JSON: │
│ { │
│ "sentiment": "happy | unhappy | │
│ question | unsubscribe", │
│ "issue_summary": "tap still drips", │
│ "needs_human": true/false, │
│ "draft_reply": "…", │
│ "confidence": 0.94 │
│ } │
└────────────────────┬─────────────────────┘
│
┌─────────────────┼──────────────────┬──────────────────┐
▼ ▼ ▼ ▼
happy unhappy question unsubscribe
┌──────────┐ ┌───────────────┐ ┌─────────────┐ ┌─────────────┐
│ auto │ │ ALERT OWNER │ │ forward to │ │ add to │
│ thank-you│ │ NOW + draft │ │ your inbox │ │ suppression │
│ reply │ │ apology, hold │ │ with draft │ │ list, reply │
└──────────┘ │ all further │ │ answer │ │ confirm │
│ asks │ └─────────────┘ └─────────────┘
└───────────────┘
The triage prompt:
You read SMS replies to a review request from {{business_name}}. Classify the reply below. Return ONLY JSON: sentiment ("happy", "unhappy", "question", "unsubscribe"), issue_summary (under 10 words, or null), needs_human (true if unhappy or any repair issue is mentioned), draft_reply (under 300 chars, warm, never defensive, never offers discounts, never mentions reviews if sentiment is unhappy), confidence (0–1). If the reply mentions ANY problem with the work, sentiment is "unhappy" even if polite.
REPLY: {{reply_text}}
The rule that makes this safe: an "unhappy" classification stops everything. No thank-you, no further asks, owner alerted with the issue summary. A complaint you catch in a private text thread is a 1-star review that never happened.
Data model
review_asks
┌────────────────┬──────────────────────────────────────────┐
│ id │ uuid │
│ customer_phone │ +1403… (unique — enforces one-ask-ever) │
│ customer_name │ Dana │
│ job_type │ hot water tank replacement │
│ job_closed_at │ timestamp │
│ asked_at │ timestamp │
│ reply_text │ text or null │
│ sentiment │ happy | unhappy | question | unsub | null│
│ issue_summary │ "tap still drips" or null │
│ review_left │ boolean (manual weekly check) │
│ saved_job │ boolean (complaint caught + fixed) │
└────────────────┴──────────────────────────────────────────┘
review_left and saved_job are your two ROI columns: reviews gained, disasters dodged.
Folder structure
review-engine/
├── CLAUDE.md ← the AI-builder brief (ships in this kit)
├── api/
│ ├── job-closed.ts ← trigger webhook → schedule the ask
│ └── inbound-sms.ts ← replies → LLM triage → route
├── lib/
│ ├── ask.ts ← template/LLM personalization + suppression
│ ├── triage.ts ← the triage prompt + JSON validation
│ └── log.ts ← review_asks read/write
└── prompts/
├── personalize.md
└── triage.md
Compliance notes (US + Canada + Google)
- US — TCPA + 10DLC: texting an existing customer about the job they just hired you for is the safe, expected case, but register A2P 10DLC with your SMS provider before volume and honour STOP/HELP instantly. Don't repurpose the review thread into marketing blasts.
- Canada — CASL: a service message to an existing customer about their own job is implied consent — identify yourself, honour opt-outs instantly.
- Never gate (Google policy): asking only happy customers ("review gating") violates Google's terms. Ask everyone; the LLM net is your protection, not selective asking.
- Never incentivize: no discounts for reviews — Google can wipe your whole profile.
- The LLM drafts must never offer compensation in an apology — that's an owner decision, made by the owner.
Numbers to watch
| Metric | Healthy | Where it comes from |
|---|---|---|
| Ask → review conversion | 15–30% | review_left / rows |
| Complaints caught privately | every one is a win | sentiment = unhappy |
| Time to owner callback on complaints | < 2 hours | alert timestamp → your call |
| Review count trajectory | +5–15/month for a 10-job/week business | Google Business Profile |
A business doing 10 jobs a week goes from 12 reviews to 100+ in a year — usually enough to change map-pack position in a small market.
Week-one checklist
- Close a test job — ask arrives 1–3 hours later, named, with the right job type
- The link opens the Google review box directly (not a search page)
- Reply "thanks, you guys were great" — auto thank-you, sentiment logged happy
- Reply "actually the tap still drips" — your phone gets the alert, no thank-you sent
- Reply STOP — suppressed, confirmed
- Close a second job for the same customer — no second ask
- Check Google for the first real review and mark
review_left
Troubleshooting
- Low conversion: your link probably opens a search page instead of the review modal. Use the official short link from Google Business Profile.
- Crew forgets to close jobs: tie the trigger to money (invoice paid). Nobody forgets to invoice.
- LLM marks polite complaints as happy: the prompt rule "ANY problem mentioned = unhappy" must be present. Test with "great service but the tap still drips."
- Asks feel robotic: switch from the static template to the LLM-personalized variant — job notes in, one specific detail out.
- Customer replies weeks later: keep the thread alive; triage runs on every inbound message, however late.