Answering Guide
Build a Missed Call Textback That Does Not Embarrass the Business
A missed call textback should know who called before it picks its words, and it should never say something the business would not say. This guide shows you how to build one that holds up when nobody is watching.
- Year 1Website
- Year 2Map Listings
- Year 3Booking Tool
- Year 4Front Desk
- Year 5Payments
- Year 6Reports
Same six pieces.
Years of lessons, one read.
Free to read. Most of this guide is open. The finishing pieces at the end come to you when you opt in.
The Short Version
- Look up the caller first. A regular with a car in the bay should not get the stranger text.
- Keep every message in one place, or a wording fix will quietly undo itself.
- Work out day or night from the local clock every few minutes. Do not schedule the switch.
- Know when a person is already in the thread, and hand off with a time limit.
- Alarm on what customers feel, and never let the bot quote a price.
Why Most Textbacks Go Wrong
A missed call textback sounds simple. Someone calls, nobody picks up, a text goes out. Most tools stop there. That is where the trouble starts.
The first text is the one that does the damage. It goes out before anyone at the shop can see it. If it is written for a stranger and sent to everyone, your best customers get it too. Picture a man who booked a brake job ten minutes ago. He calls back with a question and gets a text asking what he needs help with. Nothing broke. The code ran fine. He still thinks you forgot him.
The fixes in this guide all come from the same idea. A textback is not a feature. It is your front door. Build it like one.
Recognize the Caller Before You Choose the Words
Look the number up before you pick the message. There are three kinds of caller, and each one needs a different opening.
- A customer with open work. Name the vehicle and offer status. They want to know where their car is, not what you charge.
- A past customer with nothing open. Greet them by name, then ask what they need.
- A stranger. The plain ask.
Match on the exact ten digit number. Accept the match only when exactly one customer has it. Anything unclear falls back to the stranger text. The lookup must fail open. A slightly plain text is fine. No text at all is not.
// Pick the opening text for a missed call. A failed lookup never blocks the send.
export async function pickOpening(fromNumber, lookup, copy) {
let match = null
try {
match = await lookup(fromNumber) // exact 10 digit match, one row, or null
} catch {
match = null
}
if (match && match.openWork) {
return copy.known_open.replace('{vehicle}', match.vehicle)
}
if (match) {
return copy.known_idle.replace('{first}', match.firstName)
}
return copy.stranger
}Be careful what counts as open work. A job marked paid can still have an estimate waiting for a yes. That customer is still in a live talk with you.
Keep One Copy Source
Message text likes to spread. It ends up in the settings row, in a set of day and night defaults, in the code that swaps modes and in the code that sends. You fix a typo in one place. It looks right. Then the next mode change puts the old words back.
Pick one home for the words and make every other place read from it. Store the text in one table or one file. When you first create the row, copy the code defaults into it byte for byte. After that, a wording change is one update, not a deploy.
A quick test tells you if you have this problem. Search your code for a line of the message. If it shows up in more than one file or row, a revert is waiting for you.
Derive Day and Night Mode, Do Not Schedule It
Most shops want different words after hours. The usual build is two scheduled jobs, one at opening and one at closing. That breaks in three ways.
- Cron runs on UTC. Twice a year the switch lands an hour off.
- If the morning job fails once, the shop stays in night mode all day.
- Two jobs plus a manual script means three things write one setting. Nobody can say why it changed at 4am.
Run one job every 15 minutes instead. It works out what mode the shop should be in from the local hour. If the live mode is wrong, it fixes it and logs why. If it is right, it writes nothing.
// Returns 0 to 23 in the shop's own time zone, or null if it cannot tell.
export function localHour(timeZone, now = new Date()) {
try {
const h = Number(new Intl.DateTimeFormat('en-US', {
timeZone, hour: 'numeric', hourCycle: 'h23',
}).format(now))
return Number.isInteger(h) && h >= 0 && h <= 23 ? h : null
} catch {
return null
}
}
// One rule for "night", imported by the copy swap, the price and the alarm.
export function isNight(timeZone, now = new Date()) {
const h = localHour(timeZone, now)
if (h === null) return null
return h >= 20 || h < 7
}Two details matter here. Use hourCycle: 'h23'. Some systems show midnight as 24 with the older option, and 24 is neither night nor day. Also return null when the hour cannot be read. The copy swap should then leave things alone, while the alarm should ring anyway. Put this rule in one file and import it everywhere, so the price, the words and the alarm all agree on when night starts.
Debounce Bursts and Honor STOP
People who cannot get through often call two or three times in a row. Each missed call should not fire its own text. Keep a short memory per number and send one text per window.
// A caller who rings three times in two minutes gets one text, not three.
const WINDOW_MS = 10 * 60 * 1000
export async function shouldText(fromNumber, store, now = Date.now()) {
const last = await store.get('textback:' + fromNumber)
if (last && now - Number(last) < WINDOW_MS) return false
await store.put('textback:' + fromNumber, String(now))
return true
}STOP is not optional. When someone texts STOP, UNSUBSCRIBE, CANCEL, END or QUIT, mark the number opted out before anything else runs. Send one plain confirmation and nothing more. Check the opt out flag on every send path, including the textback, the follow up and any agent reply. Your phone provider may handle STOP for you. Record it on your side anyway, so your own code never tries to text that number again.
Quiet hours do not apply here. If someone called you, a reply is not marketing. Answer them at any hour. Quiet hours belong on campaigns you start.
Know When a Person Is Already Replying
Once staff can type into the same thread, the bot has to answer a hard question. Did we send that, or did a person? Match each outgoing text against the bodies you recorded sending. Anything with no match is treated as typed by a person, and the bot stands down.
Two traps live here. First, providers often add opt out words to the first text a new contact gets. So match on a prefix in either direction, not an exact string. Second, set a floor of about 40 characters. Short texts like "Got it" match too many things. Claiming a person's text as yours hides a real human from the system, which is the costly mistake.
Record both sides of the talk before the code decides to stay quiet. If recording runs after that check, every thread where the bot chose silence looks empty in the customer file.
Hand Off With a Time Limit
"A person will take this" cannot mean silence. If a thread is handed off and nobody answers, the customer waits forever. Give the hand off a grace period, around 15 minutes. After that, the bot may resume, but only if four things hold. No person has actually replied. The customer is still waiting. The bot has not hit its question cap. The customer has not opted out.
Once a real person speaks, the thread is theirs for good.
Alarm on the Outcome and Never Quote a Price
A job can run on time every two minutes and fail every time. A monitor that asks "did it run" stays green through the whole outage. Ask the question customers care about instead. Is anyone waiting on a reply that is not coming?
Only trust the bot's own sends as proof it is alive. Leave out threads a person owns. Cap the age so old backlog does not ring all night. Then watch for the trap in that cap. On a quiet night, a real outage ages its own victims past the limit. The waiting list goes empty and looks like an all clear. So only call it clear when you have proof the bot spoke recently.
Last, the bot never shows a customer a price. Run its intake through the same quote builder your staff use. Let it draft the quote and leave it for a person to approve. Showing a total is a small code change and a big promise, and that is the owner's call.
What the Finishing Pieces Contain
You can build a sound textback from everything above. The finishing pieces save you the last week of fixes. They hold the copy file for all three caller types with the night wording, the hand off and stand down logic as tested code, the alarm decision function with the quiet night test, and the checklist we run before a textback goes live. Enter your email to open them. For the full system this sits inside, see the AI front desk build.
The finishing pieces
Get the Rest of This Guide
You have the method. The finishing pieces are the parts you copy straight into your own work:
- The Copy File
- Stand Down and Hand Off Code
- The Alarm Decision
- The Go Live Checklist
Questions People Ask
Will a missed call text annoy my regular customers?
Only if it treats them like strangers. Look up the number first and give known customers a status text about their vehicle instead of a sales question.
Can the text go out at night?
Yes. Someone called you, so a reply is not marketing. Change the wording at night to say when a person will answer, but still reply.
What happens when a customer texts STOP?
Mark the number opted out before anything else runs, send one confirmation and stop every automated path. A later call from them should not restart texts.
Should the bot give prices by text?
No. Let it gather the details and draft a quote for a person to approve. A price in a text is a promise the owner should make, not the code.
We sell time
Get Years of Building Switched On in Hours
A thirty minute call. A written price. Nothing built until you say yes.