How BlueBot works
The whole path of a text from your app to the person's phone, and of their reply back to you, with what happens at each step and how you hear about it.
BlueBot has three parts you deal with: the API (and everything built on it, such as Zapier and MCP), a small app in your Mac's menu bar, and webhooks that tell you what happened. Your texts go out from Messages on your own Mac, so they come from your own number and appear in your existing conversations. BlueBot gives you no phone number and never sends your texts from anywhere but your Mac.
- Your app calls POST /v1/messages (or Zapier, n8n, an AI agent).
- BlueBot queues it and applies your sending rules. A text that can’t go yet waits with a hold.
- Your Mac checks in every 10 seconds, claims the text and hands it to Messages.
- Messages sends it as an iMessage, or as an SMS through your iPhone.
- Replies are read from Messages on the Mac and sent to your app as webhooks.
A text out
Dana runs a dog-grooming business. At 2:03 PM her booking system asks BlueBot to text Maria, a client, that Biscuit is ready for pickup. Here is everything that happens.
1. BlueBot accepts the text
Her system calls POST/v1/messages with Maria's number and the words. In the same request BlueBot:
- reads the number the way a person typed it and turns it into E.164 (
+15555550123); - finds Maria among Dana's contacts, or adds her;
- checks Dana's stop list. Had Maria texted STOP before, the text would be answered at once as
canceledwithcancel_reason: "opted_out", and nothing more would happen; - makes the text
scheduledif it has asend_atin the future, orqueuedif it's due now; - works out whether anything holds it right now (quiet hours, the Mac away, a daily limit, its turn in the pace) and writes that on the text as its
hold.
The answer is 201 with the message, and message.queued fires. The request is over in a fraction of a second, whether the text can go now or at 8:00 tomorrow morning. Nothing in the API waits for the Mac.
2. The Mac takes it
The BlueBot app on Dana's Mac checks in every 10 seconds. Each answer says how many texts are ready, and the Mac claims them: up to five at a time, each under a 3-minute lease. At a slow pace it claims fewer, so the last one still goes before its lease runs out. A claimed text becomes sending.
If the Mac hasn't checked in for 2 minutes, it's away. Texts then wait with hold.reason: "device_offline" and go when it comes back, unless they expire first.
3. Messages sends it
For each text, the Mac hands the words to the Messages app through AppleScript, the same way a person would send them, and waits your pace (20 seconds by default) before the next. Pictures and files are downloaded first through links that last 10 minutes, then sent after the words.
The service is chosen like this:
auto(the default) uses whatever the newest chat with Maria used, and iMessage when there's no chat yet.imessagesends an iMessage and nothing else.smssends a regular text through Dana's iPhone, which needs Text Message Forwarding turned on.
says how each one behaves.
4. The Mac reads it back
The Mac counts a text as sent only when Messages shows it. It reads its own text back from Messages' database for up to 30 seconds and reports one of these:
| What the Mac saw | What BlueBot does |
|---|---|
| Messages shows it sent | The text becomes sent, with service saying iMessage or SMS. message.sent fires. |
An iMessage failed and never went, on an auto text | BlueBot queues it once more as a regular text through the iPhone (sms_retry: true). It keeps its place in today's counts. |
| Messages refused it, or an iMessage-only text failed | The text becomes failed with a failure.reason. message.failed fires. |
| It can't tell | The Mac says nothing. When the lease runs out the text becomes unconfirmed and message.unconfirmed fires. It may have gone, so BlueBot never sends it again on its own. |
Most texts go in a few seconds. The last case is rare and is the price of never sending anyone the same text twice. covers each state and how to settle an unconfirmed text.
5. Delivered and read
For seven days after a text goes, the Mac watches it in Messages. When Messages records it delivered, and later read, the next check-in says so: the text becomes delivered, then read, and message.delivered and message.read fire. Only iMessage reports delivery, and only people who share read receipts report reading. A text that went as SMS stays sent.
A reply in
Maria texts back, "On my way!" Here is that path.
- The Mac reads it. On its next check-in the Mac reads new messages from Messages' database (it never changes that database) and posts them to BlueBot. Which conversations it reads is your inbound mode:
replies(the default) reads only conversations with people you've texted through BlueBot, and group chats where everyone else is such a person;everythingreads every conversation with a phone number or an email address. Short codes and business chats are never read. - BlueBot stores it as a message with
direction: "in"andstate: "received", on Maria's contact and in her conversation. - Stop words. If the whole reply is a stop word such as STOP, Maria goes on the stop list and every text still waiting for her is canceled.
- Files. A picture or voice memo comes up from the Mac separately, usually within seconds.
message.receivedfires once the files are up, or after 2 minutes without them, so your webhook can always fetch what the message names.
The Mac copies both directions of a conversation. A text Dana types herself on her iPhone, in a conversation BlueBot reads, also arrives, as direction: "out" with source: "mac", and fires message.sent. Tapbacks, edits and unsends Messages records come through too, as reaction.added, reaction.removed, message.edited and message.unsent. So your app, your CRM or your AI assistant sees the whole conversation, including texts it didn't send.
When you first connect a Mac, it also copies those conversations from the 7 days before.
How you hear about it
Every step above fires an event. A posts each one to your server, signed, within seconds, and retries for about 72 hours if your server doesn't answer. The same events are in /v1/events for 30 days, so a program that was down can catch up. Each event carries the message as the API returns it, with the metadata you sent, so you can match it to your own record without another request.
| You want to know | Listen for |
|---|---|
| A text went | message.sent |
| It reached their phone | message.delivered (iMessage only) |
| They replied | message.received |
| It didn't go | message.failed, message.expired, message.unconfirmed |
| They asked you to stop | contact.stopped |
| Your Mac went away or came back | device.offline, device.online |
| Something on the Mac needs you | device.check_failed |
The has every event with an example.
Where things are kept
- On BlueBot's servers: the queue, your sending rules, your contacts and stop list, and every message. The words and files of a message are deleted after 30 days; its facts (who, when, its state and service) stay so your history and counts stay right.
- On your Mac: the app's sign-in token, in the Keychain, and the files of texts waiting to send, which it deletes once each text is reported, and within an hour otherwise. The app never writes to Messages' database and never changes anything in Messages but sending.
- In your logs and analytics: never the words of a text. BlueBot keeps them out of its own logs, analytics and error reports too.
What it needs from you
BlueBot can only send while your Mac is on, awake, online and signed in to Messages. That's the trade for using your own number with no monthly line fee. A Mac mini on a shelf is the easiest way to meet it; a MacBook works if it stays plugged in and open. lists everything it checks and how to fix each one.