import { useAgent } from "agents/react"; import { useState } from "react"; import { EnvelopeIcon, TrayIcon, ClockIcon, HashIcon } from "@phosphor-icons/react"; import { Button, Surface, Empty, Text } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, ConnectionStatus, LocalDevBanner, CodeExplanation, type CodeSection } from "../../components"; import { useLogs, useUserId } from "../../hooks"; import type { ReceiveEmailAgent, ReceiveEmailState, ParsedEmail } from "./receive-email-agent"; const codeSections: CodeSection[] = [ { title: "Handle incoming emails", description: "Override the onEmail method to process incoming messages. The agent receives a parsed AgentEmail object with from, to, and a getRaw() method for full MIME parsing with postal-mime.", code: `import { Agent } from "agents"; import type { AgentEmail } from "agents/email"; import PostalMime from "postal-mime"; class ReceiveEmailAgent extends Agent { async onEmail(email: AgentEmail) { const raw = await email.getRaw(); const parsed = await PostalMime.parse(raw); this.setState({ ...this.state, emails: [...this.state.emails, { id: crypto.randomUUID(), from: parsed.from?.address || email.from, to: email.to, subject: parsed.subject || "(No Subject)", text: parsed.text, timestamp: new Date().toISOString(), }], }); this.broadcast(JSON.stringify({ type: "new_email" })); } }` }, { title: "Route emails with routeAgentEmail", description: "Use routeAgentEmail in your Worker's email handler with createAddressBasedEmailResolver. Plus-addressing (user+id@domain) automatically maps to the right agent and instance — no manual parsing needed.", code: `import { routeAgentEmail } from "agents"; import { createAddressBasedEmailResolver } from "agents/email"; export default { async email(message: ForwardableEmailMessage, env: Env) { // "receive+demo@example.com" routes to // ReceiveEmailAgent with agentId "demo" const resolver = createAddressBasedEmailResolver( "ReceiveEmailAgent" ); await routeAgentEmail(message, env, { resolver, onNoRoute: async (email) => { console.warn("No route for:", email.to); }, }); }, };` } ]; export function ReceiveDemo() { const userId = useUserId(); const { logs, addLog, clearLogs } = useLogs(); const [selectedEmail, setSelectedEmail] = useState(null); const [state, setState] = useState({ emails: [], totalReceived: 0 }); const agent = useAgent({ agent: "receive-email-agent", name: `email-receive-${userId}`, onStateUpdate: (newState) => { if (newState) { setState(newState); addLog("in", "state_update", { emails: newState.emails.length, total: newState.totalReceived }); } }, onOpen: () => addLog("info", "connected"), onClose: () => addLog("info", "disconnected"), onError: () => addLog("error", "error", "Connection error"), onMessage: (message) => { try { const data = JSON.parse(message.data as string); if (data.type) { addLog("in", data.type, data); } } catch { // ignore } } }); return ( Agents can receive real emails via Cloudflare Email Routing. Override the{" "} onEmail {" "} method to process incoming messages — parse them, store them in state, and notify connected clients. Use plus-addressing (e.g.{" "} receive+id@domain ) to route emails to specific agent instances. } statusIndicator={ } >
{/* Left Panel - Info & Stats */}
Instance:{" "} demo
Stats
Inbox
{state.emails.length}
Total
{state.totalReceived}
{state.lastReceivedAt && (
Last: {new Date(state.lastReceivedAt).toLocaleString()}
)}
Setup Instructions
  1. 1. Deploy this playground to Cloudflare
  2. 2. Go to Cloudflare Dashboard → Email → Email Routing
  3. 3. Add a catch-all or specific rule routing to this Worker
  4. 4. Send email to:{" "} receive+demo@yourdomain.com
Address Format
receive+id@domain
Routes to ReceiveEmailAgent with instance "id"
{/* Center Panel - Inbox */}
Inbox ({state.emails.length})
{state.emails.length > 0 ? ( [...state.emails].reverse().map((email) => ( )) ) : (

Send an email to see it appear here

)}
{/* Email Detail */} {selectedEmail && (
{selectedEmail.subject}
From: {selectedEmail.from}
To: {selectedEmail.to}
Date: {new Date(selectedEmail.timestamp).toLocaleString()}
{selectedEmail.messageId && (
ID: {selectedEmail.messageId}
)}
{selectedEmail.text || selectedEmail.html || "(No content)"}
{selectedEmail.headers && Object.keys(selectedEmail.headers).length > 0 && (
Headers ({Object.keys(selectedEmail.headers).length})
{Object.entries(selectedEmail.headers).map( ([key, value]) => (
{key}:{" "} {value}
) )}
)}
)}
{/* Right Panel - Logs */}
); }