The Amazon SES alternative for developers
Amazon SES is the cheapest way to send at scale and the most assembly-required way to do anything else: sending starts sandboxed behind an AWS approval, bounce and complaint handling is your Lambda to write, and receiving means raw MIME dropped in an S3 bucket you fetch and parse yourself. Here's a fair comparison, and what MailKite does differently — send the moment DNS verifies, inbound as parsed JSON, no S3/SNS/Lambda/IAM.
Amazon SES (Simple Email Service) is AWS’s raw email API: about the lowest per-message price anywhere and rock-solid deliverability on AWS IPs — but you assemble the developer experience yourself. New accounts start sandboxed (you can only email verified addresses until AWS reviews and grants production access), bounce and complaint handling is an SNS-topic-plus-Lambda you build, and receiving mail means wiring receipt rules to drop raw MIME in S3, pinging SNS/Lambda, then fetching and parsing the MIME yourself. MailKite is the developer-experience alternative: send the moment your domain passes DNS verification (no sandbox), and inbound arrives as one parsed JSON webhook — no S3, SNS, Lambda, or IAM.
I’ll be fair to SES, because it earns its place. Per email sent, it’s the cheapest game in town, the AWS sending IPs have excellent reputation, and if you’re pushing millions of transactional messages a month and have the AWS muscle to operate it, nothing beats the 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. To get from “signed up” to “reliably sending and receiving,” you typically wire up:
- The sandbox. Every new SES account can only send to addresses you’ve verified, until you file for production access and AWS approves it. Approvals are a review, not a switch — and when they’re denied, the reason is often “for security purposes, we can’t share specifics,” which is a rough place to be with a launch on the calendar.
- IAM, identities, and regions. Verified identities, IAM roles and policies, and per-region setup (SES receiving only exists in some regions at all).
- A dashboard you build. SES has no real inbound console. Visibility means stitching CloudWatch, SNS, and S3 together yourself.
- Bounce and complaint handling. You subscribe SNS topics, process notifications (usually a Lambda), and maintain suppression — because if your bounce or complaint rate drifts up, AWS will pause your sending.
- Receiving. This is the big one. SES inbound doesn’t POST you a message. It runs a receipt rule that writes the raw MIME to an S3 bucket and notifies SNS/Lambda; your code then fetches the object from S3 and runs a MIME parser to get headers, body, and attachments. You own that pipeline and its failure modes.
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 honest comparison
No adjective inflation — here’s the shape of the difference:
| 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 has 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
Same inbound email, delivered parsed — no S3 round-trip, no MIME parser:
{
"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=…" }
]
}
Inbound: the two shapes side by side
Receiving one email on SES is a pipeline you build:
// SES inbound: receipt rule → S3 → Lambda → parse MIME yourself
// (after creating the rule set, S3 bucket + IAM policy, and SNS/Lambda wiring)
import { simpleParser } from "mailparser";
export const handler = async (event) => {
const { bucketName, objectKey } = event.Records[0].ses.receipt.action; // S3 target
const obj = await s3.getObject({ Bucket: bucketName, Key: objectKey }).promise();
const mail = await simpleParser(obj.Body.toString("utf8")); // parse the raw MIME
handle(mail.from.text, mail.subject, mail.text, mail.attachments);
};
The same thing on MailKite is verify-signature, read-the-fields:
// Express
import express from "express";
import { MailKite } from "mailkite";
const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;
app.use("/hooks/mailkite", express.raw({ type: "application/json" })); // raw bytes for HMAC
app.post("/hooks/mailkite", (req, res) => {
const sig = req.headers["x-mailkite-signature"];
if (!MailKite.verifyWebhook(sig, req.body, SECRET)) return res.sendStatus(401);
const event = JSON.parse(req.body);
if (event.type === "email.received" && event.auth.spf === "pass") {
console.log(event.from.address, "·", event.subject);
// event.text / event.html already decoded; attachments are signed URLs.
}
res.sendStatus(200); // ack fast; heavy work out of band
});
app.listen(3000);
No bucket, no getObject, no MIME parser to keep alive. The same handler exists for Python, Ruby, Go, PHP, and Java — see the receiving docs and webhook security.
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. If that’s you and you’re at real scale, its per-email price is hard to walk away from, and I won’t pretend MailKite undercuts it at millions of messages a month. 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. For most teams below “dedicated deliverability engineer” scale, that trade pays for itself in the time you don’t spend on plumbing.
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 there’s no per-domain fee on MailKite either. 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: you create receipt rules that deliver the raw MIME to an S3 bucket and notify SNS/Lambda, then fetch and parse the MIME yourself. MailKite delivers the already-parsed message as JSON to your webhook — no S3, no Lambda, no MIME parser.
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 runs on your domain over a plain HTTPS webhook and REST API — call it from Lambda, ECS, EC2, or anywhere else. You’re just 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. Point a domain at MailKite and your next inbound email arrives as parsed JSON.
Related: the full MailKite vs Amazon SES comparison, the pillar on why receiving email is hard, and why deliverability feels like a black box.