Summary
receiveEmail() triggers the auto-draft agent by calling the EmailAgent Durable Object stub directly. That call fails with HTTP 500 for any mailbox whose agent instance has never been named — in practice, any mailbox that was never opened in the UI. The failure is swallowed by the surrounding .catch(), so the email is stored normally and nothing surfaces the problem.
Net effect: the "Auto-draft on new email" feature advertised in the README silently never runs for those mailboxes.
Where
https://github.com/cloudflare/agentic-inbox/blob/main/workers/index.ts — end of receiveEmail():
const agentStub = env.EMAIL_AGENT.get(env.EMAIL_AGENT.idFromName(mailboxId));
ctx.waitUntil(agentStub.fetch(new Request("https://agents/onNewEmail", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mailboxId, emailId: messageId, ... }),
})).catch((e) => console.error("Auto-draft trigger failed:", (e as Error).message)));
Root cause
partyserver's Server.fetch() requires the instance to have a name. It first tries hydrateNameFromStorage(), and if that yields nothing it demands an x-partykit-room header (partyserver@0.3.3, dist/index.js):
if (!this.#_name) await this.#hydrateNameFromStorage();
if (!this.#_name) {
const room = request.headers.get("x-partykit-room");
if (!room) throw new Error(`Missing namespace or room headers when connecting to ${this.#ParentClass.name}. ...`);
await this.setName(room);
}
An instance only gets a persisted name when something performs the set-name handshake. The UI does it implicitly: browser connections go through the /agents/* route, which resolves namespace and room from the URL. The email handler bypasses that and calls the DO directly with only Content-Type, so a fresh instance has no name and throws.
This makes the bug look intermittent: a mailbox that has been opened in the UI at least once works, because its name is already in storage. A mailbox that has only ever received email never does.
Reproduction
- Create a mailbox and do not open it in the UI.
- Send an email to it.
wrangler tail.
Observed:
{
"entrypoint": "EmailAgent",
"event": { "request": { "url": "https://agents/onNewEmail", "method": "POST" },
"response": { "status": 500 } },
"logs": [{ "level": "error", "message": [
"Error in EmailAgent:<unnamed> fetch:",
"Error: Missing namespace or room headers when connecting to EmailAgent.\nDid you try connecting directly to this Durable Object? Try using getServerByName(namespace, id) instead."
]}]
}
The email itself is stored fine — findThreadBySubject and createEmail both return ok before the agent call fails.
Fix
Resolve the agent through the SDK, which performs the set-name handshake before returning the stub:
import { getAgentByName } from "agents";
ctx.waitUntil(
getAgentByName(env.EMAIL_AGENT, mailboxId)
.then((agentStub) => agentStub.fetch(new Request("https://agents/onNewEmail", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mailboxId, emailId: messageId, ... }),
})))
.catch((e) => console.error("Auto-draft trigger failed:", (e as Error).message)),
);
Verified in production. After the change, on a mailbox created via R2 and never opened in the UI:
ok EmailAgent http://dummy-example.cloudflare.com/cdn-cgi/partyserver/set-name/
ok MailboxDO getEmail
ok MailboxDO getEmails
ok MailboxDO getThreadEmails
The set-name handshake is the step that was missing; the agent then reads the mailbox and drafts as intended.
Secondary observation
Because the call sits inside ctx.waitUntil(...).catch(console.error), this failed on every inbound email without any user-visible signal. Surfacing agent-trigger failures somewhere more visible than console.error would make this class of bug easier to catch. Related: #17 ("Log silent catches in deep-scan pipeline") touches the same theme.
Environment
agents@0.7.6, partyserver@0.3.3
wrangler@4.74.0, compatibility_date 2025-11-28
- Deployed on Workers with Email Routing catch-all → Worker
Happy to open a PR with the fix if useful.
Summary
receiveEmail()triggers the auto-draft agent by calling theEmailAgentDurable Object stub directly. That call fails with HTTP 500 for any mailbox whose agent instance has never been named — in practice, any mailbox that was never opened in the UI. The failure is swallowed by the surrounding.catch(), so the email is stored normally and nothing surfaces the problem.Net effect: the "Auto-draft on new email" feature advertised in the README silently never runs for those mailboxes.
Where
https://github.com/cloudflare/agentic-inbox/blob/main/workers/index.ts — end of
receiveEmail():Root cause
partyserver'sServer.fetch()requires the instance to have a name. It first trieshydrateNameFromStorage(), and if that yields nothing it demands anx-partykit-roomheader (partyserver@0.3.3,dist/index.js):An instance only gets a persisted name when something performs the set-name handshake. The UI does it implicitly: browser connections go through the
/agents/*route, which resolves namespace and room from the URL. The email handler bypasses that and calls the DO directly with onlyContent-Type, so a fresh instance has no name and throws.This makes the bug look intermittent: a mailbox that has been opened in the UI at least once works, because its name is already in storage. A mailbox that has only ever received email never does.
Reproduction
wrangler tail.Observed:
{ "entrypoint": "EmailAgent", "event": { "request": { "url": "https://agents/onNewEmail", "method": "POST" }, "response": { "status": 500 } }, "logs": [{ "level": "error", "message": [ "Error in EmailAgent:<unnamed> fetch:", "Error: Missing namespace or room headers when connecting to EmailAgent.\nDid you try connecting directly to this Durable Object? Try using getServerByName(namespace, id) instead." ]}] }The email itself is stored fine —
findThreadBySubjectandcreateEmailboth returnokbefore the agent call fails.Fix
Resolve the agent through the SDK, which performs the set-name handshake before returning the stub:
Verified in production. After the change, on a mailbox created via R2 and never opened in the UI:
The set-name handshake is the step that was missing; the agent then reads the mailbox and drafts as intended.
Secondary observation
Because the call sits inside
ctx.waitUntil(...).catch(console.error), this failed on every inbound email without any user-visible signal. Surfacing agent-trigger failures somewhere more visible thanconsole.errorwould make this class of bug easier to catch. Related: #17 ("Log silent catches in deep-scan pipeline") touches the same theme.Environment
agents@0.7.6,partyserver@0.3.3wrangler@4.74.0,compatibility_date2025-11-28Happy to open a PR with the fix if useful.