Docs

Email

Send mail from your Workers through a send_email binding, from domains you own, to addresses that confirmed they want it.

Updated October 31, 2026
On this page
  1. In your Worker
  2. When a send is refused
  3. Limits
  4. Related

A Worker with a send_email binding sends mail the same way it does on Cloudflare. Fortly sends it from a domain your account has verified, and only to destination addresses that have confirmed they want the mail. That makes contact forms, order notifications and alerts to your own inbox work unchanged, and it means Fortly is never an open relay.

This feature sends outbound email. Receiving and forwarding incoming mail is not available.

Set it up once per account under Email in the console. Its setup checklist connects sending domains, destinations and your Worker:

  1. Add a sending domain and publish the DNS records it shows: a TXT record proving ownership, a DKIM key, and, recommended, an SPF include. Fortly checks them every few minutes and the page updates on its own. A subdomain such as mail.example.com keeps them apart from the mail you already receive.
  2. Add a destination address. Fortly mails it a confirmation link. Nothing is sent to it until someone opens the link and confirms.

Any address at a verified domain can then send to any confirmed destination, within the Worker binding’s restrictions. Each connected Worker shows whether its own allowed sender and recipient addresses are ready, whether the mail service is configured, and whether the monthly allowance remains.

In your Worker

// wrangler.jsonc
{
  "send_email": [{ "name": "EMAIL" }]
}

Cloudflare's structured form:

JavaScript
await env.EMAIL.send({
  from: { name: "Shop", email: "orders@mail.example.com" },
  to: "me@example.com",
  replyTo: { name: buyer.name, email: buyer.email },
  subject: "New order",
  text: "…",
  html: "<p>…</p>",
});

The original raw form works too:

JavaScript
import { EmailMessage } from "cloudflare:email";

await env.EMAIL.send(new EmailMessage("orders@mail.example.com", "me@example.com", rawMimeMessage));

to, cc and bcc each take one address or a list of addresses, and every address can be a string or { email, name }. A message can have up to 50 recipients. A raw message is delivered byte for byte as you composed it.

The binding's own restrictions apply as on Cloudflare: destination_address limits it to one recipient, allowed_destination_addresses to a list, and allowed_sender_addresses limits who it may send as. Every recipient must still be a confirmed destination, and every sender must use a verified domain.

When a send is refused

send() throws an error with a code:

CodeWhy
E_SENDER_NOT_VERIFIEDThe from address is not at a verified sending domain of the project's account.
E_SENDER_NOT_ALLOWEDThe binding's allowed_sender_addresses leave it out.
E_RECIPIENT_NOT_ALLOWEDA recipient is not a confirmed destination, or the binding's restrictions leave it out.
E_FIELD_MISSING, E_VALIDATION_ERRORNo recipient, no subject, no text or HTML, or an address that is not one.
E_TOO_MANY_RECIPIENTSMore than 50 recipients.
E_RATE_LIMIT_EXCEEDEDThe account has sent its messages for the month.
E_DELIVERY_FAILEDThe mail provider did not accept the message.

Every message, sent or refused, appears under Email → Activity with the reason. Search by subject, recipient or project and filter by Sent, Refused or Failed. Activity refreshes every 30 seconds and can be refreshed manually. Removing a sending domain or destination requires confirmation before Workers lose access to it. Deployments → Bindings shows whether each send_email binding can send yet.

Limits

PlanMessages a month
Free100
Pro1,000
Max5,000

Months are calendar months in UTC. Only sent messages count; refused ones do not. Sending to any address, such as a buyer's own inbox, is not available yet.

Something missing or unclear? Tell us and we will fix the page.© 2026 Fortly