> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bluereacher.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a group message

> Send one iMessage to a group thread by group_id, or create a new group and send the first message in a single call.

Send one message to an iMessage group thread. Pass `group_id` to post into a thread that already exists, or pass a `participants` array to create a new group and send its first message in the same call. Group threads are iMessage only. There is no SMS fallback for a group.

## Endpoint

```
POST https://api.bluereacher.com/functions/v1/send-group-message
```

| Item           | Value                                       |
| -------------- | ------------------------------------------- |
| Method         | `POST`                                      |
| Base URL       | `https://api.bluereacher.com/functions/v1/` |
| Path           | `send-group-message`                        |
| Content-Type   | `application/json`                          |
| Success status | `200 OK`                                    |

## Authentication

Every request needs your API key in the `Authorization` header as a bearer token. Keys start with `bluereacher_`.

```
Authorization: Bearer bluereacher_your_api_key
```

Keys are scoped to your workspace and can send from any active line in it. Keep keys server side. A key in browser or mobile client code can send from your lines.

## Two modes

Send exactly one of `group_id` or `participants`. Sending both returns `400 invalid_request`. Sending neither returns the same error.

| Mode                      | You send       | What happens                                                                                              |
| ------------------------- | -------------- | --------------------------------------------------------------------------------------------------------- |
| Send to an existing group | `group_id`     | The message posts into that thread. No membership changes.                                                |
| Create and send           | `participants` | BlueReacher creates the group, sends your message as the first one in it, and returns the new `group_id`. |

Store the `group_id` from the response. It is the only handle that ties your outbound group messages to the inbound replies that come back from that thread.

## Request fields

| Field          | Type                       | Required                         | Description                                                                                                                                                                                                                      |
| -------------- | -------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`         | string                     | Yes, unless `line_id` is set     | The dedicated iMessage line sending the message, in E.164 format, for example `+14155550142`.                                                                                                                                    |
| `line_id`      | string                     | Yes, unless `from` is set        | The line identifier from your dashboard, for example `line_01J8ZC4K2Q`. Use this instead of `from` if you rotate numbers.                                                                                                        |
| `group_id`     | string                     | Yes, if `participants` is absent | An existing thread, for example `grp_01J9F2Q7X4T9`. Must belong to the same line.                                                                                                                                                |
| `participants` | array of strings           | Yes, if `group_id` is absent     | 2 to 24 recipient addresses in E.164 format, or Apple ID email addresses. Your own line is added automatically.                                                                                                                  |
| `content`      | string                     | Yes, unless `media_url` is set   | Message body. Up to 4,000 characters.                                                                                                                                                                                            |
| `media_url`    | string or array of strings | No                               | One URL, or up to 5 URLs, each publicly reachable over HTTPS and up to 25 MB. Sent as attachments in the thread.                                                                                                                 |
| `group_name`   | string                     | No                               | Names the thread for every participant. Up to 64 characters. Accepted only when creating a group.                                                                                                                                |
| `channel`      | string                     | No                               | Only `"imessage"` is valid here. Passing `"sms"` or `"rcs"` returns `400 invalid_channel`. Omit it and the group defaults to `imessage`.                                                                                         |
| `external_id`  | string                     | No                               | Your own message reference, up to 64 characters. Doubles as the idempotency key: the same `external_id` inside 24 hours returns the original message instead of sending a second one. If you omit it, BlueReacher generates one. |
| `contact_id`   | string                     | No                               | A BlueReacher contact ID to attribute the send to in reporting. Does not change who receives the message.                                                                                                                        |

## Group limits

These are the documented defaults on every plan.

| Limit                          | Value                                                                |
| ------------------------------ | -------------------------------------------------------------------- |
| Participants per group         | 25, counting your line                                               |
| `participants` array on create | 2 to 24 addresses                                                    |
| Group name                     | 64 characters                                                        |
| Message body                   | 4,000 characters                                                     |
| Attachments per message        | 5, up to 25 MB each                                                  |
| New groups per line            | 25 per hour                                                          |
| Rate limit cost                | One group message counts as one send, whatever the participant count |
| Membership after creation      | Fixed. Members cannot be added or removed through the API.           |

Hit the group creation cap and the API returns `429 rate_limit_exceeded` with a `retry_after` value in seconds. Sends into existing groups keep working while that cap is in effect.

## Who you can add

Participants have to clear four checks before the group is created. The checks run on the whole array first, so a bad address means nothing is sent and no group exists.

* **iMessage-capable.** Every address must be reachable on iMessage. Run your list through the availability check endpoint before you build the array, and drop anything that comes back unavailable.
* **Not opted out.** Any address suppressed by a STOP keyword or a manual opt-out is rejected. A participant who replies STOP inside a group is suppressed across your workspace and excluded from future group sends.
* **Valid format.** E.164 phone numbers such as `+14155550188`, or Apple ID email addresses such as `sam@example.com`. Anything else is rejected.
* **Distinct.** Duplicates are collapsed to one entry. Your sending line is removed from the array if you include it, then added back as the sender, so it never eats one of your 24 slots.

Membership is set once, at creation. To message a different set of people, create a new group. If a participant leaves the thread on their own, the `group_id` stays valid and later sends reach whoever is left. If every participant leaves, sends to that `group_id` return `422 group_empty`.

## iMessage only, no SMS fallback

One-to-one sends through `/send-message` fall back to RCS, then SMS, when a number cannot receive iMessage. Group sends do not. A group thread is an iMessage object, so there is no green-bubble equivalent to fall back to and no per-participant downgrade.

What that means in practice:

* If any address in `participants` cannot receive iMessage, the request fails with `422 participant_not_imessage`, the response lists the failing addresses, and no group is created.
* The `channel` field on the response is always `imessage`.
* To reach a non-iMessage number, send it a one-to-one message through `/send-message` and let the fallback chain handle it.

Filter first, send second. Checking availability before you build the array turns a failed request into a clean group.

## Response fields

| Field           | Type                     | Description                                                                                                                                   |
| --------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_id`   | string                   | Message identifier. Yours if you supplied one, otherwise generated, for example `msg_01J9F2Q7XK4B`. Echoed on every webhook for this message. |
| `group_id`      | string                   | Thread identifier. Store it to send again or to match inbound replies.                                                                        |
| `group_created` | boolean                  | `true` when this call created the thread, `false` when it posted into an existing one.                                                        |
| `group_name`    | string or null           | Thread name, or `null` if you did not set one.                                                                                                |
| `from`          | string                   | The line that sent the message, in E.164 format.                                                                                              |
| `line_id`       | string                   | Identifier of that line.                                                                                                                      |
| `to`            | array of strings         | Every participant in the thread, excluding your line.                                                                                         |
| `channel`       | string                   | Always `imessage` for group sends.                                                                                                            |
| `content`       | string or null           | The message body as sent.                                                                                                                     |
| `media_url`     | array of strings or null | Attachments accepted for this message.                                                                                                        |
| `status`        | string                   | `queued`, `sent`, `delivered`, or `failed`. Fresh sends come back as `queued`.                                                                |
| `created_at`    | string                   | ISO 8601 timestamp in UTC.                                                                                                                    |

