# Missed-Call Text-Back — SBW-001

> Send an automatic text within 30 seconds of every missed call — then let an LLM read the voicemail transcript, classify the lead, and draft the perfect reply for you.

- **Category:** Lead Rescue
- **Time to value:** 1 afternoon (basic) · 1 weekend (with the LLM layer)
- **Typical running cost:** $5–20 SMS + $3–10 LLM usage
- **Stack:** Twilio (number, voice webhooks, SMS), A webhook runner (Vercel/Cloudflare function, Make, or n8n), An LLM (Claude) for transcript reading and reply drafting, A database or Google Sheet for the call log
- **Provenance:** Production missed-call rescue stack run for phone-dependent service businesses in Alberta
- **Source:** https://smallbizworkflows.com/workflows/missed-call-text-back
- **Kit:** ships with INSTALL.md (one-shot Claude Code build), CLAUDE.md, SKILLS.md, WALKTHROUGH.md

---
## TL;DR

> A customer calls. You can't answer. Within 30 seconds they get a text from you.
> If they left a voicemail, an AI reads the transcript, figures out what they want,
> drafts a reply that mentions their actual problem, and flags the hot ones to your phone.
> You close the loop with one thumb. Nothing leaks.

## The problem this solves

You're under a sink, on a ladder, or with another customer. The phone rings, goes to voicemail, and the caller does what almost every caller does: hangs up and dials the next business on the list. That call was worth hundreds or thousands of dollars, and it disappeared silently.

A missed-call text-back turns that dead end into a conversation. The basic version sends one fast text. The full version — the one this file builds — adds an LLM brain that reads the voicemail, understands the job, and answers like a sharp office manager.

## The full picture

Every box below is one small piece. Build them left to right.

```
                          THE END-TO-END FLOW
═══════════════════════════════════════════════════════════════════════

 ┌──────────────┐  rings   ┌──────────────────┐  no answer  ┌─────────────────┐
 │   CUSTOMER   │ ───────▶ │   TWILIO NUMBER  │ ──────────▶ │ STATUS WEBHOOK  │
 │  calls you   │          │ (forwards to     │  or busy    │ fires instantly │
 └──────┬───────┘          │  your real line) │             └────────┬────────┘
        │                  └──────────────────┘                      │
        │ maybe leaves                                               ▼
        ▼                                                  ┌─────────────────┐
 ┌──────────────┐  audio   ┌──────────────────┐            │  FAST TEXT-BACK │
 │  VOICEMAIL   │ ───────▶ │  TRANSCRIPTION   │            │  sent in <30s   │
 │  (optional)  │          │ (Twilio/Deepgram)│            │  "Sorry we      │
 └──────────────┘          └────────┬─────────┘            │   missed you…"  │
                                    │ text                 └────────┬────────┘
                                    ▼                               │
                         ┌──────────────────┐                       │
                         │    LLM BRAIN     │                       │
                         │ reads transcript │                       │
                         │ → classifies it  │                       │
                         │ → drafts reply   │                       │
                         └────────┬─────────┘                       │
                                  │                                 │
                  ┌───────────────┴───────────────┐                 │
                  ▼                               ▼                 ▼
        ┌──────────────────┐            ┌──────────────────────────────┐
        │  YOUR PHONE      │            │        CALL LOG (DB)         │
        │ hot-lead alert + │            │ every call, every transcript,│
        │ AI-drafted reply │            │ every text, every outcome    │
        │ ready to send    │            └──────────────────────────────┘
        └──────────────────┘
═══════════════════════════════════════════════════════════════════════
```

The fast text-back never waits for the AI. Speed wins the lead; the LLM makes the second message smart.

## What you need

| Piece | What it does | Pick |
|---|---|---|
| Phone number | Detects the missed call | Twilio number forwarding to your real line |
| Webhook runner | Runs your logic when Twilio calls it | One serverless function (Vercel/Cloudflare) or Make/n8n |
| Transcription | Turns voicemail audio into text | Twilio's built-in, or Deepgram for better accuracy |
| LLM | Reads transcript, classifies, drafts replies | Claude (Haiku tier is plenty and cheap) |
| Call log | Remembers everything | Supabase/Postgres, or a Google Sheet to start |

## Phase 1 — The fast text-back (build this first)

```
  CALL ENDS UNANSWERED ──▶ webhook ──▶ checks ──▶ sends SMS ──▶ logs it
                                        │
                                        ├─ already texted today?  → skip
                                        ├─ landline?              → skip
                                        └─ existing open thread?  → skip
```

