Skip to the page

Concepts

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.

  1. 1Your app calls POST /v1/messages (or Zapier, n8n, an AI agent).
  2. 2BlueBot queues it and applies your sending rules. A text that can’t go yet waits with a hold.
  3. 3Your Mac checks in every 10 seconds, claims the text and hands it to Messages.
  4. 4Messages sends it as an iMessage, or as an SMS through your iPhone.
  5. 5Replies are read from Messages on the Mac and sent to your app as webhooks.
How a text goes out and how a reply comes back.

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 canceled with cancel_reason: "opted_out", and nothing more would happen;
  • makes the text scheduled if it has a send_at in the future, or queued if 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.
  • imessage sends an iMessage and nothing else.
  • sms sends a regular text through Dana's iPhone, which needs Text Message Forwarding turned on.

Services and SMS fallback 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 sawWhat BlueBot does
Messages shows it sentThe text becomes sent, with service saying iMessage or SMS. message.sent fires.
An iMessage failed and never went, on an auto textBlueBot 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 failedThe text becomes failed with a failure.reason. message.failed fires.
It can't tellThe 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. Message states 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.

  1. 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; everything reads every conversation with a phone number or an email address. Short codes and business chats are never read.
  2. BlueBot stores it as a message with direction: "in" and state: "received", on Maria's contact and in her conversation.
  3. 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. Stop words
  4. Files. A picture or voice memo comes up from the Mac separately, usually within seconds.
  5. message.received fires 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 webhook 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 GET/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 knowListen for
A text wentmessage.sent
It reached their phonemessage.delivered (iMessage only)
They repliedmessage.received
It didn't gomessage.failed, message.expired, message.unconfirmed
They asked you to stopcontact.stopped
Your Mac went away or came backdevice.offline, device.online
Something on the Mac needs youdevice.check_failed

The event catalog 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. Data retention
  • 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. The Mac app lists everything it checks and how to fix each one.