Group status is reported per message, not per participant. `delivered` means the thread accepted the message. Read receipts are not broken out by participant on group threads.

## Create a group and send

```bash theme={null}
curl -X POST https://api.bluereacher.com/functions/v1/send-group-message \
  -H "Authorization: Bearer bluereacher_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+14155550142",
    "participants": ["+14155550188", "+16505550119"],
    "group_name": "Acme rollout",
    "content": "Hey both, putting us in one thread so nothing gets crossed. Does Thursday 10am PT work for the walkthrough?",
    "external_id": "acme-rollout-intro-01"
  }'
```

Response:

```json theme={null}
{
  "external_id": "acme-rollout-intro-01",
  "group_id": "grp_01J9F2Q7X4T9",
  "group_created": true,
  "group_name": "Acme rollout",
  "from": "+14155550142",
  "line_id": "line_01J8ZC4K2Q",
  "to": ["+14155550188", "+16505550119"],
  "channel": "imessage",
  "content": "Hey both, putting us in one thread so nothing gets crossed. Does Thursday 10am PT work for the walkthrough?",
  "media_url": null,
  "status": "queued",
  "created_at": "2026-07-24T18:42:07Z"
}
```

## Send to an existing group

```bash theme={null}
curl -X POST https://api.bluereacher.com/functions/v1/send-group-message \
  -H "Authorization: Bearer bluereacher_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+14155550142",
    "group_id": "grp_01J9F2Q7X4T9",
    "content": "Sending the one-pager over now.",
    "media_url": "https://cdn.example.com/acme/one-pager.pdf"
  }'
```

Response:

```json theme={null}
{
  "external_id": "msg_01J9F30C6M2P",
  "group_id": "grp_01J9F2Q7X4T9",
  "group_created": false,
  "group_name": "Acme rollout",
  "from": "+14155550142",
  "line_id": "line_01J8ZC4K2Q",
  "to": ["+14155550188", "+16505550119"],
  "channel": "imessage",
  "content": "Sending the one-pager over now.",
  "media_url": ["https://cdn.example.com/acme/one-pager.pdf"],
  "status": "queued",
  "created_at": "2026-07-24T18:47:19Z"
}
```

