The Amazon SES alternative for AI agents — MailKite<br>Beta<br>50% off your first year — founding rate for the first 1,000 customers.<br>View pricing →
Sign in · Start free
All posts
SES<br>Amazon SES<br>vs
MailKite
Amazon SES vs MailKite — the same job (an inbox for your AI agent), two approaches.
An agent’s first job on email is usually the least glamorous one: read a six-digit code out of a signup message and type it back, or take a task someone emailed in and answer it. On SES that job starts with plumbing, not with the agent. Here’s the same inbound email on each side, and everything the agent has to run to act on it.
SES<br>Amazon SES<br>email in<br>SESreceipt rule<br>S3raw MIME<br>SNS<br>Lambdafetch + parse MIME<br>agent
…plus a sandbox approval before the agent can reply to anyone, the IAM roles,<br>and the same-region SNS/Lambda setup you build and keep alive<br>← yours to write and operate
MailKite<br>email in<br>MX edgeparse + auth<br>JSON webhooksigned, retried, replayable<br>agent
the loop below is the whole "agent" box — receive, think, reply
One inbound email reaching an agent: what you operate on SES vs on MailKite. Same input, two very different pipelines.
Everything below is a repo you can run while you read. Clone demo-amazon-ses-ai-agent and npm start, or open it in StackBlitz (real Node in a browser tab, no account) where it runs on load. You don’t need a domain to see the loop run: a webhook normally needs a public URL, so npm start boots the server and self-fires a correctly signed email.received event at its own localhost — the whole receive→think→reply loop runs in one command (the reply is a dry-run, and managed-route.mjs dry-runs the hosted path too). It also ships the SES path next to it in ses-contrast/, so every contrast here is something you can diff and test, not take on faith. A domain comes in only when you want real inbound email to arrive.
Here’s the whole MailKite side: the agent’s receive→think→reply loop. This is the heart of server.mjs in that repo — the repo wraps it in a dry-run harness and a stub agent so it runs with no key and no LLM, but the loop is exactly this, and it runs as pasted on Node 18+, one dependency (npm install mailkite).
// server.mjs — the whole agent loop. Full repo: github.com/mailkite/demo-amazon-ses-ai-agent<br>import { createServer } from "node:http";<br>import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);<br>const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;
createServer(async (req, res) => {<br>let raw = ""; for await (const c of req) raw += c;<br>// signature check, replay window, constant-time compare — one call<br>if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], raw, SECRET)) {<br>return res.writeHead(401).end();<br>res.writeHead(200).end("ok"); // ack fast; run the agent out of band
const event = JSON.parse(raw); // already parsed — no S3, no MIME parser<br>if (event.type !== "email.received") return;
// Body is UNTRUSTED input, not instructions. Weight it by the auth block.<br>const trusted = event.auth.spf === "pass" && event.auth.dmarc === "pass";<br>const reply = await runAgent({ task: event.text, from: event.from.address, trusted });
await mk.send({<br>from: event.to[0].address, // reply from the address it was sent to<br>to: event.from.address,<br>subject: `Re: ${event.subject}`,<br>inReplyTo: event.id, // threads the reply<br>html: reply.html,<br>});<br>}).listen(3000);<br>That’s a fully autonomous email agent: it hears, thinks, and answers. npm start boots the server and self-fires the exact payload MailKite’s delivery worker sends, so in one command you watch the agent read the task, take its trust verdict straight off event.auth, and dry-run the reply — no account or LLM required. mk.send() returns { id, status } so you can log the outbound message, and the identical shape exists for Python, Ruby, Go, PHP, and Java (see the receiving docs and sending docs). Open it in StackBlitz, fire the sample event, and watch a parsed email hit runAgent.
Where SES wins for agents, honestly
SES is cheap and it is capable, and I’m not going to pretend otherwise. Outbound is about $0.10 per 1,000 emails, inbound is roughly the same per 1,000 received, and AWS sending IPs have years of reputation behind them. If your agent already lives in AWS (its brain is a Bedrock call, its state is in DynamoDB, its runtime is Lambda or ECS), then SES is one more service in a console you already operate, billed on one invoice you already read. Inbound receiving is available in most SES regions now, not the tiny handful it used to be, so region availability is rarely the blocker people remember it as. For a team fluent in IAM and CloudWatch, none of the pieces below are exotic. The catch is that “not exotic” and “not your problem” are different things, and for an autonomous agent the difference is most of the work.
What SES asks of an agent builder
Two things stand between an SES account and an agent that can read and answer its own mail. The first is on the way out.
A brand-new SES...