Use cases

Build a personal AI assistant you text on iMessage

ZeroSettle lets a developer build a private AI assistant they text from iPhone, Mac and Apple Watch: run a Pro line managed by API, allowlist your own handle and your family's, drop every other sender, and give your model tools for your calendar, email and tasks. With no public server, a script on a home machine polls GET /api/imessage/messages?after_id=… every few seconds, and inbound texts show up there within about 1–2 seconds. Set the line's name and photo (say, "Jarvis") so the assistant appears like a contact; the whole setup runs on the $49-a-month Pro plan.

Key takeaways

  • What it is: your own AI assistant on its own iMessage number. Your script and your model do the thinking; ZeroSettle carries the messages.
  • Who it fits: developers and power users with an always-on machine at home and a household on iMessage.
  • Price: Pro is $49/month for one number, with no per-message fees. Growth, from $599/mo, is for several numbers and more than this needs.
  • Main limit: Pro allows first messages to 5 new contacts a day per number, and a household that texts the line first never uses any of them. On every plan, two unanswered messages to one person pause sends until they reply.
  • What it does not do: no group chats, no SMS fallback for Android, and no scheduled sends. Your code keeps the reminder timer.

Who uses iMessage for a personal AI assistant?

A personal iMessage assistant suits developers and power users who want to text an AI the way they text a friend, from iPhone, Mac or Apple Watch. Assistant apps want another login and a home-screen slot; Messages is already on all three.

ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks. Here the only "customers" are you and your household. An assistant for other people to text is a different build, covered in giving an AI agent its own iMessage number; other jobs are in the list of iMessage use cases.

How iMessage for a personal AI assistant works, step by step