1. **Get a Twilio number** and set it to forward to your existing line. Port nothing — your real number stays untouched.
2. **Set the call status webhook** to your function. A call is "missed" when dial status is `no-answer` or `busy`.
3. **Send the text within 30 seconds.** Speed is the whole product.
4. **Apply the three skip-checks** above so nobody gets double-texted and no landline gets an SMS (use Twilio Lookup for line type).
5. **Log one row per event**: time, caller, line type, text sent yes/no.

**The first message (template):**

> Hey, it's {{business_name}} — sorry we missed your call. What do you need help with? Reply here and we'll get back to you shortly. (Reply STOP to opt out.)

Keep it under 160 characters. No links — links from unknown numbers read as spam.

## Phase 2 — The LLM brain (transcripts in, smart replies out)

This is where the workflow goes from polite to profitable. If the caller left a voicemail, you have their words. Use them.

```
  VOICEMAIL AUDIO
        │
        ▼
  ┌─────────────┐     ┌──────────────────────────────────────────┐
  │ TRANSCRIBE  │ ──▶ │ LLM CLASSIFIER                           │
  └─────────────┘     │ returns strict JSON:                     │
                      │ {                                        │
                      │   "intent": "emergency | quote |         │
                      │              booking | vendor | spam",   │
                      │   "job_type": "burst pipe",              │
                      │   "urgency": 1-5,                        │
                      │   "callback_window": "tonight",          │
                      │   "draft_reply": "…",                    │
                      │   "confidence": 0.92                     │
                      │ }                                        │
                      └───────────────┬──────────────────────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              ▼                       ▼                       ▼
        urgency 4–5             urgency 2–3              spam / vendor
     ┌────────────────┐     ┌────────────────┐      ┌────────────────┐
     │ ALERT YOU NOW  │     │ SEND AI DRAFT  │      │ LOG AND IGNORE │
     │ SMS to your    │     │ as 2nd message │      │ no text sent   │
     │ cell + draft   │     │ automatically  │      │                │
     └────────────────┘     └────────────────┘      └────────────────┘
```

**The classifier prompt (copy-paste, fill the blanks):**

> You are the office manager for {{business_name}}, a {{trade}} business in {{city}}.
> Below is a voicemail transcript from a missed call.
>
> Return ONLY valid JSON with these fields:
> - intent: one of "emergency", "quote_request", "booking", "existing_customer", "vendor", "spam"
> - job_type: 2–5 words describing the job, or null
> - urgency: 1 (whenever) to 5 (right now — water, safety, no heat)
> - callback_window: when they asked to be reached, or null
> - draft_reply: an SMS reply under 300 characters, warm and direct, that names their
>   specific problem, asks ONE clarifying question, and never quotes a price or promises
>   an arrival time. Sign as {{business_name}}.
> - confidence: 0 to 1
>
> Rules: if the transcript is empty, garbled, or under 5 words, set intent to "spam" and
> confidence below 0.3. Never invent details that are not in the transcript.
>
> TRANSCRIPT:
> {{transcript}}

**Worked example — what the customer experiences:**

```
 7:42 pm  CUSTOMER calls, no answer, leaves voicemail:
          "Hi uh, this is Dana on 84th Ave, we've got water coming
           through the kitchen ceiling, please call me back."

 7:42 pm  AUTO TEXT (Phase 1, instant):
          "Hey, it's Mike's Plumbing — sorry we missed your call.
           What do you need help with? Reply here…"

 7:43 pm  LLM reads transcript → {"intent":"emergency","urgency":5,…}

 7:43 pm  YOUR PHONE buzzes:
          "URGENT missed call — Dana, 84th Ave, water through
           kitchen ceiling. Suggested reply attached. Tap to send."

 7:44 pm  YOU tap send:
          "Dana, got your voicemail about water coming through the
           kitchen ceiling — that can't wait. Can someone meet you
           tonight if we head out within the hour? — Mike's Plumbing"
```

Two minutes, three messages, and Dana never opened Google to find your competitor.

**Guardrails for the LLM layer (non-negotiable):**

- **Urgency 4–5 drafts are human-approved** — the AI proposes, you tap send. Emergencies deserve a human eye.
- **Urgency 1–3 drafts may auto-send**, but only after you've reviewed a week of drafts and trust them.
- **Low confidence (< 0.6) falls back** to the generic message. A wrong guess is worse than a plain one.
- The LLM **never quotes prices, never promises arrival times, never claims to be human** if asked.

## Phase 3 — The conversation continues

Replies come back. Route them like this:

