Small Biz Workflows
FORM SBW-001 · REV B · 5-FILE KITLead Rescue

Missed-Call Text-Back

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.

TIME TO VALUE
1 afternoon (basic) · 1 weekend (with the LLM layer)
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
Download the kit (.zip)Free · Field-Tested

PROVENANCE: Production missed-call rescue stack run for phone-dependent service businesses in Alberta. Generalized for any small business — no vendor lock-in, no secrets, adapt freely. The kit's INSTALL.md is a one-shot Claude Code build: unzip, paste one prompt, answer ~5 questions.

FILE: WORKFLOW.mdDOWNLOAD THIS FILE

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.

Don't want to download? Read it right here

Every file in the kit, on the page, in full. Expand one, hit copy, paste it wherever you work. Same content as the zip — the download just saves you the copying.

WORKFLOW.md · 15k chars
## 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.
INSTALL.md · 3k chars
# INSTALL.md — One-Shot Build · Missed-Call Text-Back (SBW-001)

You don't need to know how to code. You need three things:

1. **Claude Code** installed (claude.com/claude-code — the free way in is `npm install -g @anthropic-ai/claude-code`)
2. A **Twilio account** (twilio.com — have your login ready, Claude will tell you exactly what to click)
3. This kit, unzipped into an empty folder

## The one-shot

Open a terminal in the kit folder, run `claude`, and paste this entire block:

```
Read every file in this folder: CLAUDE.md, WORKFLOW.md, SKILLS.md, WALKTHROUGH.md.
They are the complete spec for a missed-call text-back system. CLAUDE.md is your
standing brief; its hard rules and definition-of-done are binding.

Then build it for me, one-shot, with these ground rules:

1. Ask me ONLY these setup questions, all at once, then stop asking:
   - Business name, trade, and city (for message templates and the LLM prompt)
   - My real business phone number (forwarding target)
   - My cell number (for hot-lead alerts)
   - Where to deploy: Vercel (default) or Cloudflare
   - Database: Supabase (default) or Google Sheet to start

2. Scaffold the folder structure from CLAUDE.md exactly. Build Phase 1 (fast
   text-back) completely before touching Phase 2 (LLM brain), per WALKTHROUGH.md.

3. When you need anything from a dashboard (Twilio number purchase, webhook URL
   paste, API keys into .env), give me a numbered click-path and wait. Never ask
   me to write code or config myself.

4. All secrets go in .env only. Create .env.example, never commit .env.

5. Finish by walking me through the week-one checklist in WORKFLOW.md, one test
   at a time, and don't call it done until the definition-of-done in CLAUDE.md
   is fully checked.
```

That's it. Claude Code asks you five questions, then builds, deploys, and tests
the system with you.

## What you'll be doing while it builds

```
 YOU                                  CLAUDE CODE
 ───                                  ───────────
 answer 5 questions          ──▶      scaffolds + writes everything
 click what it tells you     ──▶      wires Twilio webhooks
 paste 2–3 keys into .env    ──▶      deploys to Vercel
 make a test call            ──▶      verifies the <30s text
 read the AI's test drafts   ──▶      tunes the prompt to your voice
```

Total hands-on time: about 30 minutes of yours, spread over an afternoon.

## If something goes sideways

Paste the error back into Claude Code and say "fix this, re-read CLAUDE.md
first." The kit files carry enough context that a fresh session can always
pick up where you left off.
CLAUDE.md · 4k chars
# CLAUDE.md — Missed-Call Text-Back (SBW-001)

Drop this file in the root of your build folder. It is the standing brief for any AI assistant
(Claude Code or similar) working on this project. Read WORKFLOW.md for the full spec.

## Mission

When a call to {{business_name}} goes unanswered, text the caller within 30 seconds. If they
left a voicemail, transcribe it, classify it with an LLM, and draft a reply that names their
actual problem. Log everything. Never spam anyone.

## Architecture guts

```
 Twilio voice webhook ──▶ api/call-status ──▶ fast SMS (<30s) ──▶ call_events row
 Twilio recording cb  ──▶ api/voicemail   ──▶ transcribe ──▶ LLM classify ──▶ route by urgency
 Twilio inbound SMS   ──▶ api/inbound-sms ──▶ human forward OR LLM draft (after hours)
```

- Phase 1 (fast text) and Phase 2 (LLM) are **separate paths**. Phase 1 must never await
  transcription, classification, or anything slower than one DB check.
- The LLM returns strict JSON (schema in WORKFLOW.md). Validate it. One retry, then fall back
  to the generic message and log the failure.
- Suppression checks (24h dedupe, landline, STOP list) run **before** every send, in one place
  (`lib/sms.ts`), never inline in handlers.

## The three questions

Answer these for every component you add. If you can't, the design is wrong.

