Products

Ships to

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.

Start hereiMessage API: a developer’s guideThe overview of how iMessage APIs work, and what to ask a provider. Build an agentBuild an iMessage AI agentWebhook in, model call, tools, reply out: the Flow loop, memory, testing and production safeguards.Build an agentiMessage agent with the Vercel AI SDKTake a Vercel AI SDK agent and answer on iMessage with streamed, chat-sized bubbles.Build an agentiMessage agent with the OpenAI Agents SDKReceive an iMessage, run an OpenAI Agents SDK agent and send the result back as bubbles.Build an agentiMessage agent with the Claude Agent SDKRun a Claude agent for each iMessage and deliver its streamed answer.Build an agentLangChain iMessage agentConnect a LangChain model or chain to an iMessage conversation.Build an agentiMessage API for coding agentsLet Claude Code, Cursor or Codex set up and test a messaging integration for you.

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