T
Telephony SOPKnowledge Base
Search
← All topics

Missed Call Workflows — Text-Back Automation and Recovery

runnable

When a caller hangs up before reaching a human, automatically text them within 60 seconds with a contextual message that recovers the conversation. Reduces lost leads 40-70% in service industries. Implemented via call event webhooks (no-answer, busy, voicemail) + SMS API, with GHL or CRM integration for thread continuity.

missed-calltext-backsms-followupconversionautomation
Agent trigger phrases: missed call text back · MCTB · missed call followup · recover missed call · no answer SMS · call abandoned recovery

Missed Call Workflows

When a caller hangs up before reaching a human — no answer, busy, voicemail, abandoned in IVR — automatically respond with an SMS. This pattern is the single highest-ROI play in service-industry telephony. Industries that adopt it consistently recover 40-70% of otherwise-lost leads.

What "missed" means in practice

A "missed" call is any inbound call where the desired outcome (reach a live human, complete a transaction) did not happen. Six common terminal states:

| State | SignalWire/LaML call status | Common cause | |---|---|---| | No answer | no-answer | All ring targets timed out | | Busy | busy | Destination was on another call | | Failed | failed | Carrier-level failure (e.g., bad number) | | Voicemail left | completed (call was answered by VM) | Caller hit voicemail | | Voicemail no-message | completed (short duration, no recording) | Caller hung up at VM beep | | IVR abandon | completed (short duration, no transfer) | Caller hung up in menu |

Each state may warrant a different text-back message.

The basic flow

Inbound call → SWML or LaML script → call ends
                                       ↓
                    Webhook fires on call.completed event
                                       ↓
                    Check: did caller reach a human? did they leave a message?
                                       ↓
                    If "missed" by any definition: trigger SMS within 60s
                                       ↓
                    SMS sent from same DID the caller dialed
                                       ↓
                    Reply tracked back to CRM thread (GHL conversation, HubSpot ticket, etc.)

SignalWire implementation — status callback

Configure the phone number's voice URL to point at your SWML, and the status callback URL to fire on call termination:

@app.route("/call-status", methods=["POST"])
def call_status():
    call_status = request.form["CallStatus"]
    call_sid = request.form["CallSid"]
    caller = request.form["From"]
    called = request.form["To"]
    duration = int(request.form.get("CallDuration", 0))
    answered_by = request.form.get("AnsweredBy")  # only set if AMD ran
    
    if is_missed_call(call_status, duration, answered_by):
        message = compose_text_back(call_status, caller)
        send_sms(from_=called, to=caller, body=message)
        log_to_crm(caller, call_status, "Text-back sent")
    
    return "OK"

def is_missed_call(status, duration, answered_by):
    if status in ("no-answer", "busy", "failed"):
        return True
    if status == "completed" and duration < 15:
        return True  # IVR abandon or quick hangup
    if answered_by in ("machine_start", "machine_end_beep", "machine_end_silence"):
        return True  # VM picked up
    return False

Text-back message templates

Personalize by call state. Generic "we missed you" copy converts worse than state-specific copy.

No answer

"Hi! This is Sarah at Acme Plumbing — we just missed your call. Reply with your address and the issue, and we'll get a tech out today. Or call us back at +1 555-1234."

Busy

"Hey, sorry — we were on another call when you reached out. Reply here and we'll get back to you in 5 minutes. — Acme Plumbing"

Voicemail no message

"Got your call but didn't catch a message. Text me here with what you need and I'll handle it right away. — Sarah at Acme"

IVR abandon

"Hey — we noticed you called and hung up. Anything we can help with? Text us back and we'll skip the menu. — Acme"

After hours

"We're closed until 8 AM, but we got your call! Text us with the issue and we'll respond first thing. Emergency? Call +1 555-9999. — Acme"

Keep messages under 160 characters when possible — single SMS segment, fastest delivery, lowest cost.

Speed matters — under 60 seconds

Conversion drops sharply with response delay:

| Response time | Recovery rate | |---|---| | < 60 seconds | 40-70% | | 1-5 minutes | 20-40% | | 5-30 minutes | 10-20% | | 30+ minutes | < 5% |

The window collapses fast because callers move on to the next vendor. Build the webhook handler to fire SMS synchronously inside the status callback request — don't queue for later.

CRM thread continuity

The SMS reply must land in the same CRM thread that tracked the call. Three common patterns:

GoHighLevel (GHL)

def send_via_ghl(from_, to, body, location_id):
    # GHL conversations API auto-threads SMS + calls by phone number
    requests.post(
        f"https://services.leadconnectorhq.com/conversations/messages",
        headers={"Authorization": f"Bearer {GHL_TOKEN}", "Version": "2021-04-15"},
        json={
            "type": "SMS",
            "contactId": resolve_contact(to, location_id),
            "message": body,
            "fromNumber": from_,
        },
    )

