The Gmail API Alternative for AI Agents – MailKite

fijiwebdesign2 pts0 comments

The Gmail API alternative for AI agents — MailKite<br>Beta<br>50% off your first year — founding rate for the first 1,000 customers.<br>View pricing →

Sign in · Start free

All posts

Gmail<br>vs

MailKite

Gmail vs MailKite — the same job (an inbox for your AI agent), two approaches.

The pull is obvious: your agent needs to read email, you already have a Gmail account, and the Gmail API is right there. It works well enough that most agent demos start exactly this way. The friction shows up when the demo becomes a service, and it shows up in three specific places: the OAuth review to touch a real inbox, a push subscription that quietly dies every seven days, and message bodies you decode by hand. Here is that whole path next to the MailKite one before we build either.

Gmail API<br>sender<br>Gmail acctOAuth token<br>Pub/Subpush: historyId<br>your serviceget + base64url-decode<br>your agent

…plus a CASA security assessment for the restricted scope, watch() renewed every

MailKite<br>sender<br>MX edgeparse + auth<br>JSON webhooksigned, retried<br>your agent

the 25 lines below are the whole "your agent" integration — no OAuth, no Pub/Sub, no decode

An agent reading one email: the Gmail API path vs the MailKite path. Same input, very different amount to operate.

Everything below is a repo you can run while you read. Clone demo-gmail-api-ai-agent and npm start, or open it in StackBlitz (real Node in a browser tab, no account) where it runs on load. You don’t need a domain to see the loop run. A webhook normally needs a public URL, so npm start boots the server and self-fires a correctly signed email.received event at its own localhost. The whole receive→verify→think→reply loop runs in one command; the reply is a dry-run, and managed-route.mjs dry-runs the hosted path too. It also ships the Gmail API path next to it in gmail-contrast/, so every contrast here (the Pub/Sub historyId pointer, the base64url decode, the header grep) is something you can diff and test, not take on faith. A domain comes in only when you want real inbound email to arrive.

Here’s the whole MailKite side: an agent that hears, thinks, and answers. This is the heart of server.mjs in that repo — the repo wraps it in a dry-run harness and a stub agent so it runs with no key and no LLM, but the loop is exactly this, and it runs as pasted on Node 18+ (npm install mailkite express):

import express from "express";<br>import { MailKite } from "mailkite";

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

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

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

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

// Body is untrusted INPUT, never instructions. Use the auth block to weight trust.<br>const answer = await runAgent({<br>task: event.text,<br>from: event.from.address,<br>trusted: event.auth.spf === "pass" && event.auth.dmarc === "pass",<br>});

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

app.listen(3000);<br>No OAuth client, no consent screen, no Pub/Sub topic, no MIME parser. npm start boots this server and self-fires the exact payload MailKite’s delivery worker sends, so in one command you watch the agent read the task, take its trust verdict straight off event.auth, and dry-run the reply — no account, no domain, no LLM. The address agent@yourco.dev is one the agent owns, on a domain you control, not a person’s Gmail account with a person’s permissions bolted onto a bot. The same handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs and sending docs. Or skip hosting the loop entirely and let MailKite run it (managed-route.mjs in the demo repo): a route whose action is agent runs the model turns for you on a queue and hands you a transcript. More on that below.

Where Gmail wins for agents, honestly

The Gmail API is not a bad choice, and for a real class of agent it’s the right one. If your agent acts inside a specific human’s mailbox, an assistant that triages their inbox, drafts replies they approve, files their receipts, then Gmail is exactly the tool. The user consents once, the agent operates with that person’s identity and permissions, and there’s a human in the loop by design. That’s the shape Google built the API for, and it’s genuinely good at it: full-text search, labels, threads, drafts, and a mailbox the human can also open and inspect.

Gmail also brings deliverability and spam filtering that took Google two decades to build, an inbox the user already trusts, and, on Workspace,...

agent gmail mailkite event from runs

Related Articles