Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/reply-to-email-send-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agents": patch
---

Allow `replyToEmail()` to send through Cloudflare Email Service `send_email` bindings. The method now uses `this.env.EMAIL` automatically when present, and also accepts an explicit `sendBinding` option for differently named bindings.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ State changes sync to all connected clients automatically. Call methods like the
| **AI Chat** | Message persistence, resumable streaming, server/client tool execution |
| **MCP** | Act as MCP servers or connect as MCP clients |
| **Workflows** | Durable multi-step tasks with human-in-the-loop approval |
| **Email** | Receive and respond via Cloudflare Email Routing |
| **Email** | Send, receive, and reply via Cloudflare Email Service |
| **Code Mode** | LLMs generate executable TypeScript instead of individual tool calls |
| **SQL** | Direct SQLite queries via Durable Objects |
| **React Hooks** | `useAgent` and `useAgentChat` for frontend integration |
Expand Down
7 changes: 4 additions & 3 deletions docs/agent-class.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ class MyAgent extends Agent {

### Email Handling

Agents can receive and reply to emails using Cloudflare's [Email Routing](https://developers.cloudflare.com/email-routing/email-workers/).
Agents can send and receive emails using Cloudflare's [Email Service](https://developers.cloudflare.com/email-service/).

```ts
class MyAgent extends Agent {
Expand All @@ -354,7 +354,8 @@ class MyAgent extends Agent {
// Reply to the email
await this.replyToEmail(email, {
fromName: "My Agent",
body: "Thanks for your email!"
body: "Thanks for your email!",
sendBinding: this.env.EMAIL
});
}
}
Expand All @@ -375,7 +376,7 @@ export default {
};
```

For more details on email routing, resolvers, secure reply flows, and the full API, see the [Email Routing guide](./email.md).
For more details on sending with `env.EMAIL.send()`, routing inbound mail, resolvers, and secure reply flows, see the [Email Service guide](./email.md).

### Context Management

Expand Down
84 changes: 70 additions & 14 deletions docs/email.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,47 @@
# Email Routing
# Email Service

Agents can receive and process emails using Cloudflare's [Email Routing](https://developers.cloudflare.com/email-routing/email-workers/). This guide covers how to route inbound emails to your Agents and handle replies securely.
Agents can send and receive email with Cloudflare's [Email Service](https://developers.cloudflare.com/email-service/). This guide shows how to send outbound email with the Workers binding, route inbound mail into Agents, and handle follow-up replies securely.

## Prerequisites

1. A domain configured with [Cloudflare Email Routing](https://developers.cloudflare.com/email-routing/)
2. An Email Worker configured to receive emails
3. An Agent to process emails
1. A domain configured for [Cloudflare Email Service](https://developers.cloudflare.com/email-service/)
2. A `send_email` binding in `wrangler.jsonc` for outbound email
3. An Email Service routing rule that sends inbound mail to your Worker
4. Optional: an `EMAIL_SECRET` secret if you want secure reply routing

## Quick Start

```ts
import { Agent, routeAgentEmail } from "agents";
import { Agent, callable, routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver, type AgentEmail } from "agents/email";
import PostalMime from "postal-mime";

// Your Agent that handles emails
export class EmailAgent extends Agent {
@callable()
async sendWelcomeEmail(to: string) {
await this.env.EMAIL.send({
to,
from: "support@yourdomain.com",
replyTo: "support@yourdomain.com",
subject: "Welcome to our service",
text: "Thanks for signing up. Reply to this email if you need help."
});
}

async onEmail(email: AgentEmail) {
const raw = await email.getRaw();
const parsed = await PostalMime.parse(raw);

console.log("Received email from:", email.from);
console.log("Subject:", email.headers.get("subject"));
console.log("Subject:", parsed.subject);

// Reply to the email
await this.replyToEmail(email, {
fromName: "My Agent",
body: "Thanks for your email!"
fromName: "Support Agent",
body: "Thanks for your email! We received it."
});
}
}

// Route emails to your Agent
export default {
async email(message, env) {
await routeAgentEmail(message, env, {
Expand All @@ -38,10 +51,48 @@ export default {
};
```

## Resolvers
## Sending Outbound Email

Use the Email Service Workers binding for the first outbound message in a conversation or for any transactional email your agent needs to send.

Configure the binding in `wrangler.jsonc`:

```jsonc
{
"send_email": [
{
"name": "EMAIL",
"remote": true
}
]
}
```

Then call `env.EMAIL.send()` from a callable method, scheduled task, webhook handler, or any other agent code:

```ts
@callable()
async sendReceipt(to: string, orderId: string) {
const response = await this.env.EMAIL.send({
to,
from: { email: "billing@yourdomain.com", name: "Billing Bot" },
replyTo: "billing@yourdomain.com",
subject: `Receipt for order ${orderId}`,
text: `Your receipt for order ${orderId} is ready.`
});

return response.messageId;
}
```

Set `replyTo` to the mailbox that routes back to your Worker when you want recipients to continue the conversation with the same agent.

## Routing Inbound Mail

Resolvers determine which Agent instance receives an incoming email. Choose the resolver that matches your use case.

For basic Email Service sending and receiving, `createAddressBasedEmailResolver()` is enough. The secure reply resolver below is optional and specific to Agents SDK reply signing, not a requirement of Email Service itself.

### createAddressBasedEmailResolver

**Recommended for inbound mail.** Routes emails based on the recipient address.
Expand Down Expand Up @@ -201,6 +252,8 @@ This checks for standard RFC 3834 headers (`Auto-Submitted`, `X-Auto-Response-Su

### Replying to Emails

`replyToEmail()` will send through `this.env.EMAIL` automatically when that binding exists. If your `send_email` binding uses a different name, pass it explicitly with `sendBinding`.

Use `this.replyToEmail()` to send a reply:

```ts
Expand All @@ -210,6 +263,7 @@ async onEmail(email: AgentEmail) {
subject: "Re: Your inquiry", // Optional, defaults to "Re: <original subject>"
body: "Thanks for contacting us!", // Email body
contentType: "text/plain", // Optional, defaults to "text/plain"
sendBinding: this.env.EMAIL, // Optional when your binding is named EMAIL
headers: { // Optional custom headers
"X-Custom-Header": "value"
},
Expand Down Expand Up @@ -295,6 +349,7 @@ async onEmail(email: AgentEmail) {
await this.replyToEmail(email, {
fromName: "My Agent",
body: "Thanks for your email!",
sendBinding: this.env.EMAIL,
secret: this.env.EMAIL_SECRET // Signs the routing headers
});
}
Expand All @@ -312,7 +367,7 @@ When an email is routed via `createSecureReplyEmailResolver`, the `replyToEmail(

## Complete Example

Here's a complete email agent with secure reply routing:
Here is a complete Email Service agent that sends outbound mail and handles secure replies:

```ts
import { Agent, routeAgentEmail } from "agents";
Expand Down Expand Up @@ -348,6 +403,7 @@ export class EmailAgent extends Agent<Env> {
await this.replyToEmail(email, {
fromName: "Support Bot",
body: `Thanks for your email! We received: "${parsed.subject}"`,
sendBinding: this.env.EMAIL,
secret: this.env.EMAIL_SECRET
});
}
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ Now that you have a working agent, explore these topics:
| Add AI/LLM capabilities | [Chat Agents](./chat-agents.md) |
| Expose tools via MCP | [Creating MCP Servers](./mcp-servers.md) |
| Run background tasks | [Scheduling](./scheduling.md) |
| Handle emails | [Email Routing](./email.md) |
| Handle emails | [Email Service](./email.md) |
| Use Cloudflare Workflows | [Workflows](./workflows.md) |

---
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

## Communication Channels

- [Email Routing](./email.md) - Receiving and responding to emails
- [Email Service](./email.md) - Sending, receiving, and replying to emails
- [Webhooks](./webhooks.md) - Receiving and sending webhook events
- [Push Notifications](./push-notifications.md) - Browser push notifications via Web Push API and scheduled delivery
- TODO: [SMS](./sms.md) - Text message integration (Twilio, etc.)
Expand Down
1 change: 0 additions & 1 deletion examples/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,5 +219,4 @@ Every example needs one. Keep it short:

See `TODO.md` in this folder for the full checklist.

- `email-agent/` is worker-only and needs a frontend
- `cross-domain/` has a `vite.config.ts` but does not use `@cloudflare/vite-plugin`
4 changes: 2 additions & 2 deletions examples/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Tracked issues from the examples audit. See `AGENTS.md` in this folder for the c

All examples must be full-stack (frontend + backend). These worker-only examples need a frontend, `index.html`, `vite.config.ts`, and `src/client.tsx` added:

- [ ] `email-agent/` — add frontend demonstrating the email feature
- [x] `email-agent/` — added a full-stack Email Service demo UI
- [x] `mcp-elicitation/` — added landing page with connection instructions
- [x] ~~`mcp-server/`~~ — removed (redundant with `mcp-worker/`)
- [x] `mcp-worker/` — added MCP tool tester frontend
Expand All @@ -31,7 +31,7 @@ All examples must be full-stack (frontend + backend). These worker-only examples
## Missing env.d.ts

- [ ] `a2a/` — generate `env.d.ts` with `npx wrangler types`
- [ ] `email-agent/` — generate `env.d.ts` with `npx wrangler types`
- [x] `email-agent/` — generated `env.d.ts`
- [x] `mcp-worker-authenticated/` — generated `env.d.ts`

## Secrets examples
Expand Down
Loading
Loading