GHL automatically threads the SMS into the same conversation as the inbound call.

HubSpot / Salesforce

Send SMS via SignalWire, then push a Note or Activity to the CRM:

def send_and_log(from_, to, body, contact_id):
    sw_response = send_sms(from_, to, body)
    hubspot.engagements.create({
        "type": "SMS",
        "associations": {"contactIds": [contact_id]},
        "metadata": {"body": body, "direction": "outbound"},
    })

Custom CRM / Supabase

INSERT INTO call_events (call_sid, type, direction, body, created_at)
VALUES ($1, 'sms_textback', 'outbound', $2, now());

Then a dashboard view filters by phone number for unified conversation view.

Avoiding double-texts

If the same caller misses multiple calls in a short window (often happens with frustrated callers re-dialing), don't text them five times.

def should_send_textback(caller, called):
    recent = db.query("""
        SELECT 1 FROM textback_log
        WHERE caller = $1 AND called = $2
        AND created_at > now() - interval '15 minutes'
        LIMIT 1
    """, caller, called)
    return not recent

Cool-down window of 15 minutes is a common default.

Opt-out handling

Even though the caller initiated contact (which arguably implies consent for follow-up), STOP must still work:

@app.route("/sms-inbound", methods=["POST"])
def sms_inbound():
    from_ = request.form["From"]
    body = request.form["Body"].strip().upper()
    
    if body in ("STOP", "STOPALL", "UNSUBSCRIBE", "QUIT", "CANCEL", "END"):
        db.opt_out(from_)
        send_sms(to=from_, body="You've been unsubscribed. Reply START to resume.")
        return Response("", mimetype="text/xml")
    
    # Normal inbound handling

Conversion tracking

Tag each text-back so you can measure recovery rate:

CREATE TABLE textback_events (
    id UUID PRIMARY KEY,
    call_sid TEXT,
    caller TEXT,
    sent_at TIMESTAMPTZ,
    replied_at TIMESTAMPTZ,
    converted_at TIMESTAMPTZ,
    revenue NUMERIC
);

A "conversion" can be:

  • Reply within 24 hours → soft conversion
  • Booked appointment → mid conversion
  • Job won → hard conversion

Track all three to compute the recovery funnel.

Compliance — TCPA and CTIA

Inbound caller initiating contact does not by itself create marketing consent. The text-back must be transactional (responding to their inquiry) not promotional (selling a different service).

Transactional, allowed:

"We missed your call about plumbing — what's the address?"

Promotional, not allowed without prior opt-in:

"We missed your call! Get 20% off any service today with code AUTUMN20!"

Mixing the two ("Sorry we missed you — also we have a sale!") drifts into marketing territory. CTIA enforces this in the gray zone.

Pattern: AI receptionist + text-back hybrid

Combine the AI receptionist (handles the call when possible) with text-back (recovers when AI didn't reach a human or wasn't enough).

Flow:

  1. Caller dials, AI receptionist answers.
  2. AI qualifies, attempts transfer to live agent.
  3. If transfer fails (no agent available), AI offers to text the caller.
  4. Caller accepts → AI calls a SWAIG function that sends an SMS confirming the callback request.
  5. Webhook on call end double-checks: was an agent reached? If not, text-back fires automatically.

Pattern: missed call → voicemail-to-text

For callers who do leave a voicemail, transcribe it and text the transcript back to confirm receipt:

@app.route("/voicemail-recorded", methods=["POST"])
def voicemail_recorded():
    recording_url = request.form["RecordingUrl"]
    caller = request.form["From"]
    called = request.form["To"]
    
    transcript = transcribe(recording_url)  # AssemblyAI, Deepgram, or SW transcription
    summary = summarize(transcript)
    
    send_sms(
        from_=called,
        to=caller,
        body=f"Got your voicemail: '{summary[:120]}'. Reply here to add details or call back at any time.",
    )

This shows the caller their message was received and gives them a low-friction way to add information.

Common pitfalls

  • Texting from a different DID than they called — confusing, recipients ignore. Always send from the dialed number.
  • No cool-down — caller re-dials 3 times in 2 minutes and gets 3 identical texts. Embarrassing.
  • Treating answered-by-AMD-machine as a live answer — caller hit the office voicemail, didn't reach a human. That's a miss.
  • Slow webhook handler — synchronous DB or CRM calls block the SMS send. Fire the SMS first, log/CRM in background.
  • Missing STOP keyword respect — caller previously opted out, but the text-back fires anyway because the opt-out wasn't checked. CTIA complaint magnet.

Related patterns

References

  • TCPA 47 USC §227 — telemarketing consent rules
  • CTIA Messaging Principles v1.10 — transactional vs marketing classification
  • SignalWire docs — Call Status Callback parameters