Workers API
The Workers API provides native email sending capabilities directly from your Cloudflare Workers through bindings. If you are not using Workers, you can send emails using the REST API instead.
Configure email bindings in your Wrangler configuration file to enable email sending:
{ "$schema": "./node_modules/wrangler/config-schema.json", "send_email": [ { "name": "EMAIL" }, { "name": "RESTRICTED_EMAIL", "allowed_sender_addresses": [ "noreply@yourdomain.com", "support@yourdomain.com" ] } ]}[[send_email]]name = "EMAIL"
# Optional: restrict sender addresses for security[[send_email]]name = "RESTRICTED_EMAIL"allowed_sender_addresses = ["noreply@yourdomain.com", "support@yourdomain.com"]Send a single email using the send() method on your email binding.
interface SendEmail { send(message: EmailMessage | EmailMessageBuilder): Promise<EmailSendResult>;}
// Structured email builder (recommended)interface EmailMessageBuilder { to: string | string[]; // Max 50 recipients from: string | { email: string; name: string }; subject: string; html?: string; text?: string; cc?: string | string[]; bcc?: string | string[]; replyTo?: string | { email: string; name: string }; attachments?: Attachment[]; headers?: { [key: string]: string }; // See /email-service/reference/headers/}
interface Attachment { content: string | ArrayBuffer; // Base64 string or binary content filename: string; type: string; // MIME type disposition: "attachment" | "inline"; contentId?: string; // For inline attachments}
interface EmailSendResult { messageId: string; // Unique email ID}
// Errors are thrown as standard Error objects with a `code` property// try { await env.EMAIL.send(...) } catch (e) { console.log(e.code, e.message) }interface Env { EMAIL: SendEmail;}// Send to multiple recipients (max 50)const response = await env.EMAIL.send({ to: ["user1@example.com", "user2@example.com", "user3@example.com"], from: { email: "newsletter@yourdomain.com", name: "Newsletter Team" }, subject: "Monthly Newsletter", html: "<h1>This month's updates</h1>", text: "This month's updates",});const response = await env.EMAIL.send({ to: "customer@example.com", cc: ["manager@company.com"], bcc: ["archive@company.com"], from: "orders@yourdomain.com", replyTo: "support@yourdomain.com", subject: "Order Confirmation #12345", html: "<h1>Your order is confirmed</h1>", text: "Your order is confirmed",});const response = await env.EMAIL.send({ to: "customer@example.com", from: "invoices@yourdomain.com", subject: "Your Invoice", html: "<h1>Invoice attached</h1><p>Please find your invoice attached.</p>", attachments: [ { content: "JVBERi0xLjQKJeLjz9MKMSAwIG9iag...", // Base64 PDF content filename: "invoice-12345.pdf", type: "application/pdf", disposition: "attachment", }, ],});const response = await env.EMAIL.send({ to: "user@example.com", from: "marketing@yourdomain.com", subject: "Check out our new product!", html: ` <h1>New Product Launch</h1> <img src="cid:product-image" alt="New Product" /> <p>Check out our amazing new product!</p> `, attachments: [ { content: "iVBORw0KGgoAAAANSUhEUgAA...", // Base64 image content filename: "product.png", type: "image/png", disposition: "inline", contentId: "product-image", }, ],});Handle email sending errors gracefully:
export default { async fetch(request: Request, env: Env): Promise<Response> { try { const response = await env.EMAIL.send({ to: "user@example.com", from: "noreply@yourdomain.com", subject: "Test Email", text: "This is a test email.", });
return new Response( JSON.stringify({ success: true, emailId: response.messageId, }), ); } catch (error) { // Error has .code and .message properties console.error("Email sending failed:", error.code, error.message);
// Handle specific error types switch (error.code) { case "E_SENDER_NOT_VERIFIED": return new Response( JSON.stringify({ success: false, error: "Please verify your sender domain first", }), { status: 400 }, );
case "E_RATE_LIMIT_EXCEEDED": return new Response( JSON.stringify({ success: false, error: "Rate limit exceeded. Please try again later", }), { status: 429 }, );
default: return new Response( JSON.stringify({ success: false, error: error.message, }), { status: 500 }, ); } } },};The following error codes may be returned when sending emails:
| Error Code | Description | Common Causes |
|---|---|---|
E_VALIDATION_ERROR | Validation error in the payload | Invalid email format, missing required fields, malformed data |
E_FIELD_MISSING | Required field is missing | Missing to, from, or subject fields |
E_TOO_MANY_RECIPIENTS | Too many recipients in to/cc/bcc arrays | Combined recipients exceed 50 limit |
E_SENDER_NOT_VERIFIED | Sender domain not verified | Attempting to send from unverified domain |
E_RECIPIENT_NOT_ALLOWED | Recipient not in allowed list | Recipient address not in allowed_destination_addresses |
E_RECIPIENT_SUPPRESSED | Recipient is on suppression list | Email address has bounced or reported your emails as spam |
E_SENDER_DOMAIN_NOT_AVAILABLE | Domain not available for sending | Domain not onboarded to Email Service |
E_CONTENT_TOO_LARGE | Email content exceeds size limit | Total message size exceeds the maximum |
E_DELIVERY_FAILED | Could not deliver the email | SMTP delivery failure, recipient server rejection |
E_RATE_LIMIT_EXCEEDED | Rate limit exceeded | Sending rate limit reached |
E_DAILY_LIMIT_EXCEEDED | Daily limit exceeded | Daily sending quota reached |
E_INTERNAL_SERVER_ERROR | Internal service error | Email Service temporarily unavailable |
E_HEADER_NOT_ALLOWED | Header not allowed | Header is platform-controlled or not on the whitelist |
E_HEADER_USE_API_FIELD | Must use API field | Header like From must be set via the dedicated API field |
E_HEADER_VALUE_INVALID | Header value invalid | Malformed value, empty, or incorrect format |
E_HEADER_VALUE_TOO_LONG | Header value too long | Value exceeds 2,048 byte limit |
E_HEADER_NAME_INVALID | Header name invalid | Invalid characters or exceeds 100 byte limit |
E_HEADERS_TOO_LARGE | Headers payload too large | Total custom headers exceed 16 KB limit |
E_HEADERS_TOO_MANY | Too many headers | More than 20 whitelisted (non-X) custom headers |
The existing EmailMessage API remains supported for backward compatibility:
import { EmailMessage } from "cloudflare:email";import { createMimeMessage } from "mimetext";
export default { async fetch(request: Request, env: Env): Promise<Response> { const msg = createMimeMessage(); msg.setSender({ name: "Sender", addr: "sender@yourdomain.com" }); msg.setRecipient("recipient@example.com"); msg.setSubject("Legacy Email"); msg.addMessage({ contentType: "text/html", data: "<h1>Hello from legacy API</h1>", });
const message = new EmailMessage( "sender@yourdomain.com", "recipient@example.com", msg.asRaw(), );
await env.EMAIL.send(message); return new Response("Legacy email sent"); },};- See the REST API for sending emails without Workers
- See practical examples of email sending patterns
- Learn about email routing for handling incoming emails
- Explore email authentication for better deliverability