```
  CUSTOMER REPLIES
        │
        ▼
  ┌─────────────────────────────────────────────────┐
  │          is a human available right now?        │
  ├────────────────────────┬────────────────────────┤
  │ YES                    │ NO (after hours)       │
  ▼                        ▼                        │
  forward to your cell     LLM drafts the next      │
  or shared inbox —        reply using the full     │
  human takes the thread   thread + transcript as   │
                           context, queued for      │
                           morning approval         │
  └────────────────────────┴────────────────────────┘
          every message logs to the same call_events row
```

## Data model — where everything lives

One table. Don't overthink it.

```
 call_events
 ┌────────────────┬──────────────────────────────────────────┐
 │ id             │ uuid                                     │
 │ caller_number  │ +1403…                                   │
 │ called_at      │ timestamp                                │
 │ line_type      │ mobile | landline | voip                 │
 │ voicemail_url  │ audio link (Twilio)                      │
 │ transcript     │ text                                     │
 │ intent         │ emergency | quote_request | …            │
 │ urgency        │ 1–5                                      │
 │ ai_draft       │ text                                     │
 │ first_sms_at   │ timestamp (must be < 30s after called_at)│
 │ replied        │ boolean                                  │
 │ outcome        │ booked | lost | spam | open              │
 └────────────────┴──────────────────────────────────────────┘
```

The `outcome` column is your ROI report. Two weeks of rows tells you exactly how much money this workflow recovered.

## Folder structure (the serverless version)

```
 missed-call-textback/
 ├── CLAUDE.md            ← the AI-builder brief (ships in this kit)
 ├── api/
 │   ├── call-status.ts   ← Twilio voice webhook → fast text + log
 │   ├── voicemail.ts     ← transcription callback → LLM classify
 │   └── inbound-sms.ts   ← replies → route to human / LLM draft
 ├── lib/
 │   ├── classify.ts      ← the LLM prompt + JSON validation
 │   ├── sms.ts           ← send helpers + suppression checks
 │   └── log.ts           ← call_events read/write
 └── prompts/
     └── classifier.md    ← the prompt above, version-controlled
```

## Compliance notes (US + Canada)

**United States**
- **TCPA + carrier 10DLC:** before any volume, register your brand and campaign for A2P 10DLC with your SMS provider — unregistered traffic gets blocked and TCPA penalties are steep. A text *replying* to a call the customer just placed to you is the safe case; never auto-text marketing without prior express written consent.
- **STOP / HELP are mandatory** and must be honoured instantly (Twilio Advanced Opt-Out handles both).
- **Call recording is state-law dependent:** ~11 "all-party consent" states (CA, FL, PA, WA, etc.) require disclosing the recording. Disclose by default.

**Canada**
- **CASL:** texting back someone who just called you sits in implied-consent territory — but identify your business in every thread and honour STOP instantly.
- **PIPEDA:** voicemail transcripts are personal information. Store them in your own database, set a retention period (90 days is sensible), and don't paste them into tools you don't control.

**Both:** don't add missed callers to a marketing list without separate, explicit consent. This is practical guidance, not legal advice — confirm the rules for the states/provinces you serve.

## Numbers to watch

| Metric | Healthy | Where it comes from |
|---|---|---|
| Text-back speed | < 30 seconds | `first_sms_at − called_at` |
| Reply rate | 25–40% of missed callers | `replied` column |
| Classifier accuracy | > 90% after week 2 | spot-check transcripts vs intent |
| Recovered jobs | your real ROI | `outcome = booked` |

If you miss 10 calls a week and your average job is $300, plugging a quarter of the leak pays for everything in week one.

## Week-one checklist

- [ ] Call your own number from another phone, don't answer — text arrives in under 30s
- [ ] Leave a rambling voicemail — transcript lands, intent is right, draft sounds like you
- [ ] Leave a 3-word voicemail — falls back to generic (low confidence works)
- [ ] Call twice in one day — second call sends no duplicate text
- [ ] Call from a landline — no SMS attempted
- [ ] Reply STOP — suppression works, and the log shows it
- [ ] Read every AI draft this week and tally how many you'd send unedited

## Troubleshooting

- **Texts arrive slowly:** the webhook must fire on call *status*, not on transcription completion. Phase 1 never waits for Phase 2.
- **Transcripts are garbage:** Twilio's built-in transcription is okay; Deepgram or Whisper on the recording URL is noticeably better with trade vocabulary.
- **LLM returns invalid JSON:** validate, retry once with "Return only JSON," and on second failure fall back to the generic flow and log it.
- **Drafts sound corporate:** feed the prompt 3 examples of texts you actually sent. Tone is teachable.
- **Double-texting repeat callers:** the 24-hour suppression check must run *before* send, not after.
