Skip to main content
Let people text your agent on an iMessage line. The person writes first; your agent replies with the same code you use on Telegram.
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.
TypeScript

1. Get a line

  1. Sign in to the dashboard 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); 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.
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.

3. Tapbacks and effects

  • Reactions (POST .../reaction): ❤️ 👍 👎 😂 ‼️ ❓ show as tapbacks, and any other emoji as an emoji reaction. Groups take none (422 unsupported_content, channel_code group_unsupported).
  • Effects (POST .../effect) send text with an iMessage effect:
TypeScript
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.
TypeScript
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.

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.

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.
  • 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.

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.
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.
TypeScript
The imessage block works on iMessage only: on another channel it answers 422 unsupported_content with param imessage. See Send a basic message.

10. What iMessage supports

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.