Most channels a support team runs are ones the customer has to go to. Email needs opening, a portal needs logging into, live chat only exists while your site is open in a tab. iMessage is already open, the thread persists forever, and a customer coming back six weeks later lands in the same conversation with all the context still visible.
That persistence is the real advantage, and it is worth designing around rather than treating each message as a ticket.
Set expectations in the first message
The channel's strength is also its trap: customers read a blue bubble as a person who is present. If you are not present at 11pm, say so before they find out.
export async function onFirstInbound(msg: Inbound) { const open = withinBusinessHours(new Date(), msg.timezone); await messaging.send({ to: msg.from, text: open ? "Thanks — we've got this and someone will reply within about 15 minutes." : `Thanks for the message. We're open 8am–6pm and we'll reply first thing. ` + `If it's urgent, call ${SUPPORT_PHONE}.`, }); // Create the ticket regardless — the auto-reply is courtesy, not resolution. await helpdesk.createTicket({ channel: "imessage", contact: msg.from, body: msg.text, priority: open ? "normal" : "next_business_day", });}Do not open a channel you cannot staff
An unanswered blue bubble is worse than no channel at all, because the customer knows it was delivered and read. If you cannot cover the hours, either say so explicitly in the auto-reply or do not offer the channel.
Where AI helps and where it must stop
An AI first responder works well here — but the blue bubble implies a person, which raises the cost of a bad answer relative to a web chat widget. Draw the line explicitly, in code.
Let the model handle
- Order status and tracking
- Hours, location, parking, directions
- Rescheduling an appointment
- Simple how-do-I questions with a documented answer
Hand to a human
- Anything about money already paid
- A complaint or an unhappy tone
- Anything medical, legal or safety-related
- Any request the model has already failed once
const ESCALATE = [ /refund|charge|billing|invoice|money back/i, /cancel my|complaint|unacceptable|lawyer|attorney/i, /allergic|injury|hurt|emergency|urgent/i,]; export async function respond(thread: Thread, text: string) { // Escalate on content, on repeated failure, or on an explicit request. const mustEscalate = ESCALATE.some((r) => r.test(text)) || thread.aiAttempts >= 2 || /human|person|agent|someone real/i.test(text); if (mustEscalate) { await assignToAgent(thread); return messaging.send({ to: thread.contact, text: "Let me get a colleague on this — they'll reply shortly.", }); } const answer = await model.reply(thread, text); await thread.increment("aiAttempts"); return messaging.send({ to: thread.contact, text: answer });}The wider design considerations are in AI agents and customer texts.
What to track
- First response time, split by in-hours and out-of-hours. The out-of-hours number is the one customers actually feel.
- Resolution without escalation, if you run an AI responder — the honest measure of whether it is helping.
- Threads reopened after 24 hours. High numbers mean you are closing things that were not resolved.
- Deflection from phone. Usually the clearest cost saving, and the easiest to show.
For conversation design generally, two-way texting; for what you may safely put in a message, data retention for messages.
Common questions
- Is iMessage good for customer support?
- For businesses whose customers are mostly on iPhone, yes — threads persist, media is easy to send, and response rates are high. The main constraints are staffing hours and having a clear escalation path.