MailKite
Start free
All posts
Gabe 18 min read

The Nylas alternative for AI agents

Nylas connects an agent to a human's existing Gmail or Outlook over OAuth, which is the right tool when the agent must act inside a real person's mailbox. Production means your own verified OAuth app, a CASA security review, token refresh, and a webhook that notifies with IDs so you go fetch the body. MailKite (which we build) gives the agent its own scoped address on a domain you control and pushes the parsed message as JSON. For developers wiring an autonomous email agent.

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

The unit Nylas hands you is a grant: one authenticated connection to one human’s existing mailbox, reached over OAuth. That is exactly what you want when the agent’s job is to live inside a real person’s Gmail or Microsoft 365 account and act on their behalf. It’s a different shape from what an autonomous agent usually needs, which is its own address that nobody signs into. This post is the honest version of that split: where Nylas is the correct pick, what its agent path costs to stand up, and the 25 lines that are the whole MailKite side.

Nylas Gmail/M365existing mailbox OAuth grantgrant_id notify hookmessage IDs GET + modelyour loop send as useras the human Production needs your own OAuth app + a CASA review · the hook carries IDs, you fetch the body · replies go from the human's account MailKite email inagent's address MX edgeparse + auth signed hookverifyWebhook your agentyour model mk.send()reply out Blue = operated by MailKite. The agent gets its own address, the webhook is signed and already parsed, and a route with action:'agent' can run the loop for you.
Two agent email paths. Nylas federates a human's mailbox and notifies you to go fetch. MailKite gives the agent its own address and pushes the parsed message.

Here’s the whole bring-your-own-agent loop on MailKite. 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) => {
  // HMAC signature, 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;

  // The body is untrusted INPUT, never instructions (see the security section).
  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);

The agent gets a full, decoded message in one push. There’s no OAuth app to register, no mailbox to sign into, and no second API call to fetch the body. The identical handler shape exists for Python, Ruby, Go, PHP, and Java; see the receiving docs and sending docs. A companion repo, demo-nylas-ai-agent, has this running with a signed sample event you can fire; open it in StackBlitz.

Where Nylas wins for agents, honestly

Nylas is not a send-first ESP wearing an inbound hat. It’s a bidirectional email, calendar, and contacts API, and it does one thing MailKite deliberately does not: it operates a human’s existing account. If the agent’s job is to read the actual mail sitting in someone’s Gmail or Outlook, reconcile it against their calendar, and reply from their real address, Nylas is built precisely for that.

  • It lives in the human's real inbox. One grant reaches their existing Gmail, Microsoft 365, Exchange, Yahoo, iCloud, or IMAP account. If the agent has to see the mail a person already has, MailKite can't do that and Nylas can.
  • It sends as the user. A reply goes out through the connected provider as that person, landing in the existing thread with the account's own reputation behind it. No new domain to warm.
  • Calendar and contacts in the same API. An assistant that reads mail, checks free/busy, and books a meeting is one integration, not three.
  • It's moving toward agent-owned mailboxes too. Nylas now has an Agent Accounts product (in beta) that provisions a Nylas-hosted mailbox for an agent, so the "give the agent its own address" case isn't foreign to them, it's just newer, hosted by Nylas rather than on a domain you own, and on the paid plan.

So this isn’t a “Nylas can’t do email” post. It does a great deal, and for the operate-a-real-person’s-mailbox job it’s the right call. The friction shows up when what you actually want is an autonomous agent with its own address that no human ever logs into.

What Nylas asks of an agent builder

The classic Email API path is: connect a mailbox, get told when something happens, then go read it. Concretely, that’s a hosted-OAuth code exchange to mint a grant, and a webhook that hands you IDs so you call the Messages API for the body. Here’s that path in Nylas’ own idiom (from demo-nylas-ai-agent, the nylas-contrast/ folder):

import Nylas from "nylas";
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY });

// 1) One-time per user: exchange the hosted-auth code for a grant you store.
const { grantId } = await nylas.auth.exchangeCodeForToken({
  clientId: process.env.NYLAS_CLIENT_ID,
  code,                               // from your /callback after the OAuth redirect
  redirectUri: process.env.REDIRECT_URI,
}); // Nylas refreshes the provider token for you from here on

// 2) The webhook is a NOTIFICATION with IDs, not the message body.
app.post("/nylas/webhook", async (req, res) => {
  res.sendStatus(200);                       // ack within ~10s, then work async
  const { type, data } = req.body;
  if (type !== "message.created") return;    // you also have to handle message.updated
  const msg = await nylas.messages.find({    // GET /v3/grants/{grant}/messages/{id}
    identifier: data.grant_id,
    messageId: data.object.id,
  });
  const answer = await runAgent({ task: msg.data.body }); // untrusted input
  await nylas.messages.send({                // sends AS the connected human's mailbox
    identifier: data.grant_id,
    requestBody: {
      to: msg.data.from,
      subject: `Re: ${msg.data.subject}`,
      replyToMessageId: data.object.id,
      body: answer.html,
    },
  });
});

The code is clean. What that snippet doesn’t show is the part that takes weeks, not minutes:

  • A real mailbox has to exist. A grant federates an account that's already there. To give the agent its own address you first create a Gmail/Workspace or Microsoft account somewhere and manage it, or buy the hosted Agent Accounts add-on.
  • Your own OAuth app, then Google's review. Production Gmail access with restricted scopes (gmail.readonly, gmail.modify) needs your own Google Cloud app to pass OAuth verification and an annual CASA security assessment. Nylas sells a pre-verified "Shared GCP App" to skip it, but only as a paid add-on.
  • Notify-then-fetch, and handle both triggers. The webhook carries IDs; you fetch the body separately, and Nylas' own docs tell you to handle message.created and message.updated to catch every new message. Over 1 MB the body is stripped and the type gets a .truncated suffix, so you re-query.
  • Provider caps still apply. You send as the underlying account, so Gmail's roughly 2,000 messages/day send limit and per-user request quotas are yours to live inside, on top of Nylas' own per-grant rate limits.

