Skip to the page

Sending and receiving

Change, cancel or settle a text

Change a scheduled text, cancel one that hasn’t gone, and settle an unconfirmed text the Mac couldn’t confirm, with which states allow which.

A text can be changed while it’s scheduled, canceled while it’s waiting, and settled by you when the Mac couldn’t tell whether it went. After it goes, it’s in the person’s Messages and nobody can take it back.

What each state allows

StateChange it (PATCH)Cancel itSettle it (resolve)
scheduledYesYesNo
queuedNoYesNo
sendingNoNo: the Mac has itNo
unconfirmedNoNoYes
canceledNoYes, and nothing changesNo
sent, delivered, read, failed, expiredNoNoNo

Asking for something a state doesn’t allow answers 409 conflict, and error.message names the state the text is in. Texts that came in (received) can’t be changed, canceled or settled. Message states

Change a scheduled text

PATCH/v1/messages/{id} changes the words, the time, the expiry or the metadata of a text that is still scheduled. Send only what changes:

PATCH/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0aChange a scheduled text →
curl -X PATCH "https://usebluebot.com/api/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hi Maria, your cleaning with Dr. Patel moved to Friday at 2 PM. Reply C to confirm.",
    "send_at": "2026-11-20T17:00:00-05:00"
  }'
FieldWhat it does
textNew words, up to 2,000 characters. null removes them, which only works on a text with files.
send_atA new time. Now or in the past sends the text at once: it becomes queued and message.queued fires.
expires_atA new expiry, after the text is due. null puts back the default from your settings. A new send_at with no expires_at moves the default expiry along with it.
metadataReplaces the whole metadata object, so send every key you want to keep.

You can’t change who a text goes to, its service or its files. Cancel it and send a new one instead.

The answer is the changed message. A scheduled text that becomes due fires message.queued; otherwise a change fires no event.

You gotIt meansDo next
200 with "state": "scheduled"Changed. It goes at the new send_at.Nothing.
200 with "state": "queued"The new send_at was now or past, so it’s going.Nothing, unless you meant a later time.
409 conflictIt isn’t scheduled any more.If it’s queued, cancel it and send the new words as a new text.
400 invalid_requestAn empty body, words over 2,000 characters, or an expiry before the text is due.Fix the field error.details names.
404 not_foundNo text with that id on your account, or a text that came in.Check the msg_ id.

Cancel a text

POST/v1/messages/{id}/cancel stops a text that is scheduled or queued:

POST/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a/cancelCancel a text →
curl -X POST "https://usebluebot.com/api/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a/cancel" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Idempotency-Key: cancel-msg_1Hq2LxW9cVbN3mKpRtYs0a"
Response200A canceled text
{
  "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "message",
  "state": "canceled",
  "cancel_reason": "requested",
  "canceled_at": "2026-11-13T19:42:00.000Z",
  "hold": null
}

The text never goes, and message.canceled fires. Canceling a text that is already canceled answers it again with nothing changed, so it’s safe to retry. A text in sending can’t be canceled: the Mac took it, usually within the last few seconds, and it is already on its way to Messages.

cancel_reason says who canceled a text:

cancel_reasonWho canceled it
requestedYou, through the API, the dashboard or an integration.
opted_outBlueBot, because the person is on your stop list.
sequence_stoppedBlueBot, because the sequence the text belonged to stopped, such as when the person replied.
broadcast_canceledYou, by canceling the broadcast it belonged to.
staffSomeone at BlueBot, such as support acting on your request.

To stop everything for a while rather than cancel texts one by one, pause sending instead. Paused texts wait with the paused hold and go when you resume, unless they expire first. Holds

Settle an unconfirmed text

When the Mac takes a text, it has 3 minutes to report what happened. Usually it reports sent within seconds. If those 3 minutes pass with no report, the text becomes unconfirmed and message.unconfirmed fires.

That happens when the Mac lost its connection, went to sleep or restarted partway, or when Messages still hadn’t shown the text as sent 30 seconds after the Mac handed it over. A regular text going through an iPhone that’s out of reach is the usual case. The text may have gone, or it may not have.

BlueBot never sends an unconfirmed text again on its own, because sending a text twice is worse than asking you. You settle it with POST/v1/messages/{id}/resolve:

  1. Look at the conversation in Messages on your Mac or iPhone.
  2. If the text is there and shows as sent, mark it sent.
  3. If it isn’t, or it shows “Not Delivered”, send it again.
POST/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a/resolveSettle an unconfirmed text →
curl -X POST "https://usebluebot.com/api/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a/resolve" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: settle-msg_1Hq2LxW9cVbN3mKpRtYs0a" \
  -d '{
    "action": "mark_sent"
  }'
actionWhat happens
mark_sentThe text becomes sent with "resolution": "marked_sent", and message.sent fires.
send_againBlueBot queues a new text with the same words, files, service and metadata. The original stays unconfirmed with "resolution": "resent", and resent_message_id names the new text, which goes like any other and fires its own events.
POST/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a/resolveSettle an unconfirmed text →
curl -X POST "https://usebluebot.com/api/v1/messages/msg_1Hq2LxW9cVbN3mKpRtYs0a/resolve" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: settle-msg_1Hq2LxW9cVbN3mKpRtYs0a-again" \
  -d '{
    "action": "send_again"
  }'
Response200Sent again
{
  "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "message",
  "state": "unconfirmed",
  "resolution": "resent",
  "resolved_at": "2026-11-13T19:50:00.000Z",
  "resent_message_id": "msg_2Bf8QzD4kJmA6nXwEuTr1c"
}

Settle a text once. Settling it again, or settling a text that isn’t unconfirmed, answers 409 conflict. Send an Idempotency-Key with send_again so a retry can’t queue two new texts.

If a late report comes in from the Mac saying the text went after all, BlueBot records that it was sent, so a text you haven’t settled yet may settle itself.

Find texts that need you

GET /v1/device counts them in unconfirmed_count, and BlueBot’s window on the Mac lists them under Needs you. To read them:

GET/v1/messagesList texts →
curl "https://usebluebot.com/api/v1/messages?state=unconfirmed" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY"

A program that sends many texts should listen for message.unconfirmed and either ask a person or follow a rule you choose. A reminder that has a later reminder coming can usually be marked sent. A one-time code or a payment link should be checked by a person.

You gotIt meansDo next
200 with "state": "sent" and "resolution": "marked_sent"Recorded as sent.Nothing.
200 with "resolution": "resent"A new text is queued.Follow resent_message_id for its state.
409 conflictIt was already settled, or it isn’t unconfirmed.Read the text to see its state.

What you can’t do

BlueBot can’t edit a text after it’s sent, unsend it, or send a tapback. Messages allows those only through private frameworks in macOS that need System Integrity Protection turned off, and BlueBot won’t ask that of anyone. You can still edit or unsend a text by hand in Messages on your Mac or iPhone, and BlueBot records the change with message.edited or message.unsent. Capabilities