cd /news/ai-agents/the-amazon-ses-alternative-for-ai-ag… · home topics ai-agents article
[ARTICLE · art-47495] src=mailkite.dev ↗ pub= topic=ai-agents verified=true sentiment=· neutral

The Amazon SES alternative for AI agents

MailKite launches an Amazon SES alternative for AI agents, providing parsed JSON inbound emails and a receive-think-reply loop without the need for MIME parsing or S3 buckets, while SES requires sandbox approval and manual plumbing for autonomous email handling.

read9 min views1 publishedJul 4, 2026
The Amazon SES alternative for AI agents
Image: Mailkite (auto-discovered)

Amazon SES can send and receive, but it hands an autonomous agent a human-reviewed sandbox before it can email anyone and raw MIME in an S3 bucket before it can read a reply. MailKite (which we build) gives the agent a real inbox: inbound arrives as parsed JSON and a receive→think→reply loop runs with no pipeline to operate. For developers wiring an agent to email.

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.

Here’s the whole MailKite side: the agent’s receive→think→reply loop. 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
import { createServer } from "node:http";
import { MailKite } from "mailkite";

const mk = new MailKite(process.env.MAILKITE_API_KEY);
const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;

createServer(async (req, res) => {
  let raw = ""; for await (const c of req) raw += c;
  // signature check, replay window, constant-time compare — one call
  if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], raw, SECRET)) {
    return res.writeHead(401).end();
  }
  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
  if (event.type !== "email.received") return;

  // Body is UNTRUSTED input, not instructions. Weight it by the auth block.
  const trusted = event.auth.spf === "pass" && event.auth.dmarc === "pass";
  const reply = await runAgent({ task: event.text, from: event.from.address, trusted });

  await mk.send({
    from: event.to[0].address,       // reply from the address it was sent to
    to: event.from.address,
    subject: `Re: ${event.subject}`,
    inReplyTo: event.id,             // threads the reply
    html: reply.html,
  });
}).listen(3000);

That’s a fully autonomous email agent: it hears, thinks, and answers. 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 account is in the sandbox. Until you request production access and AWS grants it, the account can only send to verified addresses, at most 200 messages per 24 hours and 1 per second. Production access is a human review: AWS commits to an initial response within 24 hours, not an approval, and says it may take longer if it needs more information. The sandbox is also per-region, so an agent that deploys to a second region is sandboxed again there. For an agent whose whole point is to email an arbitrary person on its first run (confirm a booking, reply to a lead, answer a support thread), that’s a gate with no SLA sitting exactly where launch day is.

The second is on the way in. SES can receive email, but it does not hand you a parsed webhook. A receipt rule writes the raw, unmodified MIME to an S3 bucket and notifies you over SNS or by invoking a Lambda. The Lambda event carries headers and metadata but not the message body, so the function has to fetch the object from S3 and parse the MIME itself. (If you route the whole email inline through SNS instead, anything over 150 KB bounces, so real messages with attachments force the S3 round-trip anyway.) Every resource in that chain (the SNS topic, the Lambda, a KMS key if you encrypt) has to live in the same region as the receiving endpoint.

Here is the inbound half, honestly, in SES’s own idiom, after you’ve built the receipt rule, bucket policy, SNS topic, and IAM role:

// SES inbound: receipt rule → S3 → this Lambda → parse the MIME yourself, then act
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { simpleParser } from "mailparser";

const s3 = new S3Client({});

export const handler = async (event) => {
  // the event has headers but no body — go get the raw email from S3
  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());

  const code = mail.text?.match(/\b\d{6}\b/)?.[0];  // your parsing, your regex, your job
  // ...now run the agent. And to actually email a reply back, the account
  //    must already be OUT of the SES sandbox. See the outbound half.
};

That runs, and it’s the pattern AWS itself documents. But it’s the visible tip. Below the agent sit the receipt rules, the bucket lifecycle, the SNS subscriptions, the IAM policies, the MIME parser’s edge cases (encodings, multipart, attachments), and separately a bounce-and-complaint pipeline you subscribe to and act on, because if those rates drift up AWS s your sending. That is the pipeline the agent needs standing before it reads one code.

The comparison, agent-relevant rows only #

