Gmail API logo vs MailKite logo
Alternative to Gmail API

An agent inbox that skips OAuth review, Pub/Sub, and the 7-day watch renewal.

A Gmail account plus the Gmail API is the go-to way to give an agent an inbox — free, familiar, and fine for an assistant a human supervises. Productionize an autonomous agent on it and you inherit OAuth restricted-scope CASA review, a Pub/Sub watch() that expires every 7 days, and base64url MIME you decode by hand. MailKite gives the agent its own scoped address and delivers inbound as parsed JSON with an auth verdict.

A fair take on Gmail API

Gmail is the right tool when the agent works inside a specific human's mailbox — a co-pilot that triages their inbox and drafts replies they approve, with the user's consent and a person in the loop. Two decades of deliverability, full-text search, labels, threads, and Workspace admin controls come free, and none of the friction below applies to that case.

How it works

Gmail API vs MailKite, in one diagram

Gmail API sender Gmail acctOAuth token Pub/Subpush: historyId your serviceget + base64url-decode your agent …plus a CASA security assessment for the restricted scope, watch() renewed every <7 days on a cron, and an OAuth refresh token you keep alive — all yours to operate MailKite sender MX edgeparse + auth JSON webhooksigned, retried your agent the 25 lines below are the whole "your agent" integration — no OAuth, no Pub/Sub, no decode
Why teams switch

What MailKite does differently

Its own address, not a borrowed account

The agent gets agent@yourco.dev on a domain you control — an address it owns, not a person's Gmail account with a person's permissions bolted onto a bot. No consent screen, no user whose identity it operates under.

No OAuth restricted-scope review

Reading mail via Gmail needs the restricted gmail.readonly/gmail.modify scopes, which in production past 100 users require an annual CASA security assessment. MailKite has no OAuth flow at all — you DNS-verify the domain and you're live.

Register the webhook once

Gmail push is a users.watch() subscription that expires in 7 days; miss the daily renewal cron and mail keeps arriving while your agent hears nothing, with no error. MailKite's webhook is registered once and stays put.

Parsed JSON with an auth verdict

No Pub/Sub round-trip, no history.list → messages.get, no base64url MIME to walk. Every event arrives with decoded text/html, a resolved threadId, and an auth{spf,dkim,dmarc,spam} block — load-bearing for weighting trust on an injection surface.

Side by side

MailKite vs Gmail API

MailKite Gmail API
Agent's address Scoped address on a domain you control A Gmail/Workspace account (a human artifact)
Getting to production DNS-verify (SPF+DKIM to send, MX to receive) OAuth client + consent; restricted-scope CASA
Inbound delivery One parsed JSON webhook Pub/Sub push of a historyId → get → decode
Push longevity Register the webhook once watch() expires in 7 days; renew ~daily
Message body Decoded text / html in the payload base64url MIME you walk and decode
Auth verdict auth block on every event Parse SPF/DKIM/DMARC headers yourself
Reply / threading mk.send({ inReplyTo }) resolves it Build the RFC 2822 message + threading yourself
Automation posture Built for programmatic, per-domain use Account limits + ToS written for humans

Competitor capabilities change — we re-audit these tables regularly. Spot something out of date? Tell us and we'll fix it.

Show the payload

What actually hits your webhook

Gmail hands you a historyId and leaves the lookup, MIME decode, and header auth to you; MailKite POSTs the decoded message with its auth verdict already computed.

Gmail push (Pub/Sub → historyId, then get + decode)
http
POST /pubsub   (Cloud Pub/Sub push, base64 envelope)

{ "message": { "data": "eyJlbWFpbEFkZHJlc3MiOiJhZ2VudEB5b3Vy…" } }
// data base64-decodes to → { emailAddress, historyId }
// NOT the message. You still have to:
// gmail.users.history.list({ startHistoryId }) → what changed
// gmail.users.messages.get({ id }) → the message
// walk payload.parts, pick text/plain,
// Buffer.from(part.body.data, "base64url") → the body
// parse SPF/DKIM/DMARC out of the headers yourself
// ( and users.watch() re-called before its 7-day expiry )
MailKite
json
POST /your-webhook   Content-Type: application/json
x-mailkite-signature: t=…,v1=… (HMAC-SHA256 — verify locally)

{
"id": "msg_2Hk9…",
"type": "email.received",
"from": { "address": "ada@example.com" },
"to": [{ "address": "support@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": [
{ "filename": "po.pdf", "contentType": "application/pdf",
"size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?sig=…" }
]
}
The price advantage

Unlimited domains. One quota. Free to start.

MailKite

  • Free: 3,000 emails/mo (in + out)
  • Unlimited domains on every plan
  • Pro $20/mo → 50,000, graceful overage

Gmail API

  • Free with a Google/Workspace account
  • Restricted scopes need annual CASA in production (assessors report a few hundred to a couple thousand dollars/yr)
  • Human-account caps: 500 recipients/day personal, 2,000 Workspace, 100/message via the API

Gmail's dollar cost is zero, but the operational cost of running an autonomous agent on a human-account product — CASA review, watch renewals, per-user limits — is the real bill. MailKite prices on one combined volume, domains always free.

See full MailKite pricing →

Switch in an afternoon

Moving from Gmail API

  1. 1

    Point a domain at MailKite

    Add the MX records to receive and SPF+DKIM to send on a domain you control. No OAuth client, no consent screen, no CASA assessment — the agent gets its own scoped address like agent@yourco.dev.

  2. 2

    Replace the Pub/Sub endpoint with a webhook

    Delete the watch() renewal cron, the history.list/messages.get lookup, and the base64url decode. Point one webhook URL at your handler and read event.text, event.from.address, and event.auth directly.

  3. 3

    Verify and reply

    Check x-mailkite-signature with the SDK's verifyWebhook helper, then reply through the same send API with mk.send({ inReplyTo }) to thread it. Or set a route to action:'agent' and let MailKite run the receive→reply loop on a queue.

Questions

Can I use the Gmail API to give an AI agent its own inbox?

You can, and for a single agent that assists one real person on their own mailbox it's a good fit. For an autonomous agent with its own address, expect OAuth restricted-scope verification (CASA) for production, a Pub/Sub watch() subscription you renew before its 7-day expiry, and base64url MIME to decode. MailKite gives the agent a scoped address on your domain and delivers parsed JSON, with none of that setup.

Why does my Gmail push notification stop working after a week?

Because users.watch() creates a subscription that expires after 7 days — it's not a register-once webhook, and Google recommends re-calling it roughly daily. If the renewal cron fails, new mail arrives but your service gets no notification and no error, so the agent goes silently deaf. MailKite's webhook is registered once and stays live.

Can an agent still act inside a real person's Gmail if I use MailKite?

No, and it shouldn't try to. If the job is triaging a specific human's existing inbox with their consent, use the Gmail API — that's what it's built for. MailKite is for the other case: an agent that needs its own address, receiving and replying on a domain you control.

Go deeper

The Gmail API alternative for AI agents →

The long-form take on our blog — receipts, runnable code, and where we won't overclaim.

Try MailKite free — keep Gmail API running until you're ready.

Point a domain, drop in a webhook URL, receive your first email. Unlimited domains, no credit card.