Compare
The Cloudflare Email alternative on any DNS
Send over REST or SMTP from a platform that posts signed webhooks straight to your endpoint. Your domain keeps the DNS provider it already has.
What arrives assembled
Cloudflare Email Service sells the send. Everything around it is yours to build.
| Comes with | mailkube | Cloudflare Email | Docs |
|---|---|---|---|
| Webhooks | Yes | Queues | Docs(opens in a new tab) |
| Stored templates, versioned | Yes | Docs(opens in a new tab) | |
| Scheduled sending | 30 days | Docs(opens in a new tab) | |
| Idempotency keys | Yes | Docs(opens in a new tab) | |
| Framework packages | Django, Laravel, Rails, Spring Boot | Docs(opens in a new tab) |
Four of those rows say No, and Cloudflare’s public documentation describes none of them. The webhook row is the sharpest. A delivery event there never reaches your endpoint. You create a queue, subscribe it to one sending domain, then write a Worker to read it. Forwarding that event to your own server is your code, and so is the signature on it.
One requirement sits before all of that. Email Service runs only on a domain whose DNS is already at Cloudflare. Their documentation describes no path for a domain hosted anywhere else.
A new account also sends against a daily quota Cloudflare never publishes. It rises with your sending behaviour, your deliverability and your account standing.
Feature by feature
The rows below hold what each platform documents today. A docs link opens the mailkube page behind that row.
| Feature | mailkube | Cloudflare Email | Docs |
|---|---|---|---|
| Where account data is stored | European Union | ||
| Free plan | 4,000 a month, 140 a day | ||
| Dedicated IPs | Included from Elite | Docs(opens in a new tab) | |
| Per-topic opt-out on a send | Yes | Docs(opens in a new tab) | |
| Outbound body and link scan | Yes | ||
| Webhook event types | 11 | 6 | Docs(opens in a new tab) |
| REST API | Yes | Yes | Docs(opens in a new tab) |
| SMTP relay | Yes | Yes | Docs(opens in a new tab) |
| Official SDKs | Node, Python, Go, Java, PHP, Ruby | Node, Python, Go | Docs(opens in a new tab) |
| CLI | Yes | Yes | Docs(opens in a new tab) |
Dedicated IPs are included from the Elite plan. Elite carries one at 100,000 emails a month, and Volume carries six at 750,000. Each new one warms on a six-stage ladder, from 50 messages a day to 10,000. Cloudflare manages reputation on shared addresses and documents no dedicated IP at all.
Email Sending is in public beta. The documentation index carries the badge and the pricing page carries none.
Where your data lives
Customer data is stored in the European Union. Our infrastructure runs in European data centres, in the French OVHcloud group.
Cloudflare’s Email Service documentation names no region for the messages it handles. Message preview is on for a new sending domain by default, and it keeps the message about seven days.
The detail is in the data processing agreement.
Two transports, headers included
POST to the REST API, or point the SMTP client you already run at smtp.mailkube.com. Everything the API takes as a JSON field, SMTP takes as a header. Those headers are stripped before the message reaches the recipient.
X-Mailkube-Tagscarries the same tag list as the RESTtagsfieldX-Mailkube-Topicattributes the send to a topic, so recipients who opted out are filteredX-Mailkube-Template-IdandX-Mailkube-Template-Versionrender a saved template server-side
An SMTP send is the same message as an API send. It lands in the same logs, counts against the same quota and fires the same webhook events. The SMTP reference(opens in a new tab) lists every header, reply code and rate limit.
Cloudflare relays SMTP on smtp.mx.cloudflare.net, on port 465 under implicit TLS, with the literal string api_token as the username. Its send API takes a headers object, and any header starting with X- travels with the message. Its delivery analytics carry no dimension for those headers, so a tag set there filters nothing afterwards. Cloudflare’s own documentation adds that Email Service does not process unsubscribe links, and asks you to suppress those recipients yourself.
One more difference sits on the send itself. Every mailkube send takes an idempotency key(opens in a new tab) , and a retried call returns the first result instead of sending twice. The Cloudflare API documents no such key, and a retried call sends a second message.
Your stack, already covered
Official SDKs cover Node, Python, Go, Java, PHP and Ruby. Django, Laravel, Rails and Spring Boot each have a package of their own. It plugs into the framework’s mail layer, and every client verifies your webhook signatures for you.
Cloudflare publishes three client libraries, for Node, Python and Go. Each one covers its whole platform rather than email alone. It publishes no framework package, so a Django or Laravel project wires the transport itself.
The CLI is a single binary. It sends over either transport and manages scheduled sends. While you develop, it runs a local webhook receiver. It carries a skill file, and a coding assistant reads the real command surface. Start from the SDK documentation(opens in a new tab) or the CLI guide(opens in a new tab) .
Migrating from Cloudflare Email
The Node SDK runs on Workers with no compatibility flag to set. Keep the Worker, swap the client, and read the key from the same env binding.
The same send, Cloudflare Email on the left and mailkube on the right
Cloudflare Email
export default {
async fetch(request, env) {
await env.EMAIL.send({
from: { email: "you@example.com", name: "Your app" },
to: [{ email: "customer@example.com" }],
subject: "Hello world",
text: "It works!",
html: "<p>It works!</p>",
});
return new Response("sent");
},
};
- Host
- smtp.mx.cloudflare.net
- Port
- 465
mailkube
import { Mailkube } from "@mailkube/mailkube-node";
export default {
async fetch(request, env) {
const mailkube = new Mailkube(env.MAILKUBE_API_KEY);
await mailkube.emails.send({
from: "you@example.com",
to: "customer@example.com",
subject: "Hello world",
html: "<p>It works!</p>",
});
return new Response("sent");
},
};
- Host
- smtp.mailkube.com
- Port
- 587
SMTP takes one change more than the usual two. Move the host and the credential, then move the port from 465 to 587, where your mailer negotiates TLS on a session it has already opened. The messages you already send go out unchanged, and tags, topics and templates arrive as headers when you want them. Nothing forces a cutover date: run both platforms side by side and move one service at a time. The quickstart(opens in a new tab) covers the REST path and the SMTP guide(opens in a new tab) covers the relay.
When Cloudflare Email is the better fit
Email Service lives inside Cloudflare’s developer platform. A Worker sends through a binding with no API key to manage. Wrangler drives it from the same configuration as the rest of a deployment, and one account and one bill cover everything. A team already building there inherits all of it.
Authentication is configured for you as well. Cloudflare writes the SPF, DKIM and DMARC records when you onboard a domain, and you add nothing by hand. Here you place those records at your own provider, wherever that is.
The pricing page details every plan. The REST API and SMTP relay pages go deeper on the two transports.
Frequently asked questions
What does Cloudflare Email ask for that mailkube does not?
How do delivery events reach my server?
Is Cloudflare Email Sending finished?
Can I keep my SMTP configuration?
Can I schedule a send?
Where do the Cloudflare figures come from?
Send your first email today
Start free, no card. Point an existing SMTP client at us, or install an SDK and make one POST.