The Brevo alternative for AI agents MailKite launches an email API designed for autonomous AI agents, offering a receive→reply loop with built-in SPF/DKIM/DMARC verification, contrasting with Brevo's marketing-focused platform that requires developers to rebuild authentication and agent loops. The Brevo alternative for AI agents Brevo formerly Sendinblue is a marketing platform with a transactional API, and it can receive: Inbound Parsing POSTs email as an items array. But there's no normalized SPF/DKIM/DMARC verdict and no agent loop, so you rebuild both. MailKite which we build hands an autonomous agent a real inbox as one parsed email.received event with an auth block and a receive→reply loop. An autonomous agent doesn’t need a campaign designer or a CRM. It needs one thing most send-first platforms never shipped: its own real address that it can read from, decide on, and reply to with no human in the loop. Brevo can receive mail, so this isn’t a “can’t.” It’s a question of shape, and of what you rebuild after the JSON lands. Here’s the contrast in one picture. Here’s the whole MailKite side: email in, verify the signature, hand the body to your model, reply through the same client. It runs as pasted on Node 18+ npm install mailkite express : 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. Weight it by the auth verdict. 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 the receive→think→reply loop, whole. The same handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs /docs/receiving and sending docs /docs/sending . The rest of this post is the honest version of the diagram: where Brevo genuinely fits an agent, what its inbound path asks you to build, and what the parsed payload actually contains. Where Brevo wins for agents, honestly Brevo is a marketing platform first: email campaigns, a built-in CRM, marketing automation, SMS, and WhatsApp, with a transactional email API bolted alongside. That breadth is the point of the pitch, and for one kind of agent it’s the right tool. If your agent’s job lives near marketing, having all of it behind one API key is real leverage. An agent that qualifies a lead, updates a contact attribute, and then triggers a lifecycle sequence can do all three against the same Brevo account it reads inbound replies from. You don’t wire a CRM and an ESP and an inbox parser together; they’re already one system. Brevo’s inbound parse also does something genuinely useful for LLM input. Each parsed item carries an ExtractedMarkdownMessage field: the reply body cleaned up, with the quoted history and the signature split off into a separate ExtractedMarkdownSignature . If you’ve ever fed a raw email reply to a model and watched it dutifully summarize the person’s phone number and legal disclaimer, you know why that’s worth something. That extraction is nicer than what a lot of inbound APIs hand back. So this isn’t “Brevo can’t receive.” It can. The question is what the shape costs an agent builder. What Brevo asks of an agent builder Brevo’s Inbound Parsing https://developers.brevo.com/docs/inbound-parse-webhooks works by DNS: you add MX records pointing a subdomain their example is reply.yourdomain.com at inbound1.sendinblue.com and inbound2.sendinblue.com , then register a webhook by POSTing to /v3/webhooks with type: "inbound" and the inboundEmailProcessed event. From then on Brevo POSTs an object with an items array; each item has From , To , Subject , RawTextBody , RawHtmlBody , ExtractedMarkdownMessage , Attachments , Headers , and a SpamScore . There’s a polling path too, but mind the gotcha: GET /v3/inbound/events returns metadata and delivery logs, not the parsed body. Only the webhook POST carries RawTextBody and the markdown extraction, so an agent that wants the content has to receive the push. Two things follow from that shape once an agent is the consumer. First, it’s a batch. You iterate items , not “handle this one email.” Fine, but your loop owns it. Second, and this is the load-bearing one: there is no normalized authentication verdict. SpamScore is a float from rspamd, not a pass/fail on SPF, DKIM, or DMARC. The actual SPF and DKIM results exist only as raw header lines Received-SPF , Authentication-Results , ARC-Seal inside the Headers field. If your agent is going to act on an email, it has to reconstruct the trust verdict itself, by string-matching headers, before it decides how much weight to give a sender’s instructions: // Brevo inbound: POST { items: ... } — parsed, but the auth verdict isn't a field. import express from "express"; const app = express ; app.use express.json ; app.post "/hooks/brevo", async req, res = { res.sendStatus 200 ; // guard this endpoint yourself: a secret path or IP allowlist for const item of req.body.items ?? { const body = item.ExtractedMarkdownMessage ?? item.RawTextBody ?? ""; // SpamScore is a number, not a pass/fail. There is no spf/dkim/dmarc field. // Reconstruct the verdict from the raw headers before trusting the sender: const rawHeaders = JSON.stringify item.Headers ?? "" ; const spfPass = /spf=pass/i.test rawHeaders ; const dmarcPass = /dmarc=pass/i.test rawHeaders ; const answer = await runAgent { task: body, from: item.From?.Address, trusted: spfPass && dmarcPass, } ; // Reply is a separate product surface: the transactional send API. // Set In-Reply-To / References yourself if you want it to thread. await brevoSendTransactional { to: item.From?.Address, ...answer } ; } } ; Grepping Authentication-Results for spf=pass is exactly the kind of thing that looks fine in a demo and quietly rots: the header format varies by upstream relay, dmarc= isn’t always present, and a regex over headers an attacker partly controls is a shaky foundation for a trust decision. It’s doable. It’s just yours to get right, and it’s the check that matters most for an autonomous agent. The reply is also a separate concern. Inbound Parsing gets mail in; sending the answer is the transactional email API, a different surface a different product to activate, in fact: new accounts open a support ticket before transactional sending turns on , and threading In-Reply-To , References is on you to set. None of this is exotic. It’s a marketing suite that happens to expose an inbound parser, not a receive→reply loop for a bot. The comparison, agent-relevant rows only | Brevo | MailKite | | |---|---|---| | Product center of gravity | Marketing suite campaigns, CRM, automation | Inbound email → webhook | | Inbound shape | items array batch via webhook or poll | One email.received event | | Auth verdict SPF/DKIM/DMARC | Not a field — grep raw Headers yourself | Normalized auth block | | Spam signal | SpamScore float rspamd | spam: ham/spam plus auth | | Body for the model | ExtractedMarkdownMessage clean, nice | Decoded text + html | | Agent loop built in | None — wire model + transactional send | Optional route action: agent , or BYO | | Reply + threading | Separate transactional API; set headers yourself | mk.send { inReplyTo } resolves it | | Webhook auth | Guard the endpoint yourself | Signed; verifyWebhook in one call | | Start | Add MX + create webhook via API | DNS-verify, one webhook | | Free tier | 300 emails/day | 3,000 messages/mo in + out | The through-line: Brevo wins when the agent’s work is marketing-shaped and you want CRM, campaigns, and inbound under one roof. MailKite wins when the agent’s work is “own an address, read what arrives, decide, reply safely,” because the trust verdict and the reply path are already assembled. What actually hits your agent’s webhook Here’s the MailKite email.received event: decoded body, a resolved threadId , attachments as short-lived signed URLs, and the auth block that the Brevo path made you reconstruct. { "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": "