MailKite
Get your API key
All guides Sending email

Send email with Cloudflare

Transactional email is the mail your app sends one recipient at a time — receipts, password resets, magic links, notifications. This guide sends one through Cloudflare Email Service — from a Worker, the REST API, or SMTP — then shows the same send on MailKite.

What you'll need

  • A domain in your Cloudflare account, with access to its DNS.
  • A Worker project (Wrangler); for the REST API or SMTP, a Cloudflare API token with email sending permission.

Email Sending is the transactional half of Cloudflare Email Service. At the time of writing it is in beta on the Workers Paid plan; check the Email Service docs for current availability and limits.

Part 1 — Send with Cloudflare

1. Onboard your sending domain

In the dashboard, go to Compute & AI → Email Service → Email Sending and select Onboard Domain. Because the domain is already on Cloudflare, it writes the authentication records for you — an SPF include, a DKIM key on the cf-bounce selector, a DMARC policy, and an MX record that routes bounces back to Cloudflare:

DNS — yourdomain.com
Type   Name                                  Value
TXT yourdomain.com v=spf1 include:_spf.mx.cloudflare.net ~all
TXT cf-bounce._domainkey.yourdomain.com (DKIM key — written for you on onboard)
TXT _dmarc.yourdomain.com v=DMARC1; p=none;
MX cf-bounce.yourdomain.com (bounce route — written for you on onboard)

The same setup is available from the CLI with npx wrangler email sending enable yourdomain.com. DNS usually propagates within a few minutes.

2. Send from a Worker

Declare a send binding in your Wrangler config. It needs no API key — the binding is the credential:

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

Then call env.EMAIL.send() inside your handler with a structured message. The current binding takes to/from/ subject/html/text directly — no MIME to assemble. The from address must be on the domain you onboarded:

src/index.js
export default {
async fetch(request, env) {
const res = await env.EMAIL.send({
to: "ada@example.com",
from: "hello@yourdomain.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Your receipt is attached.</p>",
text: "Thanks! Your receipt is attached.",
});
return new Response(`Sent: ${res.messageId}`);
},
};

A raw-MIME path also exists — EmailMessage from cloudflare:email built with the mimetext library — but the structured send() above is the current, simpler form.

3. Or send over the REST API

From outside Workers, POST to the Email Sending endpoint with an API token. The body is the same flat shape as the binding:

send
curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/email/sending/send" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@example.com",
"from": "hello@yourdomain.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Your receipt is attached.</p>",
"text": "Thanks! Your receipt is attached."
}'
response
200 OK
{ "success": true,
"result": { "delivered": ["ada@example.com"], "queued": [], "permanent_bounces": [] } }

4. Or send over SMTP

Cloudflare also exposes an SMTP relay. The username is the literal api_token and the password is your API token:

smtp
Host:      smtp.mx.cloudflare.net
Port: 465 # SMTPS (implicit TLS)
Username: api_token
Password: <your Cloudflare API token>
From: hello@yourdomain.com

That's a transactional send on Cloudflare: onboard the domain, then call the Worker binding, the REST API, or the SMTP relay.

Part 2 — The same, on MailKite

MailKite sends from any runtime over a plain HTTP POST or SMTP — no Worker to deploy, no MIME to assemble — and the same verified domain also receives. When we built sending into MailKite, we wanted three things to be true: one set of DNS records, one API key that works for both HTTP and SMTP, and a body you don't have to nest. Here's the same message.

1. Authenticate your domain

Publish three TXT records — SPF, a MailKite DKIM key, and DMARC. The same domain also receives mail once its MX points at MailKite:

DNS — yourdomain.com
Type   Name                              Value
TXT yourdomain.com v=spf1 include:mailkite.dev ~all
TXT mailkite._domainkey.yourdomain.com v=DKIM1; k=rsa; p=…
TXT _dmarc.yourdomain.com v=DMARC1; p=none;

The exact DKIM value is shown in the dashboard; MailKite verifies the records for you over DNS.

2. Send over the API

One POST /v1/send with a flat from/to/ subject/html body. A 202 means it's queued:

Pick your language in the card — the dashboard and the AI assistant do the same send, and every SDK takes the same fields:

send
import { MailKite } from "mailkite";

const mk = new MailKite(process.env.MAILKITE_API_KEY);

const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
Install Docs →
response
202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }

3. Or send over SMTP

The same key is your SMTP password — no separate credential to generate:

smtp
import nodemailer from "nodemailer";

const tx = nodemailer.createTransport({
host: "smtp.mailkite.dev",
port: 587, // STARTTLS · use 465 for implicit TLS
auth: {
user: "mailkite", // any username works
pass: process.env.MAILKITE_API_KEY, // your mk_live_… key
},
});

await tx.sendMail({
from: "hello@myapp.ai", // an address on a verified domain
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});

See Send API and SMTP relay for the full reference — attachments, headers, and delivery events.

A little about MailKite

MailKite is programmable email for developers and AI agents. One API key and one set of DNS records cover both directions: send with a single POST (or the SMTP relay), and the same verified domain receives replies as a signed webhook of clean JSON. Behind the send, MailKite routes across multiple providers to keep you in the inbox — and any address can be handed to an agent that reads and replies on its own. Sending and receiving are on the free tier.

Next: the Send API for attachments and delivery tracking, or the Quickstart to get a verified domain and API key.