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 because the shape doesn’t change per provider.
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 and sending docs). The companion demo repo has the runnable version. Open it in StackBlitz, 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:
// 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; 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.
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 (or run it in your browser), then point a domain at MailKite 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.