| Component | 1. Where does state live? | 2. Where does feedback live? | 3. What breaks if I delete it? |
|---|---|---|---|
| `call_events` table | The DB — single source of truth | `outcome` column = ROI report | Everything: dedupe, ROI, audit |
| `api/call-status` | stateless; writes to call_events | response codes in Twilio debugger | no fast text — the product dies |
| `api/voicemail` + LLM | stateless; writes transcript/intent | weekly transcript-vs-intent spot check | texts go generic; leads still saved |
| `prompts/classifier.md` | git — prompt is versioned code | draft-quality tally (week-one checklist) | classifier drifts, nobody knows why |
| suppression logic | STOP list + 24h window in DB | complaint rate, CASL exposure | double-texts, angry customers, fines |

## Hard rules

- The first SMS goes out in under 30 seconds or this project has failed its only job.
- Never send SMS to `line_type = landline`. Check before send, every send.
- Urgency 4–5: AI drafts, human sends. No exceptions until the owner explicitly flips it.
- The LLM never quotes prices, never promises arrival times, never claims to be human.
- STOP/unsubscribe is honoured immediately and permanently.
- Secrets (Twilio keys, LLM keys) live in env vars only. Never in code, logs, or this file.
- Transcripts are personal information: keep them in our DB, delete after 90 days.

## Folder structure

```
 missed-call-textback/
 ├── CLAUDE.md            ← you are here
 ├── api/                 ← one file per webhook, thin handlers only
 ├── lib/                 ← classify.ts, sms.ts, log.ts — all logic lives here
 └── prompts/             ← classifier.md, versioned like code
```

## Definition of done

- [ ] Test call → SMS in < 30s (measured, not vibes)
- [ ] Voicemail → correct intent JSON on 9 of 10 real-style test messages
- [ ] Garbled/short voicemail → generic fallback, no crash
- [ ] Duplicate call same day → exactly one text
- [ ] Landline call → zero SMS attempts
- [ ] STOP → suppressed forever, logged
- [ ] Owner can read the week's call_events and point to recovered jobs
SKILLS.md · 4k chars
# SKILLS.md — Missed-Call Text-Back (SBW-001)

What you (or whoever builds this) need to know, and which skills to load into your AI
assistant before starting. "Skill" here means a reusable how-to pattern — for Claude Code
users, an installable skill file; for everyone else, a topic to get comfortable with first.

## Skill map

```
                       ┌──────────────────────┐
                       │   SBW-001 TEXT-BACK  │
                       └──────────┬───────────┘
          ┌──────────────┬───────┴───────┬────────────────┐
          ▼              ▼               ▼                ▼
   ┌─────────────┐ ┌────────────┐ ┌─────────────┐ ┌──────────────┐
   │ twilio +    │ │ LLM intake │ │ small DB /  │ │ compliance   │
   │ webhooks    │ │ (JSON out) │ │ logging     │ │ (CASL/PIPEDA)│
   │  REQUIRED   │ │  REQUIRED  │ │  REQUIRED   │ │   REQUIRED   │
   └─────────────┘ └────────────┘ └─────────────┘ └──────────────┘
```

## Required skills

| Skill | Why this build needs it | If you're using Claude Code |
|---|---|---|
| **Twilio webhooks** | Detecting the missed call and receiving voicemail callbacks is all webhook plumbing. | Pair with a `twilio-llm-intake` style skill — webhook setup + LLM prompt pattern for reliable JSON output. |
| **LLM structured output** | The classifier must return strict JSON every time, with validation and a fallback path. | Same skill covers the validate-retry-fallback discipline. |
| **Basic data modelling** | One `call_events` table, but the dedupe and ROI reporting depend on getting its columns right. | A `supabase-project-setup` style skill if you use Supabase. |
| **TCPA / CASL / PIPEDA basics** | You're texting US and Canadian customers and storing their voicemail transcripts — A2P 10DLC registration, STOP/HELP, and privacy rules apply. | No skill replaces reading the rules in WORKFLOW.md. |

## Suggested (not required)

| Skill | What it adds |
|---|---|
| **Prompt iteration** | Feeding the classifier 3 real examples of your texting voice makes drafts sound like you. |
| **Speech-to-text options** | Swapping Twilio transcription for Deepgram/Whisper when trade vocabulary trips it up. |

## Workflows that pair with this one

```
  SBW-001 TEXT-BACK ──── shares the Twilio number ───▶ SBW-003 AI VOICE RECEPTIONIST
        │                (text-back is the fallback     (answers instead of voicemail)
        │                 when the agent is off)
        │
        └────── shares the customer log ─────────────▶ SBW-002 REVIEW REQUESTS
                (a recovered lead that becomes a        (closes the loop after the job)
                 booked job feeds the review ask)
```

- **SBW-003 After-Hours AI Voice Receptionist** — upgrade path: instead of voicemail + text,
  an agent answers. Keep SBW-001 running as the safety net.
- **SBW-002 Automated Review Requests** — the lead you rescue today is the review you request
  next week. Same Twilio account, same log table pattern.

## Build order recommendation

1. Phase 1 alone (fast text) — ship it the same afternoon.
2. Live with it for a week; read the call log.
3. Add Phase 2 (LLM brain) once you know your real call mix.
4. Consider SBW-003 when after-hours volume justifies a voice.
WALKTHROUGH.md · 6k chars
# WALKTHROUGH.md — Missed-Call Text-Back (SBW-001)

