The Amazon SES alternative for developers — MailKite Beta<br>50% off your first year — founding rate for the first 1,000 customers.<br>View pricing →
Sign in · Get started
All posts<br>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 SESMailKiteStart sendingSandboxed until AWS approvesDNS-verify (SPF+DKIM), then sendInbound deliveryRaw MIME → S3 → SNS/Lambda → you parseOne parsed JSON webhookInbound setupReceipt rules + S3 + SNS/Lambda + IAMOne webhook URLBounce/complaint handlingSNS + Lambda + suppression you buildHandled; metered, no surprise pauseInbound dashboardBuild it (CloudWatch/S3)Logs + one-click replayPer-email price at scaleAmong the lowest anywhereMetered, transparentDeliverabilityExcellent AWS IPsSPF/DKIM/DMARC alignedHuman supportPaid AWS Support tierIncluded on paid plans<br>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…",<br>"type": "email.received",<br>"from": { "address": "ada@example.com" },<br>"to": [{ "address": "support@myapp.ai" }],<br>"subject": "Re: invoice #1042",<br>"text": "Looks good — approved!",<br>"html": "Looks good — approved!",<br>"threadId": "",<br>"auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },<br>"attachments": [<br>{ "id": "msg_2Hk9…:0", "filename": "po.pdf", "contentType": "application/pdf",<br>"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…" }<br>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<br>// (after creating the rule set, S3 bucket + IAM policy, and SNS/Lambda wiring)<br>import { simpleParser } from "mailparser";
export const handler = async (event) => {<br>const { bucketName, objectKey } = event.Records[0].ses.receipt.action; // S3 target<br>const obj = await s3.getObject({ Bucket: bucketName, Key: objectKey }).promise();<br>const mail = await simpleParser(obj.Body.toString("utf8")); // parse the raw MIME<br>handle(mail.from.text, mail.subject, mail.text, mail.attachments);<br>};<br>The same thing on MailKite is verify-signature, read-the-fields:
// Express<br>import express from "express";<br>import { MailKite }...