Scale outreach on iMessage, not SMS
Outbound iMessages from Salesforce, HubSpot, GoHighLevel, Clay, or API. Higher open and reply rates than SMS.
title: "Build AI Agents That Text Over iMessage: A Developer Guide" description: "How to connect AI agents (Claude, GPT, LangChain, CrewAI) to iMessage using the Tuco AI API. Architecture, code examples, and best practices for sales and support agents." publishDate: "2026-04-09" authorName: "BG" tags: ["ai-agents", "imessage-api", "developer", "automation", "langchain", "sales-agent", "support-agent"] faq:
- question: "What rate limits does the Tuco iMessage API enforce?" answer: "Tuco enforces 150 outbound messages per day per line and 25 new contacts per day to stay within Apple's spam detection thresholds. New lines start at 20 messages per day and ramp up over a three-week warm-up period. These limits protect your sending line from being flagged by Apple."
- question: "Which AI agent frameworks work with the Tuco iMessage API?" answer: "Any framework that can make HTTP requests works with Tuco's REST API. LangChain, CrewAI, AutoGen, and custom agent loops built with the Anthropic or OpenAI SDKs all integrate with a single POST endpoint. The API is framework-agnostic -- your agent just needs to send JSON and handle webhook callbacks."
- question: "How do I receive iMessage replies in my AI agent?" answer: "Tuco sends a webhook POST request to your configured endpoint whenever a customer replies. The payload includes the message body, sender phone number, and timestamp. Your agent processes the webhook, classifies the intent using an LLM, and responds via another API call."
- question: "Can I test the Tuco iMessage API before going live?" answer: "Yes, Tuco offers sandbox testing so you can send messages to your own number, verify webhook delivery, and test your classification logic before pointing real leads at your agent. Full end-to-end testing is recommended before production use."
build AI agents that text over iMessage
Every AI agent framework has tools for email. Tools for Slack. Tools for SMS. Tools for Discord, Teams, WhatsApp, and a dozen other channels.
None of them have a tool for iMessage.
That's a problem. Your customers don't live in Slack. They don't check email at 9pm. And SMS? Carrier filters kill 30-40% of business texts before they ever arrive. The green bubble graveyard is real.
iMessage is the channel people actually trust. 98% open rates. Messages land instantly with your name and photo attached. No spam folders, no carrier filtering, no A2P registration headaches. When your AI agent needs to reach a human and get a response, iMessage is the highest-signal channel available.
But connecting an AI agent to iMessage has been nearly impossible. Apple doesn't offer an API. You can't just pip install a package and start sending. The infrastructure requires real Apple hardware, managed Apple IDs, and deep knowledge of how Apple monitors messaging patterns.
That's exactly what Tuco AI's API solves. It gives your AI agent a clean REST endpoint to send and receive iMessages -- and handles all the infrastructure underneath. Real iPhones (not virtual machines), managed Apple IDs, and 200+ SIMs burned through to map Apple's spam detection rules so your messages actually land.
Here's how to wire it up.
the architecture: AI agent to iMessage in one API call
The flow is simple. Your AI agent talks to Tuco's REST API. Tuco talks to iMessage. The customer replies. Tuco sends a webhook back to your agent. Loop.
Your AI (Claude/GPT) → Tuco API → iMessage → Customer
↓
Your AI (Claude/GPT) ← Webhook ← Tuco API ← Customer Reply
sending a message
One POST request. That's it.
curl -X POST https://app.tuco.ai/api/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"text": "Hey Sarah, quick question about your last order. Got a sec?"
}'
receiving replies via webhook
When the customer replies, Tuco fires a webhook to your endpoint with the message body, sender number, and timestamp. Your AI agent processes the reply and decides what to do next -- respond, escalate, or log it.
the full agent loop in Python
Here's a minimal agent loop that sends a message, listens for the reply webhook, classifies the intent with Claude, and responds:
import anthropic
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
client = anthropic.Anthropic()
TUCO_API = "https://app.tuco.ai/api/messages"
TUCO_KEY = "YOUR_API_KEY"
def send_imessage(to: str, text: str):
"""Send an iMessage through Tuco API."""
resp = requests.post(TUCO_API, json={"to": to, "text": text},
headers={"Authorization": f"Bearer {TUCO_KEY}"})
return resp.json()
def classify_and_respond(message: str, sender: str):
"""Use Claude to classify intent and draft a reply."""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=200,
system="""You're a sales assistant replying over iMessage.
Rules:
- Keep responses under 160 characters
- Be conversational, not corporate
- If the person wants to talk to a human, reply EXACTLY: HANDOFF""",
messages=[{"role": "user", "content": message}]
)
return response.content[0].text
@app.route("/webhook/reply", methods=["POST"])
def handle_reply():
"""Process incoming iMessage replies."""
data = request.json
sender = data["from"]
message = data["text"]
reply = classify_and_respond(message, sender)
if reply == "HANDOFF":
# Notify your team via Slack, CRM, etc.
notify_human(sender, message)
else:
send_imessage(sender, reply)
return jsonify({"status": "ok"})
That's the skeleton. Every AI agent architecture -- LangChain, CrewAI, AutoGen, or a custom loop -- fits into this pattern. The Tuco API is just another tool in your agent's toolkit.
iMessage automation from your stack
Connect your CRM or API. Send to iOS users without carrier limits or spam filters.
building a sales agent that books meetings over iMessage
This is where it gets practical. You've got leads coming in from HubSpot, Salesforce, or any CRM. Your AI agent's job: reach out fast, qualify the lead, and either book a meeting or hand off to a rep.
step 1: catch the lead
Your CRM fires a webhook when a new lead comes in. You grab the name, phone number, and whatever context you have (form fills, lead source, company).
step 2: check iMessage availability
Not every lead has an iPhone. Before sending, check if the number is iMessage-capable through the Tuco API. If it's not, fall back to SMS or email.
step 3: send the first message
This is critical. Your first text needs to be under 160 characters, personal, and ask a question. Not a pitch. Not a link dump.
first_message = f"Hey {lead['first_name']}, saw you checked out our demo page. What problem are you trying to solve?"
send_imessage(lead["phone"], first_message)
Why 160 characters? Because iMessage is texting, not email. Nobody reads a wall of text from a number they don't recognize. Short, personal, question-based. That's what gets replies.
step 4: classify the reply and respond
When the lead texts back, your agent needs to figure out what they want. Here's a more detailed classification approach:
def classify_lead_reply(message: str, lead_context: dict) -> dict:
"""Classify lead intent and generate appropriate response."""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=300,
system="""Classify this iMessage reply from a sales lead.
Return JSON with:
- intent: "interested", "question", "not_now", "unsubscribe", "confused"
- reply: your response (under 160 chars, conversational)
- escalate: true/false (true if they ask for pricing or want to talk to someone)
Context about the lead:
""" + str(lead_context),
messages=[{"role": "user", "content": message}]
)
return parse_json(response.content[0].text)
# In your webhook handler:
result = classify_lead_reply(message, lead_context)
if result["escalate"]:
notify_sales_rep(sender, message, lead_context)
send_imessage(sender, "Let me connect you with someone from our team. They'll text you shortly.")
elif result["intent"] == "unsubscribe":
add_to_optout_list(sender)
send_imessage(sender, "No worries, I won't text again. Have a good one!")
else:
send_imessage(sender, result["reply"])
step 5: multi-turn conversations
Real sales conversations aren't one-and-done. The lead asks a question, your agent answers, they ask another. You need to track conversation state.
Store each message in a thread (a database row keyed by phone number). When a new reply comes in, pass the full conversation history to Claude so it has context. Without history, your agent asks "what problem are you trying to solve?" twice in a row -- and the lead immediately knows they're talking to a bot.
Keep the thread window reasonable. Last 10 messages is usually enough context without blowing up your token costs.
the results
Teams running AI sales agents over iMessage see 5-10x the reply rate compared to email sequences. That's not a guess -- it's what our customers report. Email gets 1-3% reply rates. iMessage consistently pulls 15-25%.
The speed matters too. Your AI agent responds in seconds, not hours. A lead fills out a form, and 60 seconds later they're in a two-way conversation on iMessage. By the time your competitor's email drip fires its first message four hours later, your agent has already qualified the lead and booked the meeting.
One more thing that doesn't show up in reply rate metrics: iMessage conversations feel real. The lead sees your name, your photo, and a conversational message. There's no "sent via Salesforce" footer. No 10-digit shortcode. It looks and feels like a person texted them -- because the channel itself carries trust that SMS and email can't match.
building a support agent that handles FAQs
Sales agents get the most attention, but support agents might be the better first project. The pattern is simpler and the ROI is immediate.
One of our customers -- a pharmaceutical company -- handles repeat purchase questions over iMessage. Customers text in asking about dosage, refill timing, shipping status. The basics. Before the AI agent, a human rep answered every single one of those messages manually.
how the support agent works
The agent pulls from a knowledge base (could be a vector store, a simple JSON file, or your help docs) and generates responses. The key addition: a confidence threshold.
def handle_support_message(message: str, sender: str):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=300,
system="""You're a support agent for a health products company.
Answer customer questions using ONLY the knowledge base below.
Keep answers under 160 characters.
If you're not 90%+ confident in your answer, respond EXACTLY: HANDOFF
Knowledge base:
- Standard shipping: 3-5 business days
- Refills: reorder 7 days before you run out
- Returns: 30-day window, contact support@company.com
- Dosage: follow label instructions, consult your doctor for changes""",
messages=[{"role": "user", "content": message}]
)
reply = response.content[0].text
if reply == "HANDOFF":
send_imessage(sender, "Good question -- let me get someone from our team on this. They'll text you back shortly.")
route_to_human(sender, message)
else:
send_imessage(sender, reply)
The confidence threshold is what makes this work in production. The AI handles the easy stuff -- "when will my order arrive?" -- and bumps anything ambiguous to a human. The pharmaceutical company cut their manual response volume by about 60% in the first month. The remaining 40% still goes to humans, but those are the messages that genuinely need a person.
No customer gets a wrong answer from the bot. They either get the right answer instantly, or they get a fast handoff.
the "wall of text" problem (and how to fix it)
Here's something every developer building AI agents for messaging runs into: LLMs write like they're composing emails.
It makes sense. GPT-4, Claude, and every other model trained on internet text has seen billions of emails, blog posts, and documents. Long-form writing is its default mode. Ask it a question and you get three paragraphs with bullet points and a summary.
That's fine for email. It's death for iMessage.
Think about how you actually text. Short bursts. Fragments. Maybe an emoji. You don't write your friends a five-sentence paragraph with a topic sentence and supporting evidence.
When your AI agent sends a 400-character wall of text over iMessage, the recipient's first instinct is: "this is a bot." And they're right -- but that shouldn't be the first thing they feel.
the system prompt fix
You have to constrain the model at the system prompt level. Asking nicely doesn't work -- you need hard rules.
You're texting a customer over iMessage. Follow these rules strictly:
1. NEVER exceed 160 characters per message
2. Write like you're texting a friend, not writing an email
3. Use fragments. Skip the greeting.
4. One thought per message
5. If you need to say more, break it into 2 separate texts
6. No bullet points. No numbered lists. No headers.
7. Contractions only (don't, can't, won't -- never do not, cannot)
double texting vs. single message
Sometimes the answer genuinely needs more than 160 characters. In that case, send two short messages with a 2-3 second delay between them -- the way a real person would. Two quick texts feel natural. One long block feels automated.
Your API calls would look like:
send_imessage(to, "Your order shipped yesterday")
time.sleep(3)
send_imessage(to, "Should arrive Thursday. I'll send you the tracking link")
This small detail -- splitting long responses into natural-feeling double texts -- is the difference between an AI agent that feels human and one that feels like a chatbot wearing a phone number as a disguise.
best practices: rate limits, anti-spam, and keeping your line healthy
Building the agent is the fun part. Keeping it running without getting flagged is the engineering part.
respect the rate limits
Tuco enforces these limits because Apple enforces them. Push past them and your line gets flagged:
- 150 messages per day per line. This is total outbound. Plan your agent's daily send volume around this.
- 25 new contacts per day. Messages to people you've already texted don't count toward this. But first-touch outreach to new numbers is capped.
- Warm-up period. New lines start at 20 messages/day and ramp up over three weeks. Don't skip this.
anti-spam patterns
Apple watches for bot-like behavior. Your agent needs to avoid it:
- Space out messages. Don't blast 50 messages in 60 seconds. Add random delays between sends (30-90 seconds for outbound campaigns).
- Personalize everything. Identical messages to different numbers is the fastest way to get flagged. Use the lead's name, reference their specific context.
- Avoid blast patterns. If your agent sends to 100 people at 9:00am sharp, that looks automated. Stagger over a window.
- Mix up your copy. Even small variations ("Hey" vs "Hi" vs the person's name) help your messages look organic.
fall back to SMS
About 35-40% of US smartphone users are on Android. Your agent should check iMessage availability first and fall back to SMS when the recipient doesn't have an iPhone. Don't just skip Android users -- route them through your existing SMS provider.
Build this routing into your agent's send function so it's automatic. The Tuco API can tell you whether a number supports iMessage before you send. If it doesn't, hand the message off to Twilio, your GHL account, or whatever SMS provider you're already running. Your agent shouldn't care which channel delivers the message -- it just sends and the routing layer figures it out.
test before you go live
Tuco offers sandbox testing. Use it. Send messages to your own number. Verify webhooks fire correctly. Make sure your classification logic handles edge cases (empty replies, emoji-only replies, "wrong number" responses).
Test the full loop end-to-end: trigger a fake lead in your CRM, watch the webhook fire, confirm the iMessage arrives on your phone, reply, verify your agent processes it correctly. Do this ten times with different message types before you point real leads at it. The bugs you catch in testing are embarrassing. The bugs you catch in production cost you deals.
handle opt-outs programmatically
When someone says "stop," "unsubscribe," or "don't text me," your agent needs to catch it and immediately add that number to a suppression list. This isn't optional -- it's both a legal requirement and an anti-spam necessity. Build the opt-out check into every webhook handler before any other logic runs.
OPTOUT_PHRASES = ["stop", "unsubscribe", "opt out", "don't text", "remove me"]
def is_optout(message: str) -> bool:
return any(phrase in message.lower() for phrase in OPTOUT_PHRASES)
# First thing in your webhook handler:
if is_optout(data["text"]):
add_to_suppression_list(data["from"])
send_imessage(data["from"], "You've been removed. Won't text again.")
return jsonify({"status": "opted_out"})
start building
AI agents are only as good as the channels they can reach people on. Email is slow. SMS is filtered. iMessage is where your customers actually pay attention.
The Tuco API gives your agent a clean path to iMessage without dealing with Apple hardware, Apple ID management, or spending months figuring out spam detection rules. We burned through 200+ SIMs so you don't have to.
Whether you're building a sales agent that qualifies leads in seconds, a support bot that handles FAQs at 2am, or something we haven't thought of yet -- the architecture is the same. POST to the API, listen for webhooks, let your AI decide what to say.
Three places to go from here:
- Book a demo -- we'll walk you through the API and get you set up
- Developer docs -- full OpenAPI spec, webhook reference, code examples
- Agentic use cases -- deeper dive into AI agent patterns on iMessage
If you've got an agent running on email or SMS today, you can add iMessage in an afternoon. The API is that simple. The lift in reply rates is that dramatic.
Tuco AI provides the iMessage API for AI agents and automation. Real iPhones, managed Apple IDs, and infrastructure built for developers. Get started.