MailKite
Get started
All posts
Gabe 5 min read

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 SESMailKite
Start sendingSandboxed until AWS approvesDNS-verify (SPF+DKIM), then send
Inbound deliveryRaw MIME → S3 → SNS/Lambda → you parseOne parsed JSON webhook
Inbound setupReceipt rules + S3 + SNS/Lambda + IAMOne webhook URL
Bounce/complaint handlingSNS + Lambda + suppression you buildHandled; metered, no surprise pause
Inbound dashboardBuild it (CloudWatch/S3)Logs + one-click replay
Per-email price at scaleAmong the lowest anywhereMetered, transparent
DeliverabilityExcellent AWS IPsSPF/DKIM/DMARC aligned
Human supportPaid AWS Support tierIncluded 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.

Discuss this post: Hacker News Share on X

Related posts