A script on your own machine reads the line every few seconds, ignores anyone not on your allowlist, and hands your messages to your model with your tools; the answer goes back with one send call. ZeroSettle carries the messages and nothing else.

  1. Get a Pro line and keep it Managed by API. On a line the hosted AI agent manages, your own sends are refused (403 managed_by_zerosettle). The MCP connector for Claude Code or Cursor can sign you up and mint the API token.
  2. Name the assistant with PUT /api/imessage/profile: "name": "Jarvis" (up to 64 characters; it becomes the contact's first name) and a JPEG or PNG under 3 MB. The card goes out automatically right after the assistant's first message to each person, who sees "Jarvis shared a name and photo with you".
  3. You and each family member text the line once. Anyone who texted the line first is never limited, and the assistant's links are tappable from then on.
  4. The script polls GET /api/imessage/messages?after_id=… every 3 seconds and keeps only inbound rows from an allowlisted handle. Everyone else gets no reply.
  5. It raises the typing bubble with POST /api/imessage/typing while your model works.
  6. Voice notes become text. A voice note arrives as a kind: "audio" row whose audio object carries duration_ms, and GET /api/imessage/messages/{id}/audio returns the recording as an .m4a (AAC) file for your speech-to-text.
  7. Your model answers with your tools for calendar, email, tasks and notes. The model, prompt and API keys are yours and never touch ZeroSettle.
  8. The reply goes out through POST /api/imessage/send, normally handed to Apple in 2–5 seconds.
  9. Reminders come from your timer. ZeroSettle doesn't schedule sends, so the script stores each reminder and sends it into your existing conversation when it is due.

Polling means your code sees a text within about 5 seconds: 1–2 seconds to appear in the messages list, plus up to one 3-second poll interval. With a public server, ZeroSettle's signed webhook pushes it instead; sending is the same single POST described in sending an iMessage from code without a Mac.

Example conversation: a personal AI assistant

This thread shows a calendar change, a voice note and the reminder it produced, seen from the assistant's side as the ZeroSettle console shows it.

  1. Wed 1:12 PM
  2. You: move my 3pm with Priya to tomorrow same time and tell her
  3. Jarvis is typing
  4. Jarvis: Moved to Thu 3:00 and emailed Priya 👍
  5. Wed 1:40 PM
  6. You: Voice message, 0:06
  7. Jarvis: Got it. I'll remind you about the dog food at 5:30, when you usually leave.
  8. Wed 5:30 PM
  9. Jarvis: Reminder: dog food on the way home 🐕👍
Illustrative example. Blue bubbles are sent from the assistant's number by your script; gray bubbles are yours. The six-second voice note ("remind me to buy dog food on the way home") reached the script as an .m4a file, and the 5:30 text was sent by the script's own timer.

Setup: an allowlisted poller with tools

The whole assistant can be one Python script: a poll loop, an allowlist check, a voice-note transcriber, your model call and a reminder list. The loop follows the polling recipe in the ZeroSettle docs.

python
# jarvis.py: run on an always-on machine at home with  python3 jarvis.py
import json, os, pathlib, time
import requests

BASE = "https://portal.zerosettle.io"
TOKEN = os.environ["ZEROSETTLE_TOKEN"]
# ALLOW="+15555550142,+15555550199,me@example.com": every handle you and your household text from
ALLOW = {h.strip().lower() for h in os.environ["ALLOW"].split(",")}
CURSOR = pathlib.Path("cursor.json")          # a restart resumes here instead of answering old texts
reminders: list[tuple[float, str, str]] = []  # (due unix time, handle, text): keep these on disk too
zs = requests.Session()
zs.headers["Authorization"] = f"Bearer {TOKEN}"

def think(handle: str, text: str) -> str:
    ...  # your model + your tools (calendar, email, tasks, notes, add_reminder)

def transcribe(m4a: bytes) -> str:
    ...  # your speech-to-text

def page(after: int) -> dict:
    r = zs.get(f"{BASE}/api/imessage/messages", params={"after_id": after, "limit": 100}, timeout=15)
    r.raise_for_status()
    return r.json()

def send(handle: str, text: str) -> None:
    for attempt in range(5):
        r = zs.post(f"{BASE}/api/imessage/send", json={"to": handle, "text": text[:4000]}, timeout=120)
        if r.status_code == 200:
            return
        err = r.json().get("error", {})
        if err.get("code") not in ("line_busy", "phone_offline", "phone_timeout"):
            print("send failed:", err.get("code"), err.get("message"))  # e.g. recipient_opted_out
            return
        time.sleep(2 * (attempt + 1))

def handle(m: dict) -> None:
    if m["direction"] != "in" or m["handle"] not in ALLOW:
        return                                # strangers get nothing back
    if m["kind"] == "audio":
        if not m.get("audio"):                # the file never finished downloading
            return send(m["handle"], "That voice note didn't come through. Can you send it again?")
        f = zs.get(BASE + m["audio"]["url"], timeout=30)
        f.raise_for_status()
        text = transcribe(f.content)
    elif m["kind"] == "message":
        text = (m.get("text") or "").strip()
        if not text:                          # a video or file: the row has no file and no text
            return send(m["handle"], "I can't open videos or files. Send a text or a voice note.")
    else:
        return                                # tapbacks and photos: handle them if you like
    zs.post(f"{BASE}/api/imessage/typing", json={"to": m["handle"], "state": "start"}, timeout=30)
    send(m["handle"], think(m["handle"], text))

if CURSOR.exists():
    cursor = json.loads(CURSOR.read_text())
else:                                         # first run: skip old history
    cursor = 0
    while (nxt := page(cursor)["next_after_id"]) != cursor:
        cursor = nxt

while True:
    try:
        r = page(cursor)
    except requests.RequestException as e:   # offline or a 5xx: wait and poll again
        print("poll failed", repr(e)); time.sleep(10); continue
    for m in r["messages"]:
        try:
            handle(m)
        except Exception as e:                # one bad message never stops the loop
            print("handle failed", m["id"], repr(e))
    cursor = r["next_after_id"]
    CURSOR.write_text(json.dumps(cursor))
    for due in [x for x in reminders if x[0] <= time.time()]:
        reminders.remove(due)
        try:
            send(due[1], due[2])
        except Exception as e:                # a network error must not kill the loop either
            print("reminder failed", due, repr(e))
    time.sleep(3)

Give think() an add_reminder tool that appends to reminders, and load recent history with GET /api/imessage/messages?handle=…&before_id=… (the rows just before the new message) when the model needs context; the AI agent section of the docs shows the thread-to-request mapping and the tool loop. A 👍 on a reminder arrives as a kind: "reaction" row whose target_id points at it, so the script can mark it done, as in on-call alerts you acknowledge with a tapback.

What goes wrong, and what the script does

  • The home machine sleeps. ZeroSettle keeps every message and the saved cursor resumes where it stopped, but reminders due meanwhile go out late. Decide whether a stale one is still worth sending.
  • A voice note arrives without its file. audio is null when the recording never finished downloading, so the script asks you to resend.
  • You send a video, a file or several photos. Video and files arrive as a row with no file, so the script answers that it can't open them. Of several photos sent together, only the first is delivered.

Limits and consent for a personal AI assistant

An assistant that texts only you and your household never meets ZeroSettle's new-contact limit, because each thread is a conversation someone started by texting the line. The cost is the $49 Pro plan.

How a personal assistant's traffic counts on Pro (ZeroSettle API docs, checked October 8, 2026)
What happensCounts asPro limit
You or a family member texts the lineInbound messageUnlimited (counted)
The assistant replies or sends a reminderOngoing conversationNo new-contact limit
Several reminders you don't answerUnanswered messages2, then 403 awaiting_reply until they reply
The assistant texts someone who never texted the lineNew contact5 a day per number
Typing bubble or name-and-photo cardNot a messageNever counts

A household of four who each text the line once uses none of the 5 daily new contacts. If the assistant texts your partner before your partner has texted it, that is one of the 5 for the day. Every plan pauses sends to a person after 2 unanswered messages (sends within 5 minutes of each other count as one), so if you ignore two reminders, the next is refused with 403 awaiting_reply until you text back; a 👍 counts as a reply.

Consent is simple here: the assistant only texts you and your household, and every other sender is ignored. Ask each person before adding their handle, and have them text the line first. The docs' quiet hours (nothing promotional before 8 am or after 9 pm in the recipient's time zone) are about promotional texts, and replies and reminders you asked for generally aren't promotional. This isn't legal advice; if you're unsure how the rules apply to you, check with counsel.

