The Amazon SES alternative for developers
SES is the cheapest way to send and the most assembly-required way to receive: sandbox approval, then raw MIME in S3 behind SNS, Lambda, and IAM. MailKite (which we build) sends the moment DNS verifies and delivers inbound as one parsed JSON webhook. Honest comparison, working code, runnable demo.
Here is that difference in one picture: the same inbound email, and everything you operate to receive it on each side. The rest of the post is the honest version of this diagram — where SES genuinely wins, what it costs you in plumbing, and the 25 lines of working code (from a runnable demo repo you can open in your browser) that are the entire MailKite side.
Here is the entire MailKite receiving pipeline. Not a fragment: this runs as pasted on Node 18+, one dependency (npm install mailkite).
// minimal.mjs — the whole pipeline. Full version + tests: github.com/mailkite/demo-amazon-ses-alternative
import { createServer } from "node:http";
import { MailKite } from "mailkite";
const SECRET = process.env.MAILKITE_WEBHOOK_SECRET ?? "whsec_demo_secret";
createServer(async (req, res) => {
let raw = "";
for await (const chunk of req) raw += chunk;
// signature check, replay window, constant-time compare — one call
if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], raw, SECRET)) {
res.writeHead(401).end();
return;
}
const event = JSON.parse(raw); // already parsed email — no S3, no MIME parser
if (event.type === "email.received") {
console.log(event.from.address, "·", event.subject, "·", event.text);
}
res.writeHead(200).end("ok");
}).listen(3000);
(Can’t take a dependency? The repo’s raw-server.mjs is the zero-dependency version — hand-rolled HMAC, replay window, constant-time compare. Our first draft of it got the timestamp unit wrong, which is rather the point of the SDK call.)
Run it without an account, in one command: clone the repo and npm start boots the server and self-fires a correctly signed email.received event at its own localhost — no domain needed — and a parsed email lands in your terminal. Open it in StackBlitz (real Node in your browser tab) to watch it run on load. Tamper with a byte and you get a 401.
Where SES wins, honestly
Per email sent, SES is about the cheapest game anywhere, and AWS sending IPs have years of reputation behind them. If you push millions of transactional messages a month, have AWS muscle in-house, and can staff the plumbing, nothing beats its unit economics. This post isn’t “SES is bad.” It’s “SES makes you the integrator,” and for a lot of developers that trade isn’t worth it.
What SES actually asks of you
The low price tag is real; the operational bill is the part nobody quotes you. Receiving one email is a pipeline you build and keep alive, top to bottom:
Between “signed up” and “reliably sending and receiving” sits the rest of it too:
Here’s the SES version of the 25 lines up top, after you’ve built the receipt rules, bucket policy, SNS topic, and IAM role (ses-contrast/handler.mjs in the demo repo):
// SES inbound: receipt rule → S3 → SNS → this Lambda → parse the MIME yourself
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { simpleParser } from "mailparser";
const s3 = new S3Client({});
export const handler = async (event) => {
const { bucketName, objectKey } = event.Records[0].ses.receipt.action;
const obj = await s3.send(new GetObjectCommand({ Bucket: bucketName, Key: objectKey }));
const mail = await simpleParser(await obj.Body.transformToString());
console.log(mail.from?.text, "·", mail.subject); // attachments, encodings: your job now
};
None of this is exotic if you’re an AWS shop. But it’s a stack of undifferentiated plumbing between you and “an email came in, do something with it.”
The comparison, no adjective inflation
| Amazon SES | MailKite | |
|---|---|---|
| Start sending | Sandboxed until AWS approves | DNS-verify (SPF+DKIM), then send |
| Inbound delivery | Raw MIME → S3 → SNS/Lambda → you parse | One parsed JSON webhook |
| Inbound setup | Receipt rules + S3 + SNS/Lambda + IAM | One webhook URL |
| Bounce/complaint handling | SNS + Lambda + suppression you build | Handled; metered, no surprise pause |
| Inbound dashboard | Build it (CloudWatch/S3) | Logs + one-click replay |
| Per-email price at scale | Among the lowest anywhere | Metered, transparent |
| Deliverability | Excellent AWS IPs | SPF/DKIM/DMARC aligned |
| Human support | Paid AWS Support tier | Included on paid plans |
The through-line: SES wins raw per-email price and world-class sending IPs. MailKite wins time and total cost of ownership: no sandbox wait, no AWS services to run, and inbound that’s already parsed.
What actually hits your webhook
The same inbound email, delivered parsed. No S3 round-trip, no MIME parser, and the auth block means you never re-derive SPF/DKIM/DMARC yourself:
{
"id": "msg_2Hk9…",
"type": "email.received",
"from": { "address": "ada@example.com" },
"to": [{ "address": "support@myapp.ai" }],
"subject": "Re: invoice #1042",
"text": "Looks good — approved!",
"html": "<p>Looks good — approved!</p>",
"threadId": "<a1b2c3@mail.example.com>",
"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },
"attachments": [
{ "id": "msg_2Hk9…:0", "filename": "po.pdf", "contentType": "application/pdf",
"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…" }
]
}
Don’t take the shape on faith — fire one and watch it come back, auth block and all. Spoof the SPF/DKIM to fail and see the verdict flip:
The same handler exists for Python, Ruby, Go, PHP, and Java: see the receiving docs and webhook security. If you’d rather keep SMTP, the submission edge takes AUTH on :587 too.
Where I won’t overclaim
SES is genuinely excellent at the thing it’s for: cheap, high-deliverability, high-volume sending, operated by teams already fluent in AWS. At millions of messages a month its per-email price is hard to walk away from, and I won’t pretend MailKite undercuts it there. My claim is specific: for the developer experience (starting without a sandbox, and receiving mail as parsed JSON instead of raw MIME in a bucket), MailKite removes the AWS pipeline SES makes you build and maintain. We built MailKite because we kept rebuilding that pipeline; below “dedicated deliverability engineer” scale, the trade pays for itself in the plumbing you don’t write. And you don’t have to leave AWS to take it: the webhook is plain HTTPS, so the receiver can be the Lambda you already have.
FAQ
Is MailKite cheaper than Amazon SES? Per email sent at high volume, no: SES is about the cheapest anywhere. But SES receiving adds S3, SNS, and Lambda costs plus the engineering time to build and run the pipeline, and MailKite has no per-domain fee. MailKite starts free (3,000 messages/mo, in + out), so total cost of ownership favors MailKite until you’re sending at large scale.
Can Amazon SES receive email? Yes, in supported regions: receipt rules deliver raw MIME to an S3 bucket and notify SNS/Lambda, then you fetch and parse the MIME yourself. MailKite delivers the already-parsed message as JSON to your webhook.
What is the SES sandbox? Every new SES account is sandboxed: you can only send to verified addresses until AWS reviews your request and grants production access. MailKite has no sandbox; once your domain passes DNS verification (SPF + DKIM), you can send to anyone.
Do I have to leave AWS to use MailKite? No. MailKite works over a plain HTTPS webhook and REST API: call it from Lambda, ECS, EC2, or anywhere else. You’re replacing the SES receiving pipeline (and the sandbox), not your infrastructure.
Does MailKite match SES deliverability? MailKite sends with aligned SPF/DKIM/DMARC on a monitored transactional stream. SES’s AWS IPs have a long, strong reputation; at very high volume with a warmed dedicated IP, SES has the edge. For typical transactional and inbound-heavy workloads, MailKite’s placement is built to land in the inbox.
If SES has you maintaining a receipt-rule-to-S3-to-Lambda pipeline just to read an email, or waiting on a sandbox approval to send one, there’s a simpler shape. Clone the demo repo (or run it in your browser), then point a domain at MailKite and your next inbound email arrives as parsed JSON. Found a case the demo doesn’t cover? Open an issue.
Related: the full MailKite vs Amazon SES comparison, the pillar on why receiving email is hard, and why deliverability feels like a black box.