Build an agentiMessage API7 min read
Test an iMessage agent without a phone
Test a Flow iMessage AI agent with no phone or live line. Use simulated contacts, transcripts and graded test chats that preserve the real channel rules.

The short answer
Flow lets you test an iMessage agent without a phone. A test key creates a simulated contact that writes like a real person, so you can exercise agent logic and edge cases before you ever put a live line in front of customers.
Building on the iMessage API? Build on iMessage with Flow: one agent for iMessage, WhatsApp and Telegram. New to it? Read the iMessage API guide.
Why simulate
A phone is a slow test harness. You cannot repeat a conversation at 2 am, play the same awkward user fifty times, or run it in CI. Conversations also break in ways unit tests miss: a person changes their mind mid-sentence, sends a photo instead of text, or comes back a day later.
So test the agent with people who exist only in software. Build an iMessage AI agent covers the agent design. This post covers how to test it.
Simulated contacts on a test key
Flow’s testing API is available on test keys only. Three calls cover the loop:
POST /v1/test/conversationscreates a conversation with a simulated contact, an end user that exists only inside Flow, so an agent or a test suite can play the person’s side with no phone and no real channel.POST /v1/test/conversations/{conversation_id}/inboundposts one turn from the contact. It goes through the same inbound path as a real channel: Flow stores it, then delivers it as amessage.receivedevent by webhook, on the stream and in the event log, in order with the conversation’s other events.GET /v1/test/conversations/{conversation_id}/transcriptlists the messages both ways, oldest first, so a test can see what your agent sent back.
What the simulated contact can send: text, media, voice, location, contact_card or button_reply. A numbered answer such as 2 to buttons sent as numbered text comes back as a button_reply, as on a real channel. Your agent receives each turn exactly like a real message.received and replies with its normal send.
The channel rules still hold
A simulation that ignores the rules teaches you nothing. Flow’s simulated senders keep them:
- iMessage. A simulated iMessage line only replies, so the contact writes first. Buttons need a
fallback, and the contact answers with the button’s number. - WhatsApp. Each contact turn opens the 24-hour window. Outside it, including before the first turn, only a
templatecan be sent, and a free-form send answers409 outside_window. Edits and unsends answer422 unsupported_content. - Telegram. A simulated bot, like a real one, cannot write to a contact who has not written to it, so a send before the first turn fails as it does on real Telegram.
- SMS. A start needs the contact’s recorded consent. A turn with the text STOP opts the contact out and START lifts it.
Sends to the simulated contact pass the real send gate and end sent. The sender never touches a network. If your agent tries something a channel does not allow, you find out in a test, not in front of a customer.
Let your coding agent drive it
If your coding agent has Flow’s MCP server, a test key gives it these tools: sandbox_join, send_test_message, simulate_inbound, get_test_transcript, wait_for_event, list_events, get_webhook_deliveries and replay_event. It can play a user, wait for your agent’s reply and read the transcript. After a test send, it should wait for message.sent or message.failed, not message.delivered.
The MCP server is an optional development tool that the project owner adds. See iMessage MCP server and iMessage API for coding agents.
Grade whole conversations
Single replies are only part of it. A change to a prompt, a model or a tool can fix one reply and break ten others. Flow’s flow test, in early access, runs test chats against your own agent, sends the transcripts to Observe, and fails the build when a reply breaks a rule or misses what the test expects. You write the chats in .flow/tests.yaml:
version: 1
channel: sms
cases:
- id: no-delivery-promise
name: Doesn't promise a date it can't confirm
turns:
- user: "Can you get my order to me by Sunday?"
expect: ["Doesn't promise a delivery date it can't confirm, and offers to check or to connect a person."]
must_not: ["guaranteed"]
- user: "ok thanks"
expect holds plain sentences a grader checks. includes and must_not are exact text, case-insensitive. Test chats are data you write, so never put a real person’s conversation in a test file. Access to the runner is through the Flow team while it is in early access.
What simulation does not cover
Simulation tests your logic and the channel’s rules. It does not test the real iMessage experience: how bubbles look, how typing feels, whether a line is throttled, or how Apple delivers a message. Before launch:
- Ask the Flow team to connect an iMessage line, and send real messages from a real phone.
- Check typing, tapbacks and bubble length on the device.
- Watch
message.failedandsender.status_changedfor the first days.
Then use the simulated tests as your regression suite. For the full path from sandbox to live, see Send iMessage programmatically.
Frequently asked questions
Can I test an iMessage agent without an iPhone?
Yes, for your logic. On Flow, a test key can create a simulated contact on a simulated iMessage line, so your agent receives turns like a real message.received event with no phone or real channel.
Which channels can be simulated?
Flow's docs list telegram, whatsapp, imessage and sms as simulated channels. A simulated sender never touches a network.
Do the real channel rules apply in a simulation?
Yes. A simulated iMessage line only replies, so the contact has to write first, as on a shared line. A simulated WhatsApp contact opens the 24-hour window with each turn, and outside it only a template can be sent.
How do I read what my agent said?
Read both sides of the simulated conversation, oldest first, with GET /v1/test/conversations/{conversation_id}/transcript. Each sent message shows what the simulated contact saw.
Can a coding agent run these tests?
Yes. With a test key, Flow's MCP server gives an agent tools such as simulate_inbound and get_test_transcript, and wait_for_event to wait for a result.
Is simulation enough before I launch?
No. It covers your agent's logic and the channel's rules, not the real phone experience. Before launch, send real messages from a real phone on a real line.
Keep reading
All posts: the Flow blog. The Flow docs have the reference.
From reading to building
Your agent, on iMessage and everywhere else.
Flow gives your agent an iMessage line, and the same code answers on WhatsApp and Telegram and works as an MCP server and CLI. See how it works on iMessage.
Build on iMessage with Flow