Don't text "stop" to your own assistant

A text with the word STOP, or a clear request like “leave me alone”, opts the sender out, and ZeroSettle then blocks every number on your account from reaching them. After "stop reminding me about the dog food", every reply and reminder fails with 403 recipient_opted_out. Say "cancel" or "never mind" instead; only START (or unstop) as the whole message opts you back in.

When ZeroSettle is not a fit for a personal AI assistant

ZeroSettle is the wrong choice for a personal assistant in these cases:

  • You want it free. Self-hosting on your own Mac costs nothing in software (as of October 2026, per each project's docs): OpenClaw, an MIT-licensed open-source assistant, reaches iMessage through a signed-in Mac's Messages, and the Apache-2.0 BlueBubbles server runs on any Mac on macOS Sierra or newer with iMessage activated. The trade-offs are in self-hosting iMessage vs a managed API.
  • You want it in group chats with friends. ZeroSettle conversations are one-to-one; group chats are on the roadmap.
  • You want it to text as you, from your own number. ZeroSettle gives the assistant its own number, which stays ZeroSettle's; porting a number in or out isn't offered.
  • You want ZeroSettle to schedule reminders. Your code keeps the timer, and a reminder is only as punctual as the machine running it.
  • Someone in the household uses Android. There is no SMS fallback, so the assistant can't reach them.

How much does iMessage for a personal AI assistant cost?

A personal assistant costs the Pro plan, $49 a month, because ZeroSettle has no per-message fees: messages are unlimited and only counted.

ZeroSettle plans (zerosettle.io, checked October 8, 2026)
PlanPriceWhat it includes
Pro$49/monthOne number, self-serve, unlimited messages with people who message you first, first messages to 5 new contacts a day, no setup fee, 30-day money-back guarantee
GrowthFrom $599/moSeveral numbers working as one, after a demo

AI credits don't apply: only the hosted AI agent spends them, and a line your own code answers uses none. Your model and speech-to-text providers bill you directly. Compare plans on the ZeroSettle pricing table or get a number.

Frequently asked questions

Can I text the assistant from my Mac or Apple Watch?

Yes. It is an ordinary iMessage thread, so it works wherever you use Messages. Messages can start conversations from your phone number or an email address, so put every handle you send from on the allowlist.

Can the assistant see photos I send it?

Yes, once your script passes them on; the sample above skips them. A photo arrives as a kind: "image" row, and GET /api/imessage/messages/{id}/image returns the file with the same bearer token, HEIC converted to JPEG and a Live Photo as its still, ready for a vision model.

What happens when a stranger texts the number?

Nothing comes back, because the script answers only allowlisted handles. The texts are still stored on the line; DELETE /api/imessage/conversations/{handle} removes them without sending anything to the stranger.

How is this different from OpenClaw?

OpenClaw is an open-source assistant in its own right, and its iMessage channel runs free through Messages on a Mac you keep signed in. ZeroSettle is only the transport: for $49 a month the assistant gets its own number, your Mac stays out of it, and your script, on any always-on computer, still does the thinking.

Sources

  1. ZeroSettle iMessage API docs: "Name and photo", "Attachments", "Read messages", "Typing indicators", "AI agent" and "Errors, limits, and compliance". Checked October 8, 2026.
  2. iMessage, OpenClaw docs. Checked October 8, 2026.
  3. openclaw/openclaw, GitHub (MIT license). Checked October 8, 2026.
  4. Server Overview, BlueBubbles docs. Checked October 8, 2026.
  5. BlueBubblesApp/bluebubbles-server, GitHub (Apache-2.0 license). Checked October 8, 2026.
  6. If your iPhone sends text messages from your email address, Apple Support. Checked October 8, 2026.

Written by the ZeroSettle team. ZeroSettle is an iMessage API: a real phone number on real iMessage, running on Apple hardware we operate for you, that your code texts customers from and hears back on through a bearer token and signed webhooks.

Product facts on this page come from the ZeroSettle iMessage API docs and zerosettle.io as of October 8, 2026; plan limits and STOP were updated October 12, 2026. Nothing here is legal advice. Outside facts come from the pages listed under Sources, checked the same day.

Text your customers from your own iMessage number

ZeroSettle gives your business its own phone number on real iMessage, running on Apple hardware we operate for you in a server facility. Your code sends with one API call and hears every reply on a signed webhook.

Pro is $49 a month with a 30-day money-back guarantee. Growth, for several numbers working as one, is from $599/mo.