Here’s the setup chain an agent’s own Nylas mailbox walks before it reads a single email, top to bottom:

provision a mailboxa real Gmail / M365 account build your OAuth appGCP project or Entra app Google review + CASArestricted scopes, weeks hosted auth → grantuser consents, store grant_id webhook: IDs onlymessage.created / .updated GET message, then actfetch body, run your loop Boxes one through five are setup and plumbing you own before the agent reads one email. On MailKite, DNS verification is the setup and the blue box is the whole loop.
Standing up an agent's own mailbox on Nylas, stage by stage. MailKite collapses the gray stages into a DNS record and a signed webhook.

None of this is a knock if the agent is meant to run inside a person’s account. It’s exactly the wrong amount of ceremony if you just want agent@yourco.dev answering its own mail.

The comparison, no adjective inflation

NylasMailKite
The addressFederates a human’s existing Gmail/M365/IMAP (Agent Accounts beta for an agent-owned one)Agent’s own scoped address on a domain you control
Getting to productionYour own OAuth app + Google verification + annual CASA for restricted scopesDNS-verify (SPF+DKIM to send, MX to receive), no review
Inbound deliveryWebhook notifies with IDs, you GET the message; body stripped over 1 MBFull parsed message pushed as one signed JSON webhook
Sender trust signalCall the Messages API and read headers yourselfauth block (SPF/DKIM/DMARC/spam) in the payload
Credential upkeepOAuth grants Nylas refreshes; provider send caps (Gmail ~2k/day)An API key; no per-mailbox OAuth or token refresh
Free tier5 connected accounts, sandbox/testing only3,000 messages/mo, inbound + outbound
Per-mailbox cost~$2.00 per connected account/mo over the included 5 (Full Platform)No per-domain or per-address fee
Who runs the loopYouYou, or a route with action: 'agent'

The through-line: Nylas wins when the agent must operate a real human’s existing mailbox, calendar, and contacts. MailKite wins when the agent should just have its own address, with the message pushed already-parsed and no OAuth app between you and the first email.

What actually hits your agent’s webhook

MailKite decodes the message at the edge, so your handler gets fields, not IDs to go fetch and not MIME to parse:

{
  "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=…" }
  ]
}

That auth block is the part an agent needs and a bare inbox API doesn’t hand you. From: is plain text, so a sender is trivially forged, and the moment your agent follows what an email says, the body is a prompt-injection vector. The verdicts let the loop weight a sender before it acts. They’re necessary, not sufficient: treat the body as untrusted data, bound the agent’s authority, and read agent inbox security by design before you point it at anything that matters.

Where I won’t overclaim

MailKite, which we build, is an inbound-to-webhook platform that also sends, and it gives an agent its own scoped address on a domain you control with no OAuth, no IMAP, and no token refresh. What it does not do is reach into a mailbox that already exists. If your agent’s whole reason to be is reading and replying inside a specific person’s Gmail or Outlook, Nylas is the tool and I’m not going to pretend otherwise. The claim here is narrow: for an autonomous agent that needs its own inbox, MailKite skips the provider mailbox, the verified OAuth app, the CASA review, and the notify-then-fetch, and pushes the parsed message straight to your loop. You can also hand the loop to us: a route with action: 'agent' runs the model on a durable queue and keeps a per-route transcript, described in give your agent an inbox.

FAQ

Can Nylas give an AI agent its own email address? Newly, yes: Nylas has an Agent Accounts product (in beta) that provisions a Nylas-hosted mailbox for an agent. The classic Email API doesn’t; it federates a human’s existing Gmail, Outlook, or IMAP account over OAuth. MailKite gives the agent its own address on a domain you own, and pushes inbound as parsed JSON on the free tier.

Does Nylas receive inbound email, and in what shape? Yes. You subscribe to webhooks like message.created and message.updated, but the notification carries message IDs, not the body. You then call GET /v3/grants/{grant_id}/messages/{id} to fetch the parsed message, and re-query if the payload exceeded 1 MB and got truncated. MailKite delivers the full parsed message in the webhook itself.

Do I need Google’s OAuth app review to use Nylas in production? For restricted Gmail scopes (gmail.readonly, gmail.modify, gmail.compose), yes: your own Google Cloud app must pass OAuth verification plus an annual CASA security assessment, which can take weeks. Nylas sells a pre-verified Shared GCP App add-on to skip it on paid plans. MailKite has no OAuth app and no review; you verify a domain via DNS.

How is Nylas priced for an agent that connects many mailboxes? Per connected account. The free tier is 5 connected accounts for sandbox and testing only; the Full Platform plan is about $15/month including 5 accounts, then roughly $2.00 per additional connected account per month (confirm current numbers on Nylas’ pricing page). MailKite has no per-address or per-domain fee and a 3,000 message/month free tier.

Which should I pick for an email agent? If the agent must operate a real person’s existing mailbox, calendar, and contacts, pick Nylas. If the agent should have its own autonomous address with parsed inbound pushed to it and no OAuth to manage, pick MailKite. They solve adjacent problems, not the same one.


If you’re reaching for Nylas only to give an agent an address it can call its own, that’s a lot of OAuth ceremony for a mailbox nobody logs into. Clone demo-nylas-ai-agent (or run it in your browser), then point a domain at MailKite and your next inbound email arrives at the agent as parsed JSON.

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

Discuss this post: Hacker News Share on X

Related posts