cloudflare / cloudflare/agentic-inbox

Auto-draft trigger fails with 500 for mailboxes never opened in the UI

Open Beginner friendly
#57 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.