Build an agentiMessage API5 min read
iMessage agent with Mastra
Run a Mastra agent on iMessage. Receive messages, stream the answer back as chat bubbles, keep per-person memory and receive webhooks in a Mastra server.

The short answer
Keep your Mastra agent, tools and workflows as they are. Call agent.stream for each incoming message and pass the stream to reply(). Use the Flow conversation ID as the memory thread and the contact ID as the resource, so each person keeps their own history.
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.
The code
This is the TypeScript example from Flow’s Mastra guide:
// npm install @flow-engineer/messaging @mastra/core @ai-sdk/openai
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";
import { FlowMessaging, contentText } from "@flow-engineer/messaging";
const flow = new FlowMessaging();
const agent = new Agent({
id: "bakery-assistant",
name: "Bakery assistant",
instructions: "You help customers of Asha's Bakery with orders and delivery. Keep answers short.",
model: openai("gpt-4.1-mini"),
});
for await (const event of flow.events.stream({ types: ["message.received"] })) {
const stream = await agent.stream(contentText(event.data.message.content));
await event.conversation.reply(stream); // reads stream.textStream
}
reply() reads the stream’s textStream, keeps the typing indicator on and sends a bubble at each paragraph break. On iMessage the bubbles are shorter, about 400 characters before a cut at a sentence end. Your agent’s tools and workflows stay in Mastra.
Get a test key with npx @flow-engineer/messaging init and chat with the agent on the Telegram sandbox. Send iMessage programmatically covers keys and retries.
Memory per person
An iMessage agent that forgets who you are feels broken. With Mastra memory, key it by the Flow identifiers:
- Use the Flow conversation ID as the thread ID.
- Use the contact ID,
conversation.contact, as the resource ID.
Each person then keeps their own history, and one person’s chat never leaks into another’s. An iMessage handle can be a phone number or an email address, so key by the contact ID, not by number.
Webhooks in a Mastra server
If you run a Mastra server, receive Flow webhooks on a custom route and call the same code. Verify the signature with the raw body:
const event = await flow.webhooks.constructEvent(rawBody, signatureHeader, secret);
// event.conversation is ready to reply on
constructEvent returns the typed event, with event.conversation ready to reply on. Register the URL with POST /v1/webhook_endpoints and keep the secret it returns. iMessage webhooks: signing, retries and ordering covers what to expect from deliveries.
Before you go live
- The line. Flow’s iMessage numbers are US numbers, shared or dedicated. Build on the Telegram sandbox with the same code, then move to iMessage.
- Test the agent. Use simulated contacts, then a real phone. See Test an iMessage agent without a phone.
- Design the tools. Read before write, confirm before you act. See Build an iMessage AI agent.
Other frameworks: Vercel AI SDK, OpenAI Agents SDK, Claude Agent SDK and LangChain.
Frequently asked questions
Can I run a Mastra agent on iMessage?
Yes. Flow's docs have a Mastra guide. You receive the message through Flow, run the Mastra agent and deliver its streamed answer as chat bubbles, keeping the agent's existing tools and workflows.
How does the reply stream work?
reply() reads the stream's textStream, keeps typing on and sends a bubble at each paragraph break.
How do I keep memory per person?
Use the Flow conversation ID as the Mastra thread ID and the contact ID, conversation.contact, as the resource ID. Each person then keeps their own history.
Can I receive webhooks in a Mastra server?
Yes. Add a custom route, verify the signature with flow.webhooks.constructEvent using the raw body, the Flow-Signature header and your secret, and use the typed event's conversation to reply.
Does the same agent work on other channels?
Yes. The messaging code is the same on each connected channel, so you can start on the Telegram sandbox and move to iMessage when a line is connected.
Can I trace the agent's turns?
Flow's docs describe sending traces from agent frameworks to Flow Observe, which shows each turn's model and tool calls and grades every reply.
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