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 a send_email binding in your Wrangler configuration file to enable email sending:
{ "send_email": [{ "name": "EMAIL" }],}[[send_email]]name = "EMAIL"You can restrict which senders and recipients a binding may use. Refer to Configure send bindings for the available restriction attributes and examples.
Send a single email using the send() method on your email binding.
interface SendEmail { send(message: EmailMessage | EmailMessageBuilder): Promise<EmailSendResult>;}
interface EmailAddress { email: string; name?: string;}
// Structured email builder (recommended)interface EmailMessageBuilder { to: string | EmailAddress | (string | EmailAddress)[]; // Max 50 recipients from: string | EmailAddress; subject: string; html?: string; text?: string; cc?: string | EmailAddress | (string | EmailAddress)[]; bcc?: string | EmailAddress | (string | EmailAddress)[]; replyTo?: string | EmailAddress; attachments?: Attachment[]; // Custom headers. See /email-service/reference/headers/ headers?: { [key: string]: string }; // The combined number of addresses in `to`, `cc`, and `bcc` must not // exceed 50. See /email-service/platform/limits/ for all limits.}
interface Attachment { content: string | ArrayBuffer | ArrayBufferView; // 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) }const response = await env.EMAIL.send({ to: "recipient@example.com", from: "welcome@yourdomain.com", subject: "Welcome to our service!", html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>", text: "Welcome! Thanks for signing up.",});For multiple recipients, CC/BCC, and named addresses, see Specify recipients.
Send files by including base64-encoded content in the attachments array. The total message size must not exceed 5 MiB (including attachments).
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", }, ],});For inline images and file uploads, see Email attachments.
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_TOO_MANY_ATTACHMENTS | Too many attachments in attachments array | attachments array exceeds 32 entries |
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 allowlist |
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 allowlisted (non-X) custom headers |
The EmailMessage API remains supported for backward compatibility. Use it when you already have a raw RFC 5322 ↗ MIME message to send. For new code, prefer the structured send() method above.
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 SMTP for sending from any SMTP-capable application or mail client
- See practical examples of email sending patterns
- Learn about email routing for handling incoming emails
- Explore email authentication for better deliverability