This example shows how to combine the new Cloudflare Email Service sending binding with Agents SDK email routing in one full-stack app.
- Send transactional email from an agent with
this.sendEmail() - Route inbound email into an agent with
routeAgentEmail() - Parse MIME content with
postal-mime - Optionally sign follow-up replies with
replyToEmail()andEMAIL_SECRET - Keep inbox and outbox state synced to a React client with
useAgent() - Simulate inbound email locally with
/api/simulate-emailbefore you deploy routing
Before running this example, you need:
-
A domain onboarded to Cloudflare Email Service
- Log in to the Cloudflare Dashboard
- Navigate to Compute & AI > Email Service
- Select Onboard Domain and choose your domain
- Add the DNS records (SPF and DKIM) to authorize sending
-
A verified sender address in the Cloudflare dashboard
- The example defaults to
mailbox-7f3a@example.com - Change
EMAIL_FROMinwrangler.jsoncto your verified address
- The example defaults to
-
DNS propagation (usually 5-15 minutes, up to 24 hours)
npm install
cp .env.example .env
npm startBefore you send real email, review wrangler.jsonc:
- Update
vars.EMAIL_FROMto your verified sender address - Optional: add
EMAIL_SECRETif you want signed reply routing:
wrangler secret put EMAIL_SECRETThe send_email binding is configured with remote: true, so npm start can call the real Email Service API from local development.
If EMAIL_SECRET is missing, the example still runs. Inbound mail uses address-based routing only, and auto-replies are sent unsigned.
| Error | Cause | Solution |
|---|---|---|
E_SENDER_NOT_VERIFIED |
Domain or sender not verified | Complete domain onboarding in dashboard |
E_RATE_LIMIT_EXCEEDED |
Too many emails sent | Wait and retry |
E_DAILY_LIMIT_EXCEEDED |
Daily quota reached | Wait for next day or upgrade plan |
- Open the app in your browser
- Use Send outbound email to send a real message through Email Service
- Use Simulate inbound email to exercise the inbound routing path locally
- Deploy the Worker and route
EMAIL_FROMto it to receive live email
const response = await this.sendEmail({
binding: this.env.EMAIL,
to,
from: { email: this.env.EMAIL_FROM, name: "Email Service Agent" },
replyTo: this.env.EMAIL_FROM,
subject,
text: body,
html: body.replace(/\n/g, "<br />"),
secret: this.env.EMAIL_SECRET
});const addressResolver = createAddressBasedEmailResolver("EmailServiceAgent");
await routeAgentEmail(message, env, {
resolver: async (email, env) => {
if (env.EMAIL_SECRET) {
const secureResolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
const secureReply = await secureResolver(email, env);
if (secureReply) return secureReply;
}
return addressResolver(email, env);
}
});With EMAIL_FROM="mailbox-7f3a@example.com", replies to mailbox-7f3a@example.com route to the EmailServiceAgent instance named mailbox-7f3a.
The app includes a small HTTP helper so the example is still useful before you deploy routing:
curl -X POST http://localhost:8787/api/simulate-email \
-H "Content-Type: application/json" \
-d '{
"from": "customer@example.com",
"subject": "Question about my account",
"body": "Can you confirm my renewal date?"
}'That request creates a mock ForwardableEmailMessage and runs it through the same resolver chain as a real routed email.
- Onboard your domain in Cloudflare Email Service for both sending and routing
- Verify the sender address used by
EMAIL_FROM - Deploy this Worker with
npm run deploy - Add an Email Service routing rule that sends
EMAIL_FROMto this Worker
../../docs/email.md— Email Service with agents../playground— larger email routing demos inside the playground