Qualify Leads With an AI Agent Over iMessage
Short answer: put an AI agent on a dedicated iMessage number, write your qualifying questions (budget, timeline, location, need) into its business context as a script, and give it a goal: a booking link it shares only when a lead qualifies. ZeroSettle's hosted agent does this with no server, reads your pricing and FAQ files, and can check availability through an MCP server you connect. Your reps take the hot leads. If you want a score on every lead posted to your CRM, run your own model behind the webhook and add a structured scoring step.
Key takeaways
- Write the script, not a persona. The agent knows only what your business context, files and connected tools give it, so the qualifying criteria, the order of questions and what disqualifies a lead all go in writing.
- The goal is the gate. Set
goaltodemo_bookedwith your booking link, and tell the agent to share it only once the criteria are met. - Handoff has two shapes. With the hosted agent, reps read conversations in Close, HubSpot or GoHighLevel, or you switch
managed_bytoapifor the whole line group. With your own code, a person can take over one conversation at a time. - Consent first. Only text leads who gave you their number for this. Leads who text first are never limited.
- Price: Pro is $49/month for one number with 500 free credits once; Growth is $199/month per number with 50 new contacts a day each; Enterprise is custom.
Why qualify leads with an AI agent over iMessage?
Because the first few questions on a new lead are the same every time, and an agent can ask them the minute the lead arrives while your reps spend their calls on people who are ready to buy. Where is the project, when does it need to happen, what budget is realistic, what exactly do they need? Those answers decide whether a lead gets a rep's hour or a polite referral.
iMessage suits this because it is where people already answer friends, so a short question from a business number reads like a normal conversation. ZeroSettle gives you a dedicated number on real iMessage, running on Apple hardware we operate for you in a server facility, and either its hosted agent answers on that number or your own code does. iMessage reaches people on Apple devices; a lead on Android won't get it (SMS fallback is a limited pilot ZeroSettle has to enable per account), so keep a call or email path for those leads.
This post is for sales teams and agencies that get more inbound leads than reps can call. If speed is your main problem, read speed to lead for home service businesses first; for the wider funnel, see iMessage for lead generation.
What should the agent qualify on?
Qualify on the few facts that decide what a rep does next, usually budget, timeline, location and need, and decide in advance what counts as a pass for each. Vague criteria give vague conversations. Write each one as a question the agent can ask in plain words, an answer that qualifies, and what to do when it doesn't.
| Criterion | How the agent asks | Qualifies | If not |
|---|---|---|---|
| Location | “Is the house in the Denver metro?” | Inside the service-area file | Thank them, say it's outside the area, stop |
| Need | “Cabinets and counters, or a full remodel with layout changes?” | Kitchen or bath remodel | Small repairs: give the handyman referral from the context |
| Timeline | “When are you hoping to start?” | Within 6 months | Offer to check back; no booking link yet |
| Budget | Share the starting price from the pricing file, then ask if it fits | At or above the listed starting price | Explain smaller-scope options, no pressure |
Ask one question per text. Sharing the starting price first (rather than asking “what's your budget?”) gives the lead something concrete to react to and saves a round trip.
How do you write the qualifying script?
Write it into the agent's business_context the way you would brief a new SDR: who it is texting and why, the criteria in order, what it may share, when to send the booking link, and what it must never say. The hosted agent uses it exactly as written, never invents facts it doesn't give, and the field holds up to 40,000 characters.
You are Cedar & Pine Remodeling's automated assistant. You text people who
asked for a remodel quote on cedarpine.example or a lead form. Your job is to
learn four things, one question at a time, then invite qualified leads to
book a free in-home design visit.
Ask in this order, skipping anything they already told you:
1. Location: is the home in the Denver metro? Check the service-area file.
2. Need: kitchen, bath, or other; cosmetic or layout changes.
3. Timeline: when they want to start.
4. Budget: share the starting price for their project from the pricing
file and ask whether it fits. Never quote a final price.
Qualified = in the service area + kitchen or bath remodel + start within
6 months + budget at or above the starting price. Only then send the visit
link. Not qualified: be kind, say what we can't do, and give the referral
or check-back offer below. Never argue.
If they ask for a person, or ask about permits, financing approval or
anything not in these notes or the files: say a project designer will call
them within one business day.
If anyone sincerely asks whether you are a person, say you are Cedar &
Pine's automated assistant.
Never promise dates, discounts or prices that aren't in the files.
Example:
Them: how much for a kitchen?
You: Full kitchen remodels here usually start around $45,000. Is that in
the range you had in mind?
Keep the voice out of it; the personality setting handles tone. A qualifying agent for a sales team usually wants professional (“polished, no slang or emoji”, in the docs' words).
Which agent settings fit qualification?
A qualifying agent needs six settings beyond the business context: who answers, the voice, the speed, the goal and link, the opener, and the follow-up triggers. Each is a field on PUT /api/imessage/agent and a box in the console's Messaging tab (walkthrough).
| Field | Suggested | Why |
|---|---|---|
managed_by | zerosettle | The hosted agent answers every inbound text on the line group |
personality | professional | Polished, no slang or emoji |
response_time | instant | Leads go cold; human (5 to 10 minutes) if you want it to pace like a person |
goal + link | demo_booked + your booking page | The agent invites them to book with this link when they're interested |
first_message | Two short texts ending on a question | Sent when you start a conversation and as the first answer to someone who texts first |
triggers | One or two once follow-ups | Picks quiet conversations back up; never fires when the lead wrote last |
From code, send the script from a file so quoting stays sane. Only the fields you send change:
BASE=https://portal.zerosettle.io # ZEROSETTLE_TOKEN is exported in your shell
jq -n --rawfile ctx qualify.txt '{
managed_by: "zerosettle", business_context: $ctx,
personality: "professional", response_time: "instant",
goal: "demo_booked", link: "https://cedarpine.example/visit",
timezone: "America/Denver",
triggers: [
{repeat: "once", after_days: 1, at: "morning",
instruction: "If they have not replied, send one short follow-up that asks your last question again in different words."},
{repeat: "once", after_days: 4, at: "afternoon",
instruction: "If they qualified but have not booked, remind them once that the visit link is there. If they said no or not now, send nothing."}]}' |
curl -X PUT "$BASE/api/imessage/agent" \
-H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" -d @-
Triggers land at a random moment in their window (morning is 9 to 11 AM in your timezone) and follow every sending rule. One matters here: a contact who has never replied gets at most 2 messages, and texts sent within 5 minutes of the first count as one. A two-text opener plus the next-morning follow-up uses both; anything after that waits for their reply. Lead nurture without getting your number flagged covers follow-up cadence in depth.
What should the agent read and look up?
Give it files for what it should know and an MCP server for what it should look up. Files are price lists, FAQs, service-area lists and policies (PDF, Excel, CSV, Word or text, up to 10 MB each and 10 per line group, sharing a space of 20,000 tokens); the agent reads every file before every answer. Keep prices in a file rather than the script, so updating them is a re-upload.
curl -X POST "$BASE/api/imessage/agent/files" \
-H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" \
-d "{\"name\": \"pricing.pdf\", \"data_b64\": \"$(base64 < pricing.pdf | tr -d '\n')\"}"
curl -X POST "$BASE/api/imessage/agent/mcp" \
-H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" \
-d '{"url": "https://mcp.cedarpine.example/mcp", "token": "YOUR_SERVER_TOKEN"}'
An MCP server lets the agent answer “do you have anything next week?” with real times instead of just the link. ZeroSettle lists the server's tools and turns on the ones that only read; a tool that changes something, such as booking a slot, starts off, because anyone who texts the number could ask the agent to use it. A read-only check_availability plus your booking link is the safe default. Details are in the AI agent docs.
How do leads get into the conversation?
Either the lead texts your number first, or you start the conversation with someone who gave you their number for this. Both are fine; the first is better.
- Text first. Put a “Text us” button (an
sms:link) on the form's thank-you page, your ads and your site. People who text first never count toward the new-contact limit, and links you send them are tappable from the start. See how to get customers to text you first. - You start. When a form submission with consent language arrives, start the agent on that number. It sends your
first_messageand answers whatever comes back.
curl -X POST "$BASE/api/imessage/agent/start" \
-H "Authorization: Bearer $ZEROSETTLE_TOKEN" -H "Content-Type: application/json" \
-d '{"to": "+13035550147", "name": "Maya"}'
Conversations you start count as new contacts: 5 a day per number on Pro, 50 a day per number on Growth (and 3 per 15 minutes). Over the limit you get 429 new_contact_limit with retry_after_s. Keep links out of the opener: until the lead replies or saves your number, a link from your number shows as plain text they can't tap (why). For ad leads, texting Facebook and Instagram lead-ad leads shows the wiring, and agencies on GoHighLevel can follow the GoHighLevel setup guide.
Consent covers who you text
Only start conversations with people who gave you their number for this purpose: a quote form, a lead-ad form with consent language, or a past customer who agreed to texts. Never purchased lists, scraped numbers or cold outreach. ZeroSettle suspends lines used for unsolicited messaging, and a lead's STOP blocks every number on your account from texting them again. iMessage marketing and TCPA consent explains the rules; it is not legal advice.
What does a qualifying conversation look like?
A good one is short, asks one thing at a time, discloses honestly, and only sends the link once the lead has replied and qualified.
- Tue 10:14 AM
- Business: Hi, this is Cedar & Pine Remodeling's automated assistant. Thanks for asking about a remodel.
- Business: Is the home in the Denver metro, and when are you hoping to start?
- Lead: yes, Lakewood. hoping for spring
- Lead: wait is this a real person
- Business: I'm Cedar & Pine's automated assistant. A project designer takes over once we know a bit more.
- Business: What's the scope: cabinets and counters, or a full remodel with layout changes?
- Lead: full gut, we want to move the sink to an island
- Business: Got it. Full kitchen remodels with layout changes usually start around $45,000 here. Is that in the range you had in mind?
- Lead: we were thinking 50-60
- Business: That fits what you described. Pick a time for a free in-home design visit here: https://cedarpine.example/visit
- Lead: booked thursday 4pm
- Business: Thanks, Maya. See you Thursday at 4.
demo_booked. Blue bubbles are the hosted AI agent, sent from the business's number; gray bubbles are the lead's. The opener is the first_message, sent exactly as written (the name you pass is known to the agent but never put into it). The agent answers the two-text burst at once, discloses when asked, and sends the link only after the lead replied and met all four criteria.How do you hand hot leads to a person?
With the hosted agent, the booking itself is the cleanest handoff: the lead picks a time on your calendar and a rep shows up prepared. For everything else there are three routes, and the main constraint is that the hosted agent's setting covers a whole line group, not one conversation.
| Route | How it works | Watch out for |
|---|---|---|
| Reps work the CRM | Connect Close, HubSpot or GoHighLevel. Every 1:1 text, the agent's included, is logged on the lead (group chat messages are not logged in Close or HubSpot). Reps read the thread and call. | While the agent manages the number, texts sent from Close, HubSpot or GoHighLevel are refused (403 managed_by_zerosettle or a failed status) |
| The agent routes it | Tell it in the script to promise a call from a person when a lead asks for one; optionally give your MCP server a tool that files a task for your team | A tool that changes something starts off; turn it on deliberately |
| Switch to API | Set managed_by to api; reps reply from the console or the CRM | It applies to every conversation on that line group, and the webhook gets all inbound texts from then on |
Sending your own text through POST /api/imessage/send while the agent manages the group does go out, but the agent reads it as its own and answers the reply. That's fine for a nudge, not for a takeover. GET /api/imessage/agent/started lists the conversations you opened with replied on each, which is a quick way to see who engaged. CRM setup is in the CRM integrations docs.
How do you score leads with your own code?
Run your own model behind the webhook (managed_by: "api") and add a second, structured call after each reply that scores the lead from the thread and posts the result to your CRM. Your code then owns each conversation, so a rep can take over one lead while the model keeps answering the rest. The reply loop itself is in Building iMessage Agents; this is the scoring step.
// src/score.ts
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { zs } from "./zerosettle.js"; // the fetch wrapper from the agent guide
const claude = new Anthropic();
const LeadScore = z.object({
in_service_area: z.enum(["yes", "no", "unknown"]),
timeline: z.enum(["within_6_months", "later", "unknown"]),
budget_fits: z.enum(["yes", "no", "unknown"]),
need: z.string(),
score: z.enum(["hot", "warm", "cold", "disqualified"]),
summary: z.string(),
});
const RUBRIC = `You score inbound remodeling leads from an iMessage thread.
Hot: Denver metro, starting within 6 months, budget at or above the starting price, clear project.
Warm: two or three of those. Cold: one or none so far.
Disqualified: outside the service area, or they asked us to stop.
Use only what the lead said. Answer "unknown" when they haven't said.`;
export async function scoreLead(handle: string) {
const q = new URLSearchParams({ handle, after_id: "0", limit: "500" });
const rows: any[] = (await zs(`/api/imessage/messages?${q}`)).messages;
const transcript = rows
.filter((r) => r.status !== "failed" && r.text)
.map((r) => `${r.direction === "in" ? "Lead" : "Us"}: ${r.text}`)
.join("\n");
const res = await claude.messages.parse({
model: "claude-opus-5-5",
max_tokens: 16000,
output_config: { effort: "low", format: zodOutputFormat(LeadScore) },
system: RUBRIC,
messages: [{ role: "user", content: transcript }],
});
return res.parsed_output; // null if it couldn't be parsed
}
Call it after each reply your agent sends, post the score to your CRM, and mark hot leads as owned by a person so your reply loop leaves them alone:
// src/handoff.ts
import { scoreLead } from "./score.js";
export const humanOwned = new Set<string>(); // persist this in real code
export async function afterReply(handle: string) {
if (humanOwned.has(handle)) return;
const s = await scoreLead(handle);
if (!s) return;
// Your CRM's own API; the URL is a placeholder.
await fetch(`${process.env.CRM_URL}/leads/score`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.CRM_TOKEN}` },
body: JSON.stringify({ phone: handle, ...s }),
});
if (s.score === "hot") {
humanOwned.add(handle); // reply() returns early for these handles
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: `Hot lead ${handle}: ${s.summary}` }),
});
}
}
With the line group managed by API, the rep answers that lead from the console composer, Close, HubSpot or the GoHighLevel inbox, and the conversation keeps logging to the CRM. Two rules from the webhook and AI agent docs matter here: answer only message.received (your own sends come back as message.sent), and check STOP before any model call. Your model's bill is yours; texts from a line group managed by API use no credits (videos aside).
Should the agent say it is automated?
Yes when asked, and preferably up front. The hosted agent says it is your automated assistant when someone sincerely asks, and points anyone who wants no more texts to STOP. To disclose from the first text, say so in first_message, as the example does; it is sent both when you start a conversation and as the first answer to someone who texts first. With your own model, put the same rule in the system prompt. AI disclosure laws vary by place and change; this is not legal advice.
What does it cost?
The number is a subscription, and the hosted agent's texts use credits: one per text it sends. A conversation with a two-text opener and six one-text answers uses 8 credits. Free credits come once per account and never refill; packs are 1,000 for $50 or 5,000 for $200 and never expire.
| Plan | Price | New contacts | Free credits (once) |
|---|---|---|---|
| Pro | $49/month, one dedicated number | 5 a day | 500 |
| Growth | $199/month per number (upgrade in the console), or $599/month for 3 numbers from a Growth link plus $199 per extra number; team members included | 50 a day per number, 3 per 15 minutes | 1,000 |
| Enterprise | Custom pricing through sales, for high volume and compliance needs | Set with sales; book a demo | |
Each number allows 10,000 messages a day, sent and received, with known contacts. The 30-day money-back guarantee covers the subscription.
For a team whose reps share the inbox, Growth's team members matter as much as its contact limit. Leads who text first don't touch the new-contact limit on any plan.
What mistakes should you avoid?
- Leaving the example context in place. Until you replace it, the agent talks about a made-up software business.
- Asking four questions in one text. People answer the last one. One question per text, in the order of what disqualifies fastest.
- Sending the link too early. In an opener it's untappable, and before qualification it books visits your reps shouldn't take.
- Prices in the script. Put them in a file and replace the file when they change.
- Turning on write tools casually. Anyone who texts the number can ask the agent to use them.
- Chasing silence. Two unanswered messages is the hard cap for a reason: repeated texts to people who never answer are the fastest way to get a number flagged by Apple, and then every send fails.
- Testing on leads. Text the number from your own iPhone with the questions real leads ask, including ones the context doesn't cover. It should say a person will follow up, not guess.
Ready to try it? Get a number, write the script, and run five test conversations before the first real lead.
Frequently asked questions
Can the AI agent book the meeting itself?
It shares your booking link when a lead is ready, which is what the demo_booked goal and link are for. With an MCP server connected, it can also check open times. A tool that actually books (one that changes something) starts off; turn it on only if you are fine with anyone who texts the number using it.
Do leads who text us first count toward the new-contact limit?
No. People who text the number first never count, and neither do ongoing conversations. The limit (5 a day per number on Pro, 50 a day per number on Growth) counts only conversations the number starts.
Can a rep take over one conversation while the agent keeps answering the rest?
Not with the hosted agent: switching managed_by to api hands every conversation on that line group back to you. A text you send through the API while the agent manages the group goes out, but the agent treats it as its own and answers the reply. Per-conversation takeover is something your own code does when the group is managed by API.
Will the agent tell leads it is an AI?
Yes. The hosted agent says it is your automated assistant when someone sincerely asks. You can also say so up front in the first message, which is sent both when you start a conversation and as the first answer to someone who texts first.
Do the agent's conversations show up in our CRM?
Yes, with Close, HubSpot or GoHighLevel connected: every 1:1 text the number sends or receives, the agent's included, is logged on the matching record. Group chat messages are not logged in Close or HubSpot. Zapier can also start a Zap on each message.
Can we use the agent on a list of leads we bought?
No. Text only people who gave you their number for this purpose, such as through a quote form or a lead-ad form with consent language, or who texted you first. ZeroSettle suspends numbers used for unsolicited messaging.
Sources
- ZeroSettle iMessage API docs: “Your iMessage number” (Pricing), “Send a message” (Links in a first message), “AI agent” (Managed by AI Agent, Triggers, Connectors, Credits), “AI agent in the console”, “Webhooks, step by step”, “Errors, limits, and compliance” and “CRM integrations”. Checked November 2, 2026.