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

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.

read10 min views1 publishedJul 4, 2026
The Brevo alternative for AI agents
Image: Mailkite (auto-discovered)

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

):

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 and sending docs. 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 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": "<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=…" }
  ]
}

From:

is plain text and trivially forged, so an email body is untrusted input, a prompt-injection vector, not instructions. The auth

block is how the agent decides how much to trust a sender before it acts, without string-parsing a header itself. It’s necessary, not sufficient; the real defense is bounding what a fooled agent can do, but starting from a normalized verdict beats starting from a regex.

Two ways to run it on MailKite #

MailKite, which we build, gives an agent a scoped address on a domain you control (agent@yourco.dev

), and runs the loop one of two ways. Bring your own: the inbound webhook hits your endpoint, your model runs, you reply with mk.send()

, which is the code at the top. Or let MailKite run it: create a route whose action

is agent

with an agentPrompt

, and the model loop runs on a durable Cloudflare Queue with an agent_runs

ledger and a per-route transcript you can drill into, capped and reaped so a slow model call can’t wedge ingest. Either way there’s no IMAP on a bot, no shared personal Gmail, no OAuth token-refresh churn, and no MIME parsing. To start, DNS-verify the domain (SPF + DKIM to send, MX to receive); there’s no sandbox or approval wait, the free tier is 3,000 messages a month across inbound and outbound, and SMTP-only apps can send through the submission edge on :587

/:465

. The companion repo demo-brevo-ai-agent runs both loops end to end;

open it in StackBlitzand fire a sample event in your browser.

FAQ #

Can Brevo receive inbound email? Yes. Brevo’s Inbound Parsing points a subdomain’s MX at inbound1/inbound2.sendinblue.com

and POSTs parsed mail as an items[]

array (or you poll the inbound events endpoint). Each item has the body, Headers

, Attachments

, and a SpamScore

. MailKite delivers the same message as a single email.received

event with a normalized auth

block.

Does Brevo’s inbound payload include an SPF/DKIM/DMARC verdict? Not as a field. SpamScore

is a spam float, and the SPF/DKIM results live only as raw lines (Received-SPF

, Authentication-Results

) inside Headers

, so you parse the verdict yourself. MailKite normalizes it into auth: { spf, dkim, dmarc, spam }

at the edge.

Is Brevo good for AI agents? For a marketing-shaped agent, yes: campaigns, CRM, automation, and transactional send behind one API key is real leverage, and ExtractedMarkdownMessage

gives the model a clean body. For an agent whose core job is receive→decide→reply safely, you’ll rebuild the trust verdict, the reply threading, and the loop. MailKite ships those.

What does Brevo cost versus MailKite? Brevo’s free plan is 300 emails/day with paid transactional plans from around $9/mo; marketing automation caps contacts on lower tiers. MailKite’s free tier is 3,000 messages/month across inbound and outbound with no per-domain fee. Confirm current numbers on each pricing page before you commit.

Can I keep sending marketing email on Brevo and run the agent on MailKite? Yes. They’re not exclusive. Run campaigns and lifecycle automation on Brevo, and point the agent’s address (a subdomain or a separate domain) at MailKite so it receives parsed JSON with an auth

block and replies over the Send API.

If your agent is fishing an SPF result out of raw headers before it dares trust an email, that’s the seam. Clone the demo repo (or run it in your browser), then point a domain at MailKite and your agent’s next inbound email arrives parsed, authenticated, and ready to answer.

Related: the pillar on giving your agent its own inbox, and agent inbox security by design.

── 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-brevo-alternativ…] indexed:0 read:10min 2026-07-04 ·