Amazon SES MailKite
Agent’s first reply Blocked until AWS approves production access Send once the domain’s DNS verifies
Reading an inbound email Raw MIME → S3 → SNS/Lambda → you parse One parsed JSON webhook
Verification codes / magic links Parse them out of MIME yourself event.text / event.html , already decoded
Threading a reply Reconstruct from headers inReplyTo: event.id , resolved threadId
Trust signal for injection defense Derive SPF/DKIM/DMARC yourself auth block in every payload
Setup to receive Receipt rules + S3 + SNS/Lambda + IAM One webhook URL
Who runs the loop You (and the pipeline under it) You, or a route with action: "agent"
Free tier ~$200 in credits, 6 months (new accounts) 3,000 messages/mo, in + out, no per-domain fee

The through-line: SES wins raw per-email price and AWS-grade sending IPs. For an agent, MailKite wins the two things it actually blocks on: sending without a review, and reading mail without a pipeline.

What actually hits your agent’s webhook #

The same inbound email, delivered parsed. No S3 round-trip, no MIME parser, and an auth

block so the agent never has to re-derive SPF/DKIM/DMARC to decide how much to trust the sender:

{
  "id": "msg_2Hk9…",
  "type": "email.received",
  "from": { "address": "ada@example.com" },
  "to": [{ "address": "agent@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=…" }
  ]
}

That auth

block is load-bearing, and not because it’s convenient. Inbound email is untrusted input: From:

is plain text, so anyone can forge a sender and then tell your agent what to do in the body. Checking auth

before you weight a sender’s instructions is necessary but not sufficient. You can’t prompt your way out of prompt injection; the real defense is bounding what a fooled agent can do. That reasoning has its own post (agent inbox security by design), and it’s worth reading before either loop touches anything that matters.

If you’d rather not host the loop at all, MailKite, which we build, can run the agent for you: a route whose action

is "agent"

carries a free-text agentPrompt

, and each inbound message runs the model loop on a durable queue with a full transcript you can drill into. Same parsed inbound edge, no server of yours in the path. The inbox-agents docs have the shape.

FAQ #

Can a new Amazon SES account send email right away? No. New accounts are sandboxed: you can only send to verified addresses, capped at 200 messages a day and 1 per second, until you request production access and AWS approves it. That approval is a human review with an initial response within 24 hours and no guaranteed turnaround. MailKite has no sandbox; once your domain passes SPF + DKIM, an agent can send to anyone.

How does an agent read a verification code from SES? It doesn’t get it handed over. SES writes the raw MIME email to S3 and invokes a Lambda whose event has no body, so the Lambda fetches the object from S3 and parses the MIME (commonly with mailparser

) to find the code. MailKite delivers the decoded text

and html

in the email.received

webhook, so the code is a field, not a parsing job.

Does SES give me a parsed inbound webhook like MailKite? No. SES inbound actions deliver the raw, unmodified message to S3 or over SNS, and you own the parsing. MailKite decodes the message at the edge and POSTs parsed JSON with text

, html

, a resolved threadId

, attachments as signed URLs, and an auth

result.

Do I have to leave AWS to use MailKite? No. MailKite is a plain HTTPS webhook and REST API, so the receiver can be the Lambda, ECS task, or EC2 box your agent already runs on. You’re replacing the SES receiving pipeline and the sandbox, not your infrastructure.

Is SES cheaper than MailKite? Per email at high volume, SES is about the cheapest anywhere, and I won’t pretend otherwise. But its inbound path adds S3, SNS, and Lambda costs plus the engineering time to build and operate the pipeline, and MailKite starts free at 3,000 messages a month in and out with no per-domain fee. For an agent workload, total cost of ownership favors MailKite until you’re sending at real scale.

If SES has your agent waiting on a sandbox approval to send its first reply, or maintaining a receipt-rule-to-S3-to-Lambda pipeline just to read a code, there’s a simpler shape. Clone the demo repo (or run it in your browser), then point a domain at MailKite and your agent’s next inbound email arrives as parsed JSON.

Related: the pillar on giving your agent an inbox, agent inbox security by design, the full MailKite vs Amazon SES comparison, and the SES alternative for developers.

── more in #ai-agents 4 stories · sorted by recency
── more on @mailkite 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-amazon-ses-alter…] indexed:0 read:9min 2026-07-04 ·