## Keep the group\_id and reuse it

```javascript theme={null}
const BASE = "https://api.bluereacher.com/functions/v1/";
const headers = {
  Authorization: "Bearer bluereacher_your_api_key",
  "Content-Type": "application/json",
};

async function sendGroup(body) {
  const res = await fetch(`${BASE}send-group-message`, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
  return data;
}

// 1. Create the thread and send the opener.
const opener = await sendGroup({
  from: "+14155550142",
  participants: ["+14155550188", "+16505550119"],
  group_name: "Acme rollout",
  content: "Hey both, one thread for the rollout. Thursday 10am PT work?",
});

await db.deals.update(dealId, { group_id: opener.group_id });

// 2. Follow up later in the same thread.
await sendGroup({
  from: "+14155550142",
  group_id: opener.group_id,
  content: "Calendar invite is out, see you Thursday.",
});
```

## Replies and events

Inbound group replies POST to your webhook URL with the `group_id` attached, so you can thread them against the right deal. `from` is the individual who replied. `to` is your line.

```json theme={null}
{
  "external_id": "msg_01J9F31RB8QD",
  "group_id": "grp_01J9F2Q7X4T9",
  "from": "+16505550119",
  "to": "+14155550142",
  "contact_id": "con_01J8ZD9M7P",
  "channel": "imessage",
  "content": "Thursday works for me.",
  "created_at": "2026-07-24T18:51:33Z"
}
```

Status changes for outbound group messages arrive on the same webhook with the message `external_id` and the new `status`. A group message reports one status for the thread, so track state at the message level, not per participant.

## Errors

Errors return a JSON body with an `error` object. Nothing is sent when a request fails validation, including partial group creation.

```json theme={null}
{
  "error": {
    "code": "participant_not_imessage",
    "message": "2 participants cannot receive iMessage. No group was created and no message was sent.",
    "participants": ["+13125550164", "+13235550110"]
  }
}
```

| Status | Code                         | Meaning and fix                                                                                    |
| ------ | ---------------------------- | -------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`            | Malformed JSON, or both `group_id` and `participants` sent, or neither. Send exactly one.          |
| 400    | `invalid_channel`            | `channel` was set to something other than `imessage`. Groups are iMessage only.                    |
| 400    | `group_name_locked`          | `group_name` was sent with `group_id`. Names are set at creation.                                  |
| 400    | `missing_content`            | Neither `content` nor `media_url` was present. Send at least one.                                  |
| 401    | `invalid_api_key`            | Key missing, malformed, or revoked. Check the `Authorization` header.                              |
| 403    | `line_not_active`            | The line in `from` or `line_id` is not active on your workspace. Check the dashboard.              |
| 404    | `group_not_found`            | Unknown `group_id`, or it belongs to a different line. Send from the line that created the group.  |
| 413    | `media_too_large`            | An attachment is over 25 MB, or you sent more than 5.                                              |
| 422    | `participant_not_imessage`   | One or more addresses cannot receive iMessage. Failing addresses are listed in `participants`.     |
| 422    | `participant_opted_out`      | One or more addresses are suppressed. Remove them and resend.                                      |
| 422    | `too_many_participants`      | More than 24 addresses in `participants`. The thread cap is 25 including your line.                |
| 422    | `too_few_participants`       | Fewer than 2 addresses. Use `/send-message` for one-to-one.                                        |
| 422    | `invalid_participant_format` | An address is not E.164 or a valid Apple ID email.                                                 |
| 422    | `group_empty`                | Every participant has left the thread. Create a new group.                                         |
| 429    | `rate_limit_exceeded`        | Line send rate or the 25 new groups per hour cap. Retry after `retry_after` seconds.               |
| 500    | `internal_error`             | Send failed on our side. Retry with the same `external_id`, which is safe because it deduplicates. |

## Practical notes

* Group sends are for conversations with people who already know you, like a buyer and their colleague, or a rep, an AE, and a prospect. Cold group blasts read as spam and get reported as spam.
* Keep threads small. Three to five people holds a conversation. Twenty turns into noise, and one STOP from anyone in it suppresses that contact everywhere.
* Consent is per person. Every participant needs a lawful basis to be messaged, the same as any one-to-one send.
* Lines are dedicated iMessage lines, managed for you, so no A2P registration required for group iMessage traffic. Any SMS traffic you send on other paths still carries its own registration requirements.
