# The SMTP2GO alternative for AI agents

> Source: <https://mailkite.dev/blog/smtp2go-for-ai-agents/>
> Published: 2026-07-04 00:00:00+00:00

# The SMTP2GO alternative for AI agents

SMTP2GO is a clean, drop-in SMTP relay for sending, but it can't receive email at all, so an agent that has to read verification codes or replies needs a second provider bolted on. MailKite (which we build) gives the agent a real inbox as parsed JSON with a receive→reply loop, and sends too. For developers wiring an autonomous agent to email.

An autonomous agent’s email life is a loop: it sends a signup, an email comes back with a verification code, it reads the code, it continues. SMTP2GO covers exactly one arrow of that loop. It’s a send-only SMTP relay and API. Its webhooks report what happened to mail *you sent* (`delivered`

, `open`

, `click`

, `bounce`

, `spam`

), and its own support docs tell you to use your mailbox provider’s IMAP/POP for anything incoming. So the moment your agent needs to *read* mail, SMTP2GO isn’t in the picture and you’re standing up a second provider to do the receiving.

Here’s the whole MailKite side of that loop: receive, think, reply. It runs as pasted on Node 18+ (`npm install mailkite express`

), and it’s lifted straight from [our agent-inbox pillar](/blog/give-your-agent-an-inbox/) because the shape doesn’t change per provider.

``` python
import express from "express";
import { MailKite } from "mailkite";

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

app.use("/hooks/agent", express.raw({ type: "application/json" }));

app.post("/hooks/agent", async (req, res) => {
  // signature check, replay window, constant-time compare — one call
  if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], req.body, SECRET)) {
    return res.sendStatus(401);
  }
  res.sendStatus(200); // ack fast; run the agent out of band

  const event = JSON.parse(req.body);
  if (event.type !== "email.received") return;

  // Body is untrusted INPUT, never instructions (see the security note below).
  const answer = await runAgent({
    task: event.text,
    from: event.from.address,
    trusted: event.auth.spf === "pass" && event.auth.dmarc === "pass",
  });

  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: answer.html,
  });
});

app.listen(3000);
```

