cloudflare / cloudflare/agentic-inbox
Auto-draft trigger fails with 500 for mailboxes never opened in the UI
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 964
- PR merge metrics
- No merged PRs in 30d
Description
## 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()`:
```js
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`):
```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
1. Create a mailbox and do **not** open it in the UI.
2. Send an email to it.
3. `wrangler tail`.
Observed:
```json
{
"entrypoint": "EmailAgent",
"event": { "request": { "url": "https://agents/onNewEmail", "method": "POST" },
"response": { "status": 500 } },
"logs": [{ "level": "error", "message": [
"Error in EmailAgent: 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:
```js
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.
Contributor guide
Research direction
Start in workers/index.ts at the end of receiveEmail(), then review the agents SDK usage and the existing EmailAgent request path. Reproduce with a mailbox that has never been opened, use wrangler tail to confirm the agent trigger succeeds, and verify that an auto-draft is created without the 500 error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, cloud
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 85/100