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

# iMessage

> Receive and answer iMessage, one-to-one and in group chats, on Flow's shared line or a dedicated line.

Let people text your agent on an iMessage line. The person writes first; your agent replies with the same code you use on Telegram.

> [!NOTE]
>
> There are two kinds of line. **Flow's shared line** is free to try: your first sign-in at flow\.engineer opens it to your account, and people reach your app by texting its join message. A **dedicated line** is your own number, set up with the Flow team; only dedicated lines take group chats, call forwarding and, on request, writing first. Lines reply after the person writes, unless writing first is switched on for a dedicated line.

```ts TypeScript
// npm install @flow-engineer/messaging
import { FlowMessaging, contentText } from "@flow-engineer/messaging";
import { askMyAgent } from "./agent"; // returns a string, or any LLM stream

const flow = new FlowMessaging();

for await (const event of flow.events.stream({ types: ["message.received"] })) {
  const text = contentText(event.data.message.content);
  if (event.conversation.type !== "group") {
    await event.conversation.react(event.data.message.id, "👍"); // shows as the Like tapback; groups take no reactions
  }
  await event.conversation.reply(await askMyAgent(text));
}
```

## 1. Get a line

**Flow's shared line (free)**

1. Sign in to the [dashboard](https://api.flow.engineer/admin) with your Flow account. Your first sign-in (or the first key you make there) starts the account's free trial, which opens Flow's shared iMessage line to every app of the account, with test and live keys. The Flow CLI's `npx @flow-engineer/flow setup` starts the same trial.
2. `GET /v0/senders?channel=imessage` lists the line (`kind` `shared`) with your app's `join_code`, the message a person sends to join your app. `address.link`, when set, opens Messages with the line and that message filled in.
3. Run the code above, send the join message to the line from an iPhone or Mac, and your agent answers. A person's messages go to your app until they send another app's join message.

The free trial has room for 25 people per channel in total (see [Pricing](https://flow.engineer/docs/unified-messaging-api/pricing.md)); an account that moves to a paid plan keeps the line, with that plan's limits. The shared line ignores group chats and never writes first.

**A dedicated line**

1. Sign in to the [dashboard](https://api.flow.engineer/admin) with a Flow account and make a live key (`fk_live_...`) on the [Keys page](https://api.flow.engineer/admin/keys?mode=live). See [Going live](https://flow.engineer/docs/unified-messaging-api/guides/going-live.md).
2. Ask the Flow team for a dedicated line on the Dedicated plan. Lines are not made through the API: `POST /v0/senders` with `"channel": "imessage"` answers `501 not_implemented`.
3. With the live key, `GET /v0/senders?channel=imessage` lists the line. Its `address.link`, when set, is the line's opt-in link: it opens Messages with the line and a prefilled text the person sends to start.
4. Run the code above, text the line from an iPhone or Mac, and your agent answers.

What a line can do beyond replying is on the sender, in `imessage` (set by the Flow team): `fallback` (the SMS/RCS add-on, section 6), `call_forwarding` (section 8) and `can_initiate` (writing first, section 5).

## 2. Bubbles fit iMessage

People read iMessage as short lines. `reply()` uses shorter bubbles here (about 400 characters before it cuts at a sentence end) than on Telegram. See [Streaming replies](https://flow.engineer/docs/unified-messaging-api/concepts/streaming-replies.md).

## 3. Tapbacks and effects

* **Reactions** ([`POST .../reaction`](https://flow.engineer/docs/api-reference/messages/react-to-a-message)): ❤️ 👍 👎 😂 ‼️ ❓ show as tapbacks, and any other emoji as an emoji reaction. Groups take none (`422 unsupported_content`, `channel_code` `group_unsupported`).
* **Effects** ([`POST .../effect`](https://flow.engineer/docs/api-reference/messages/send-text-with-an-imessage-effect)) send text with an iMessage effect:

```ts TypeScript
import { effect } from "@flow-engineer/messaging";

await event.conversation.send(effect("Your order is confirmed!", "confetti"));
```

Effects are `slam`, `loud`, `gentle`, `invisible_ink`, `echo`, `spotlight`, `balloons`, `confetti`, `love`, `lasers`, `fireworks` and `celebration`. Which ones a contact's device shows may vary, and a message that goes by SMS or RCS shows the text without the effect. Effects work on iMessage only: send the same text as a basic message on other channels.

## 4. No buttons: list the choices in the text

iMessage has no buttons, lists or carousels: they answer `422 unsupported_content` with `param` `kind`, and nothing is sent. Flow converts nothing, so send a basic message whose text lists the choices, and read the person's answer, which arrives in words as a `basic` message.

```ts TypeScript
await event.conversation.send("Which time works, 10:00 or 14:00?", { idempotencyKey: `${event.id}:time` });
```

If your code sends `buttons` on every channel, catch the `422` and send the text instead with a new `Idempotency-Key`: a `422` is kept under its key, so add what the substitute is, such as `evt_123:time:basic`. See [`unsupported_content`](https://flow.engineer/docs/unified-messaging-api/errors/unsupported_content).

## 5. Who writes first

iMessage lines are personal-style numbers, and iMessage watches closely for unwanted messages. Flow protects your line:

* **The person writes first.** A line replies only, unless writing first is switched on for it (`imessage.can_initiate`). From a reply-only line, starting a conversation with a new contact (`POST /v0/messages`) is refused with `403 permission`. Someone who has written to the line before is an open conversation, and replies to them are not budgeted.
* **Writing first is available on request**, for dedicated lines only. The Flow team switches it on per line, within a warm-up: the line's new-contact budget (`limits`) starts small and grows. If the channel still refuses a start, the send ends in `message.failed` with `outside_window`. The shared line never writes first.
* **Get people to write with the opt-in link.** When the line has one, the sender's `address.link` opens Messages with the line and a prefilled text the person sends to start. Put it on your site, receipts or a QR code.
* **No cold outreach.** Sending the same text to many people, or messages that get no reply, can throttle the line (`sender_throttled`).

See [The send gate](https://flow.engineer/docs/unified-messaging-api/concepts/send-gate.md).

## 6. SMS and RCS from the same number

With the SMS/RCS add-on (`imessage.fallback` on the sender), a message to someone who cannot get iMessage goes by SMS or RCS from the same number. It is included on Flow's shared line, and is an add-on for a dedicated line, arranged with the Flow team. Without it, a message to someone not on iMessage fails (`message.failed`).

Your app never picks the network: it is chosen per message when it is sent. `Message.network` says which way a message went: `imessage`, `sms` or `rcs`. It is set on inbound messages, and on yours once the channel reports it (`data.message.network` in `message.delivered` or `message.failed`).

The network is chosen after Flow sends, so Flow sends every message as for iMessage: a message that goes by SMS or RCS shows no `imessage.styles`, no `imessage.subject` and no effect. `Message.network` tells which way it went. Keep text readable without them when the add-on is on. Edits and unsends of a message that went by SMS or RCS are refused with `422 unsupported_content`, `channel_code` `not_on_sms`: send a new message instead.

## 7. Group chats

Only dedicated lines take group chats, and only ones people add the line to. Flow's shared line ignores group chats: they never reach your app from it.

* **A group is one conversation** with `type` `group`. It has no `contact`; `group` holds the group's `name`, when it has one, and its `participants` (each a contact on the channel, made the first time Flow sees them). Every inbound message carries `from`, the contact who wrote it.
* **People who join or leave change the list without an event.** Read the conversation (`GET /v0/conversations/{conversation_id}`) for the current list; events inline only `group_name`.
* **Writers count toward your plan's people**; members who never write do not. A writer past a limit is held: their messages wait and arrive when they are let in, and the rest of the group's messages are not held up. See [Pricing](https://flow.engineer/docs/unified-messaging-api/pricing.md#past-a-limit).
* **Reply with the normal sends** (`POST /v0/conversations/{conversation_id}/basic` and the other kinds' endpoints): basic messages (with `imessage.styles`, `imessage.subject` and `link_preview`), media, voice notes, effects, threaded replies (`reply_to`), edits and unsends work in groups.
* **No reactions, typing or read receipts from your line.** Each answers `422 unsupported_content` with `param` `kind` and `channel_code` `group_unsupported`, and nothing is sent: leave them out in groups.
* **Flow cannot create a group, rename one, or add or remove people.** A start (`POST /v0/messages`) always makes a direct conversation.

Test groups before you have a line: `POST /v0/test/conversations` with `"channel": "imessage"` and a `group` (`name`, `participants`) makes a simulated group conversation under the same rules. See [Test and live mode](https://flow.engineer/docs/unified-messaging-api/concepts/test-and-live-mode.md).

## 8. Calls

* **Phone calls** to a dedicated line's number can be forwarded to a phone you choose, set up with the Flow team with the SMS/RCS add-on (`imessage.call_forwarding.phone` shows where). Forwarded calls send no event. The shared line does not forward calls.
* **FaceTime calls** are never forwarded: the line cannot answer them, and each attempt arrives as a `call.received` event (`data.call.contact`, `data.call.network` `facetime`) in the direct conversation with the caller, made if it is new. A webhook endpoint receives it only when it lists `call.received` in `events`.
* A call is not a message: it does not count toward your plan's people, and it does not let a reply-only line write to the caller first.

## 9. Text styles, subjects and link previews

`text` is sent exactly as written: Flow reads no markdown, and text that looks like markup shows as written. Style a basic message with `imessage.styles`, ranges of the text (`start` and `length` in UTF-16 code units) with a `style`: `bold`, `italic`, `underline`, `strikethrough`, `big`, `small`, or the animations `shake`, `nod`, `explode`, `ripple`, `bloom` and `jitter`. `imessage.subject` (up to 200 characters) shows as the message's bold subject line, and `link_preview: false` sends links without a rich preview.

```ts TypeScript
await flow.messages.basic(event.conversation.id, {
  text: "Your order is on its way. Track it: https://example.com/t/4821",
  imessage: { subject: "Order 4821", styles: [{ start: 14, length: 10, style: "big" }] },
  link_preview: false,
});
```

The `imessage` block works on iMessage only: on another channel it answers `422 unsupported_content` with `param` `imessage`. See [Send a basic message](https://flow.engineer/docs/api-reference/messages/send-a-basic-message-text-or-media-with-a-caption.md).

## 10. What iMessage supports

| Kind                                 | iMessage                                                                                                                                                                                                     |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Text (`basic`)                       | yes, up to 9999 characters, sent as written; styles and a subject through the `imessage` block (section 9)                                                                                                   |
| Images, video, audio                 | yes                                                                                                                                                                                                          |
| Documents                            | yes, sent and received; malware-scanned before Flow keeps them (an upload that fails answers `422 file_blocked`)                                                                                             |
| Reactions                            | yes: tapbacks for ❤️ 👍 👎 😂 ‼️ ❓, emoji reactions for others; not in groups                                                                                                                                |
| Typing                               | yes, within 5 minutes of the contact's last message (otherwise `409 outside_window`, safe to ignore); not in groups (`422`, `group_unsupported`)                                                             |
| Read receipts                        | yes, for the whole conversation (`up_to` answers `422 unsupported_content`); not in groups (`422`, `group_unsupported`)                                                                                      |
| Effects                              | yes (may be lost on a message that goes by SMS or RCS)                                                                                                                                                       |
| Buttons, lists, carousels, templates | no (`422 unsupported_content`): list the choices in a basic message's text                                                                                                                                   |
| Edit                                 | yes, within 15 minutes of sending (otherwise `422 unsupported_content`, `edit_window_closed`); not the message that started the conversation (`first_message_not_editable`), nor one that went by SMS or RCS |
| Unsend                               | yes, within 2 minutes of sending (otherwise `422 unsupported_content`, `unsend_window_closed`); not one that went by SMS or RCS                                                                              |
| Voice notes                          | yes, from an mp3, wav, m4a, caf or aac file                                                                                                                                                                  |
| Locations, contact cards             | no (`422 unsupported_content`): send a basic message with a maps link, or the name, numbers and email addresses                                                                                              |

Each endpoint's page opens with the channels it works on and their limits.

## 11. Contacts

An iMessage contact's `address.handle` is a phone number **or an email address**. Do not assume a phone number. Reply into the conversation the person started rather than addressing them by handle.

## Related

* [Going live](https://flow.engineer/docs/unified-messaging-api/guides/going-live.md): live keys and iMessage lines.
* [Pricing](https://flow.engineer/docs/unified-messaging-api/pricing.md): plans and how many people each reaches.
* [Content types](https://flow.engineer/docs/unified-messaging-api/concepts/content-types.md)