That’s a complete inbound-capable agent: it hears, thinks, and answers, and the identical handler shape exists for Python, Ruby, Go, PHP, and Java (see the [receiving docs](/docs/receiving) and [sending docs](/docs/sending)). The companion [demo repo](https://github.com/mailkite/demo-smtp2go-ai-agent) has the runnable version. [Open it in StackBlitz](https://stackblitz.com/github/mailkite/demo-smtp2go-ai-agent?file=server.mjs), run `npm start`

, and fire the sample event to watch a parsed email land.

## Where SMTP2GO wins for agents, honestly

Don’t rip out SMTP2GO if all your agent does is *send*. For pure outbound it’s genuinely one of the least fussy relays around:

The line is clean: if the agent only emits messages, SMTP2GO is a fine, minimal choice and MailKite doesn’t undercut it on relay simplicity. This post is about the other half of the loop.

## What SMTP2GO asks of an agent builder

The instant your agent needs an inbox (a signup emails back a code, a customer replies, a task arrives by email), SMTP2GO has no seam for it. You run a second provider for receiving, and you own the plumbing between them:

``` python
// Outbound is easy — SMTP2GO is a drop-in relay.
import nodemailer from "nodemailer";
const relay = nodemailer.createTransport({
  host: "mail.smtp2go.com", port: 587,
  auth: { user: process.env.SMTP2GO_USER, pass: process.env.SMTP2GO_PASS },
});
await relay.sendMail({ from: "agent@myapp.ai", to, subject, html });

// Inbound has no SMTP2GO path. You stand up a SECOND mailbox and poll it:
import { ImapFlow } from "imapflow";
import { simpleParser } from "mailparser";

const imap = new ImapFlow({
  host: "imap.fastmail.com", port: 993, secure: true,
  auth: { user: process.env.IMAP_USER, pass: process.env.IMAP_PASS },
});
await imap.connect();
const lock = await imap.getMailboxLock("INBOX");
for await (const msg of imap.fetch({ seen: false }, { source: true })) {
  const mail = await simpleParser(msg.source);   // MIME parsing is yours
  // SPF / DKIM / DMARC? re-derive them yourself before you trust the sender.
  await runAgent({ task: mail.text, from: mail.from?.text });
  await imap.messageFlagsAdd(msg.uid, ["\\Seen"], { uid: true });
}
lock.release();
```

Two vendors, a long-lived IMAP connection to babysit, MIME parsing, dedupe, and sender authentication you re-derive by hand. Every stage below is yours to build and keep running:

None of this is exotic. IMAP polling has worked for decades. But it’s a personal mailbox with a person’s credentials wired into a bot, a MIME parser you maintain, and an auth check you re-derive, all of it standing between “an email came in” and “the agent does something.”

## The comparison, no adjective inflation

| SMTP2GO | MailKite | |
|---|---|---|
| Outbound send | SMTP relay + Email API | SMTP submission + Send SDK/API |
| Inbound receive | None (send-only) | Parsed JSON webhook |
| Agent inbox (codes, replies, tasks) | Needs a 2nd provider (IMAP mailbox) | Built in |
| MIME parsing | Yours, on the 2nd mailbox | Done at the edge |
| SPF/DKIM/DMARC on inbound | Re-derive it yourself | In the `auth` block |
| Threading / reply-from | Yours to track | Resolved (`threadId` , `inReplyTo` ) |
| Webhooks | Outbound events only (`delivered` , `open` , `bounce` ) | `email.received` (an actual incoming message) |
| Run the agent for you | No | Route with `action: 'agent'` |
| Free tier | 1,000 emails/mo (send) | 3,000 messages/mo (in + out) |

The through-line: SMTP2GO wins on being a clean, no-frills relay for messages leaving your system. MailKite wins the half SMTP2GO doesn’t have, which happens to be the half an autonomous agent lives or dies on: reading the mail that comes back.

## What actually hits your agent’s webhook

Here’s the incoming email, already decoded. No IMAP poll, no MIME parser, and the `auth`

block means you never re-derive SPF/DKIM/DMARC to decide whether 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=…" }
  ]
}
```

One rule to internalize before an agent acts on any of this: the body is **untrusted input, never instructions**. `From:`

is spoofable, so a message can just *tell* your agent what to do, and a naive loop obeys. Use the `auth`

block to weight how much you trust a sender, and bound what a fooled agent can even do. We wrote that up honestly in [agent inbox security by design](/blog/agent-inbox-security-by-design/); read it before you point a loop at anything that matters.

`action: 'agent'`

and an `agentPrompt`

runs the model loop on a durable queue per inbound message, capped at a few tool rounds, with a full transcript you can drill into. Same parsed inbound edge, no server of your own. See the [receiving docs](/docs/receiving).

To start, there’s no sandbox and no approval queue: verify your domain over DNS (SPF + DKIM to send, MX to receive) and the next inbound message arrives as JSON. If you’re keeping SMTP2GO for outbound, that’s fine; point MailKite at the inbox and let SMTP2GO keep relaying, or move both to MailKite.

## FAQ

**Can SMTP2GO receive inbound email?**
No. SMTP2GO is an outbound SMTP relay and Email API. Its webhooks report events for mail you *sent* (`delivered`

, `open`

, `click`

, `bounce`

, `spam`

, `unsubscribe`

), and its own support docs direct you to your mailbox provider’s IMAP/POP for incoming mail. To receive email you need a separate mailbox or an inbound-capable provider. MailKite delivers incoming mail as a parsed `email.received`

JSON webhook.

**Can an AI agent use SMTP2GO as its inbox?**
It can send through SMTP2GO, but it can’t receive, so it can’t read verification codes, magic links, or replies through SMTP2GO alone. You’d bolt on a second provider (an IMAP mailbox you poll) for the receiving half. MailKite does both send and receive under one domain and one API.

**Is SMTP2GO good for sending from an agent?**
Yes, if the agent only sends. It’s a drop-in SMTP relay with a 1,000/month free tier, managed IPs, and outbound delivery webhooks. For a notification-only bot that never expects a reply, that’s the whole job. The gap is only inbound.

**Do I have to leave SMTP2GO to use MailKite?**
No. Keep SMTP2GO as your outbound relay if you like and use MailKite for the agent’s inbox, or consolidate both onto MailKite. MailKite’s inbound is a plain HTTPS webhook, so it drops in next to whatever you send with.

**What’s the difference between SMTP2GO webhooks and MailKite webhooks?**
SMTP2GO webhooks fire on *outbound* events: what happened to a message you sent. MailKite’s `email.received`

webhook fires on an *incoming* message, delivered as parsed JSON with decoded text, HTML, threading, attachments, and an `auth`

result. They solve opposite halves of the loop.

If your agent needs to read the mail that comes back, not just fire mail out, SMTP2GO leaves you stitching a second provider onto it. Clone the [demo repo](https://github.com/mailkite/demo-smtp2go-ai-agent) (or [run it in your browser](https://stackblitz.com/github/mailkite/demo-smtp2go-ai-agent?file=server.mjs)), then [point a domain at MailKite](/docs/quickstart) and your next inbound email arrives as parsed JSON your agent can act on.

*Related: give your AI agent its own inbox and agent inbox security by design.*