The build, step by step, in plain words. Each step tells you three things:
**what to do**, **how long it takes**, and **how you know it worked**.

## Your board

Move each card to the right as you go. You're done when everything is in SHIPPED.

```
 ┌─ TO DO ─────────┐  ┌─ BUILDING ──────┐  ┌─ TESTING ───────┐  ┌─ SHIPPED ───────┐
 │ 1 get number    │  │                 │  │                 │  │                 │
 │ 2 forward calls │  │  cards move     │  │  cards move     │  │  cards move     │
 │ 3 fast text     │  │  this way ──▶   │  │  this way ──▶   │  │  this way ──▶   │
 │ 4 call log      │  │                 │  │                 │  │                 │
 │ 5 voicemail+AI  │  │                 │  │                 │  │                 │
 │ 6 hot alerts    │  │                 │  │                 │  │                 │
 └─────────────────┘  └─────────────────┘  └─────────────────┘  └─────────────────┘
```

## How the pieces hold hands

```
                 ┌───────────┐
                 │ customer  │
                 └─────┬─────┘
                calls  │
                       ▼
   ┌───────────────────────────────────┐
   │        twilio number              │
   └──────┬──────────────────┬─────────┘
   missed │                  │ voicemail
          ▼                  ▼
   ┌────────────┐     ┌────────────┐
   │ fast text  │     │ transcript │
   └─────┬──────┘     └─────┬──────┘
         │                  ▼
         │           ┌────────────┐
         │           │  AI brain  │
         │           └─────┬──────┘
         │            ┌────┴────┐
         ▼            ▼         ▼
   ┌──────────────────────┐ ┌──────────┐
   │      call log        │ │ your     │
   │  (remembers it all)  │ │ phone    │
   └──────────────────────┘ └──────────┘
```

Every arrow is one connection you will build. There are only six.

---

## Step 1 — Get your Twilio number (20 minutes)

**Do this:** Make a Twilio account. Buy one local number. Cost: about $1.50/month.

**You know it worked when:** you can see your new number in the Twilio console.

## Step 2 — Forward calls to your real phone (15 minutes)

**Do this:** In the number's voice settings, forward incoming calls to your real
business line. Set it to give up after about 20 seconds of ringing.

**You know it worked when:** you call the Twilio number from a friend's phone and
your real phone rings.

## Step 3 — Send the fast text (2–3 hours)

This is the heart. When the forwarded call isn't answered, Twilio tells your
webhook, and your webhook sends one text.

**Do this:**
1. Make one small function (Vercel/Cloudflare) or one Make/n8n scenario.
2. Point Twilio's call-status webhook at it.
3. When status is `no-answer` or `busy`, send the SMS template from WORKFLOW.md.
4. Skip if: same caller already texted today, or the number is a landline.

```
   call missed ──▶ your function ──▶ checks ──▶ text goes out
                                      │
                              skip if │ texted today?
                                      │ landline?
```

**You know it worked when:** you call your number, don't pick up, and the text
lands on your test phone in under 30 seconds. Time it for real.

## Step 4 — Start the call log (1 hour)

**Do this:** Make one table (or one Google Sheet) named `call_events`. Every missed
call adds a row: who called, when, did we text, did they reply. The full column
list is in WORKFLOW.md.

**You know it worked when:** three test calls = three rows, no duplicates.

## Step 5 — Add the AI brain (half a day)

Now the voicemail becomes useful.

**Do this:**
1. Turn on voicemail recording + transcription for unanswered forwards.
2. When the transcript arrives, send it to the LLM with the classifier prompt
   from WORKFLOW.md.
3. Save what comes back (intent, urgency, draft reply) onto the same call row.

```
   voicemail ──▶ transcript ──▶ AI reads it ──▶ saves:
                                                 what they want
                                                 how urgent (1–5)
                                                 a ready-to-send reply
```

**You know it worked when:** you leave yourself a fake voicemail ("water leaking
through my ceiling, call me back, it's Dana") and the row shows
`intent: emergency, urgency: 5` with a draft that mentions the ceiling.

## Step 6 — Hot-lead alerts to your pocket (1–2 hours)

**Do this:** When urgency is 4 or 5, send *yourself* a text: who called, what
they need, and the AI's suggested reply. You read it, fix a word if needed, send it.

**You know it worked when:** the fake emergency voicemail makes your own phone
buzz within a minute, with a reply you'd actually send.

---

## After it ships — the weekly 10 minutes

```
   every Friday:
   ┌──────────────────────────────────────────────┐
   │ 1. open call_events                          │
   │ 2. mark outcomes: booked / lost / spam       │
   │ 3. count booked × your average job value     │
   │ 4. spot-check 3 transcripts vs the AI's call │
   └──────────────────────────────────────────────┘
```

That number in line 3 is what this build earns. Most owners stop doubting the
system the first time it catches one after-hours emergency.