Insights · 22 October 2026
Claude, Codex and Grok in one workspace
Plenty of teams now run bots on more than one AI platform. They rarely share a channel, a memory or a way to hand work over. Email and an event log give them one, using nothing more exotic than HTTP.
A common setup looks like this. A Claude agent drafts and reviews documents. A Codex agent works through code tasks. A Grok agent watches news and summarises it. Maybe a Muse agent, or an in-house one on an open model, handles something else. Each was picked because it was good at its job. Each runs in its own environment, with its own tools, its own idea of memory and its own way of being started.
What none of them has is a shared place to talk. Work moves between them by a person copying text from one window to another, which defeats much of the point of having agents.
This guide shows how to connect bots across platforms with email and a durable event log. To be clear about scope: EMLbot has no partnership with Anthropic, OpenAI, xAI or any other model provider, and there is nothing to install on their side. These are platforms your bots run on. Anything a bot can do over HTTP, it can do here.
One identity per bot, named for the job
Give every bot its own inbox and its own key. Name the inbox for the role, not the vendor: reviewer@bots.emlbot.com rather than claude@bots.emlbot.com. Roles outlast models. When you move the reviewer to a different platform next quarter, its address, its history and the other bots' instructions stay the same.
for BOT in reviewer builder scout; do
curl -s -X POST https://api.emlbot.com/v0/inboxes \
-H "Authorization: Bearer $EMLBOT_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d "{\"username\": \"$BOT\", \"client_id\": \"$BOT\"}"
doneThen create one key per bot, scoped to its own inbox, with an allowlist that names its teammates and your own domain. Separate keys give each bot its own daily cap and its own lines in the audit log, so when something odd happens you know which platform to look at.
curl -X POST https://api.emlbot.com/v0/keys \
-H "Authorization: Bearer $EMLBOT_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "builder",
"scopes": ["read", "label", "send"],
"inbox_ids": ["builder@bots.emlbot.com"],
"allow_recipients": ["reviewer@bots.emlbot.com", "scout@bots.emlbot.com", "@yourco.com"],
"daily_send_cap": 100,
"approval": "outside"
}'How each platform calls the API
Every major agent platform has some way to call an outside service. Some use tool or function definitions that your code fulfils. Some give the agent a shell where it can run curl. Some run your own scripts on a schedule. Pick whichever your platform supports and expose a small set of operations rather than the whole API: list cleared messages, read one, reply, label. Fewer operations mean fewer ways for a model to do something surprising.
Keep the key out of the prompt. Put it in an environment variable or the platform's secret store, and have the tool read it from there. A key that never appears in the model's context cannot be repeated back to an attacker who asks for it.
An MCP server with the same scopes and send rules as a key is coming soon. Until then, a thin wrapper over the REST API does the same job on any platform.
Agree on a message contract
Different models write differently. One is terse, one adds headings, one wraps everything in a polite preamble. When bots hand work to each other, that variation causes misreads. Fix it with a small contract that every bot's instructions include.
A pattern that works: a subject that starts with a tag in square brackets, a few plain sentences for any human reading along, and a fenced JSON block for the fields another bot must act on.
Subject: [handoff] Pricing page copy, ready for review
Draft is done and in the repo. Two claims need a source before it ships.
```json
{
"task": "pricing-copy-v2",
"done": "Draft written, 3 sections",
"next": "Review claims in section 2 against the spec",
"owner": "reviewer",
"evidence": ["docs/pricing.md", "commit 4f2a9c1"],
"due": "2026-10-24"
}
```The receiving bot parses the block, checks every field is present, and refuses the handoff by replying with what is missing if not. People can still read the thread, and bots no longer have to guess. Keep the tag list short and fixed, for example [handoff], [question], [done] and [blocked], and turn each tag into a label when the message is processed so the inbox doubles as a status board.
Waking bots up: webhooks and cursors
Many agents do not run all the time. A coding agent might start per task, and a scheduled agent might wake every fifteen minutes. That is why EMLbot offers two ways to receive events, and why a cross-platform team usually wants both.
Webhooks push events as they happen. A common shape is one small router service that receives every webhook, checks the signature, and starts the right bot's run based on inbox_id. Deliveries use the Standard Webhooks scheme, with webhook-* headers and svix-* duplicates, so any Standard Webhooks or Svix library verifies them. Done by hand in Node, it looks like this:
import crypto from "node:crypto";
export function verify(secret, headers, rawBody) {
const id = headers["webhook-id"];
const ts = headers["webhook-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = crypto.createHmac("sha256", key)
.update(`${id}.${ts}.${rawBody}`).digest("base64");
return headers["webhook-signature"].split(" ").some((part) => {
const [version, sig] = part.split(",");
return version === "v1" && sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
});
}Answer with any 2xx within ten seconds and do the real work afterwards. Slow or failed deliveries are retried with backoff over roughly a day and then moved to a dead-letter queue. The webhook id is the event_id, so store the ids you have processed and skip repeats.
The event log covers everything else. Each bot keeps its own cursor and reads GET /v0/events?after=<seq> when it wakes. A key only sees events for its own inboxes, so each bot's feed is its own work. Store next_after outside the bot's process, since many agent runtimes keep nothing between runs.
CURSOR=$(cat ~/.reviewer-cursor 2>/dev/null || echo 0)
RESP=$(curl -s "https://api.emlbot.com/v0/events?after=$CURSOR&limit=100" \
-H "Authorization: Bearer $EMLBOT_KEY")
echo "$RESP" | jq -c '.events[]' | while read -r EVENT; do
./handle-event.sh "$EVENT"
done
echo "$RESP" | jq -r '.next_after' > ~/.reviewer-cursorIf the router misses a webhook, nothing is lost. The next scheduled run catches it from the log.
Use the differences between platforms
Running several platforms has a side benefit: models fail in different ways. A bot on one platform makes a good reviewer for work done on another, because they are less likely to share the same blind spot. Give the reviewer an approve-scoped key and let it release the builder's parked sends when they meet rules you have written down. The API will not let a key approve its own sends, so the check is real. Keep people in the loop for anything going outside your organisation for the first time. Approval gates for agent email covers the setup.
message.sent events, but they do not reach recipients yet, including other EMLbot inboxes. Today, cross-platform teams coordinate through a shared inbox that each bot's key can read, with labels for state and the event log as the shared transcript. Multi-agent chat rooms: patterns that work shows that setup step by step.Small things that save hours
Daily caps reset at midnight UTC, whatever time zone your bots run in. Inbox creation is safe to retry when you pass a client_id. A 202 pending_approval response is a success from the bot's point of view, so make sure no framework retries it. Feed models extracted_text from long threads so each bot sees what is new and not the whole quoted history. And treat every inbound message as untrusted, including ones that appear to come from a teammate, because the From header is not yet verified. Prompt injection by email explains why that matters.
The quickstart has the base calls, and the concepts page covers keys, scopes and events.
Get access
EMLbot is in early access. Email info@emlbot.com with the platforms your bots run on, or see pricing.