# Holds

> Why a text that can't go yet is accepted and waits instead of failing, every reason it can wait, the order they're checked in, and when each one clears.

A text that can't go right now isn't refused. BlueBot accepts it, answers `201`, and puts a `hold` on it that says why it's waiting and, when it knows, until when. When the hold clears, the text goes by itself. Your code never needs to retry a send because of quiet hours, a sleeping Mac or a daily limit.

## What a hold looks like

Every outgoing message has a `hold` field. It's `null` when nothing holds the text, and an object when something does:

The answer, `201`:

```json
{
  "id": "msg_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "message",
  "state": "queued",
  "to": "+15555550123",
  "hold": {
    "reason": "quiet_hours",
    "until": "2026-11-14T14:00:00.000Z",
    "description": "Waiting for quiet hours to end at 8:00 AM tomorrow."
  },
  "expires_at": "2026-11-15T04:40:00.000Z"
}
```

| Field         | What it is                                                                                                                                                                                      |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reason`      | Why it waits, as a stable code. Program against this.                                                                                                                                           |
| `until`       | When it should stop waiting, when that's known: the end of quiet hours, the next midnight, or the text's estimated slot in the pace. `null` when nobody can say, such as while the Mac is away. |
| `description` | The hold in words, in your time zone, ready to show a person.                                                                                                                                   |

A hold is only ever on a `queued` text. A `scheduled` text has none until it's due.

## Every reason

| `hold.reason`            | What it means                                                                                                                           | When it clears                                                                                                 |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `account_held`           | BlueBot’s operator paused this account’s sending. Nothing sends.                                                                        | Only BlueBot, after you write to support. Meanwhile the API still accepts sends, and they wait with this hold. |
| `platform_stopped`       | BlueBot stopped all sending for every account, for an incident.                                                                         | BlueBot turning sending back on. Texts go in their order, subject to their expiry.                             |
| `plan_lapsed`            | The account’s plan isn’t active.                                                                                                        | An active plan again (update the card on the Billing page).                                                    |
| `paused`                 | You paused sending from the Mac’s menu or the dashboard.                                                                                | Resuming, or the pause’s own end (`until`): an hour, or 8:00 AM in your zone.                                  |
| `device_offline`         | Your Mac hasn’t checked in for 2 minutes: asleep, off, or off the internet.                                                             | The Mac checking in again. If it stays away past the text’s `expires_at`, the text expires.                    |
| `quiet_hours`            | It’s inside your quiet hours (9:00 PM to 8:00 AM in your zone unless you changed them).                                                 | `until`: the end of quiet hours. The text then takes its place in the pace.                                    |
| `daily_new_people_limit` | You’ve texted as many new people today as your setting allows (50 by default). People you already have a conversation with don’t count. | `until`: the next midnight in your zone, or raising the setting.                                               |
| `platform_daily_limit`   | The account sent 1,000 texts today, BlueBot’s ceiling for one account.                                                                  | `until`: the next midnight in your zone. This one isn’t a setting.                                             |
| `pacing`                 | Waiting its turn: the Mac leaves your pace (20 seconds by default) between texts.                                                       | `until`: its estimated slot. It moves as texts ahead of it go or are canceled.                                 |

## The order they're checked

BlueBot checks the reasons in a fixed order and shows the first that applies, so a text has one hold at a time. The first six apply to the whole account, so every queued text shares them:

1. `account_held`
2. `platform_stopped`
3. `plan_lapsed`
4. `paused`
5. `device_offline`
6. `quiet_hours`

The last three apply text by text, in the order the texts were queued:

7. `daily_new_people_limit`, only for a text to someone you've never texted with through BlueBot
8. `platform_daily_limit`
9. `pacing`

When one hold clears, the next may show. A text queued at 11:00 PM waits on `quiet_hours`; at 8:00 AM, if twenty texts went in ahead of it, it waits on `pacing` for a few minutes more, then goes. A text held by a daily limit doesn't use up a place in the pace, so texts behind it that can go still go.

Holds are worked out again every time anything changes: a send, a cancel, a settings change, the Mac checking in or going away. Raise `new_people_per_day` and the texts waiting on it are ready on the Mac's next check-in.

## What a hold doesn't change

- **Expiry still runs.** A held text that's still waiting at its `expires_at` (24 hours after it was due, by default) becomes `expired` and never goes. Choose `expires_at` for texts that only make sense soon, and longer for ones that can wait. [Limits](https://usebluebot.com/docs/limits)
- **You can still cancel it.** A held text is `queued`, and `POST /v1/messages/{id}/cancel` cancels it.
- **The stop list isn't a hold.** A text to someone on your stop list doesn't wait; it's `canceled` at once with `cancel_reason: "opted_out"`. [Stop words](https://usebluebot.com/docs/concepts/stop-words)

## New sends while the account is held

Most holds let new sends in: they're accepted and wait with everything else. Two refuse new work instead, because nothing will move until a person acts:

- **A lapsed plan** answers new sends, uploads and webhook creation with [`plan_lapsed`](https://usebluebot.com/docs/errors/plan_lapsed) (402). Texts already queued wait with `hold.reason: "plan_lapsed"` and go once the plan is active again, if they haven't expired. Reading, canceling and changing settings keep working.
- **BlueBot pausing your account** shows as `account_held` on every queued text. Write to [support@usebluebot.com](mailto:support@usebluebot.com); a person at BlueBot releases it.

## Don't retry a held text

The answer to a held send is a success. Sending the same text again makes a second text with its own hold, and both go when the hold clears. If your code sends in a loop, send each text once with an `Idempotency-Key`, and let the holds do the waiting. [Idempotency](https://usebluebot.com/docs/concepts/idempotency)

## Find what's held

- `GET /v1/device` counts them: `held_count` is how many queued texts are waiting on a hold, and `queued_count` is every queued text.
- `GET /v1/messages?state=queued` lists them, each with its `hold`.
- `message.queued` fires once when a text is queued and carries the hold it had at that moment. A hold changing doesn't fire an event. Listen for `message.sent`, `message.expired` and `message.canceled` to learn how each one ends.

```bash
curl "https://usebluebot.com/api/v1/messages?state=queued&limit=50" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY"
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const page = await client.messages.list({
  state: 'queued',
  limit: 50,
});
console.log(page.data);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

page = client.messages.list(state="queued", limit=50)
print(page.data)
```

| You got                                       | It means                                                                       | Do next                                                                                                                        |
| --------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Every text with the same `hold.reason`        | An account-wide hold: the Mac is away, it's quiet hours, or sending is paused. | Fix the cause, or wait for `hold.until`.                                                                                       |
| `pacing` with times spread over the next hour | A long line at your pace.                                                      | Nothing; they go in order. Lower `pace_seconds` only if you understand the risk. [Sending rules](https://usebluebot.com/docs/concepts/sending-rules) |
| `daily_new_people_limit` on some texts        | You've texted as many new people today as your setting allows.                 | Wait for midnight, or raise `new_people_per_day`.                                                                              |
| `device_offline` with `until: null`           | The Mac hasn't checked in for 2 minutes.                                       | Wake it, or check its power and network. [The Mac app](https://usebluebot.com/docs/concepts/mac-app)                                                 |

## Tell people why

If your app shows what it sent, show `hold.description`. It's written for a person and set in your account's time zone: "Waiting for quiet hours to end at 8:00 AM tomorrow.", "Waiting for your Mac to come back online.", "Waiting its turn. It should go around 2:41 PM." The dashboard, the Mac's menu and BlueBot's MCP tools all use the same words.
