Source: https://www.agentweb.us/guides/photon-agentweb-starter/

# How do I build an iMessage agent that actually does things?

By Andrey Marey, Founder of AgentWeb · As of Oct 20, 2026

> **Short answer.** Use Photon's `spectrum-ts` SDK to receive and send iMessages, and AgentWeb to run the web actions. In the message loop, your model turns a text into a task. Your code calls AgentWeb's `/api/execute` with a domain, an action and params, and sends the result back into the same conversation. Anything paid or public waits for the user's yes.

## Steps

1. Run `npm install spectrum-ts` and set `SPECTRUM_PROJECT_ID` and `SPECTRUM_PROJECT_SECRET` from your Photon dashboard.
2. Get an AgentWeb `aw_` key and set `AGENTWEB_API_KEY`.
3. Write a `decide()` function with your own model that returns either a reply or an action (`domain`, `action`, `params`). Use AgentWeb's `list_actions` to give the model the real action names.
4. Call AgentWeb for actions, and send the result back with `space.send`.
5. For paid or public actions, ask first and only run them after a yes.

## The code

```ts
import { Spectrum } from "spectrum-ts";
import { imessage } from "spectrum-ts/providers/imessage";
import { decide } from "./decide"; // your model: returns { reply } or { domain, action, params, needsYes }

async function agentweb(domain: string, action: string, params: object, key: string) {
  const res = await fetch("https://mcp.agentweb.us/api/execute", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.AGENTWEB_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": key,
    },
    body: JSON.stringify({ domain, action, params }),
  });
  return res.json(); // { ok, result, credits_charged, balance_remaining, support_id }
}

const app = await Spectrum({ providers: [imessage.config()] });

for await (const [space, message] of app.messages) {
  if (message.content.type !== "text") continue;
  const step = await decide(message.content.text);
  if (step.reply) { await space.send(step.reply); continue; }
  if (step.needsYes) { await space.send(step.confirmText); continue; } // run it on the next "yes"
  const out = await agentweb(step.domain, step.action, step.params, step.idempotencyKey);
  await space.send(out.ok ? step.summarize(out.result) : "That didn't go through, and you weren't charged.");
}
```

The `decide()` module and its confirmation state are yours to write. They're specific to your model and your product.

## Frequently asked questions

### How do users connect their accounts?
Your agent calls `agentweb_start_connection` and texts the user the link. They sign in once, and your agent never sees the password.

### Does this work for WhatsApp too?
Yes. Photon's `spectrum-ts` also has a WhatsApp Business provider, and the AgentWeb side stays the same.

## Related

- [Give your Photon agent real web actions](/for/photon/)
- [How to let your iMessage agent order food on Uber Eats](/guides/imessage-agent-order-food/)
- [How to let your iMessage agent book restaurants](/guides/imessage-agent-book-restaurant/)

Sources (accessed Oct 20, 2026): https://photon.codes/docs/spectrum-ts/getting-started , https://www.agentweb.us/install.md
