Skip to content

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.

Email binding

Configure email bindings in your Wrangler configuration file to enable email sending:

JSONC
{
"$schema": "./node_modules/wrangler/config-schema.json",
"send_email": [
{
"name": "EMAIL"
},
{
"name": "RESTRICTED_EMAIL",
"allowed_sender_addresses": [
"noreply@yourdomain.com",
"support@yourdomain.com"
]
}
]
}

send() method

Send a single email using the send() method on your email binding.

Interface

TypeScript
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) }

Basic usage

TypeScript
interface Env {
EMAIL: SendEmail;
}

Attachments

TypeScript
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",
},
],
});

Error handling

Handle email sending errors gracefully:

TypeScript
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 },
);
}
}
},
};

Error codes

The following error codes may be returned when sending emails:

Error CodeDescriptionCommon Causes
E_VALIDATION_ERRORValidation error in the payloadInvalid email format, missing required fields, malformed data
E_FIELD_MISSINGRequired field is missingMissing to, from, or subject fields
E_TOO_MANY_RECIPIENTSToo many recipients in to/cc/bcc arraysCombined recipients exceed 50 limit
E_SENDER_NOT_VERIFIEDSender domain not verifiedAttempting to send from unverified domain
E_RECIPIENT_NOT_ALLOWEDRecipient not in allowed listRecipient address not in allowed_destination_addresses
E_RECIPIENT_SUPPRESSEDRecipient is on suppression listEmail address has bounced or reported your emails as spam
E_SENDER_DOMAIN_NOT_AVAILABLEDomain not available for sendingDomain not onboarded to Email Service
E_CONTENT_TOO_LARGEEmail content exceeds size limitTotal message size exceeds the maximum
E_DELIVERY_FAILEDCould not deliver the emailSMTP delivery failure, recipient server rejection
E_RATE_LIMIT_EXCEEDEDRate limit exceededSending rate limit reached
E_DAILY_LIMIT_EXCEEDEDDaily limit exceededDaily sending quota reached
E_INTERNAL_SERVER_ERRORInternal service errorEmail Service temporarily unavailable
E_HEADER_NOT_ALLOWEDHeader not allowedHeader is platform-controlled or not on the whitelist
E_HEADER_USE_API_FIELDMust use API fieldHeader like From must be set via the dedicated API field
E_HEADER_VALUE_INVALIDHeader value invalidMalformed value, empty, or incorrect format
E_HEADER_VALUE_TOO_LONGHeader value too longValue exceeds 2,048 byte limit
E_HEADER_NAME_INVALIDHeader name invalidInvalid characters or exceeds 100 byte limit
E_HEADERS_TOO_LARGEHeaders payload too largeTotal custom headers exceed 16 KB limit
E_HEADERS_TOO_MANYToo many headersMore than 20 whitelisted (non-X) custom headers

Legacy EmailMessage API

The existing EmailMessage API remains supported for backward compatibility:

TypeScript
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");
},
};

Next steps