cd /news/developer-tools/the-grant-id-one-handle-for-mail-cal… · home topics developer-tools article
[ARTICLE · art-28429] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

The grant_id: One Handle for Mail, Calendar, and Webhooks

Nylas has introduced Agent Accounts, a beta feature that collapses multiple identifiers for email, calendar, and webhooks into a single grant_id. This single handle allows autonomous agents to perform all operations—sending mail, reading inboxes, managing events, and receiving webhook notifications—through the same endpoint family. The grant_id simplifies provisioning, operation, governance, and teardown, eliminating the complexity of managing separate OAuth client IDs, refresh tokens, and provider-specific IDs.

read5 min views1 publishedJun 15, 2026

Anyone who's wired an autonomous agent into email and calendar the traditional way knows the identifier sprawl: an OAuth client ID, a refresh token per user, a Gmail-specific message ID format, a Microsoft Graph calendar ID, and a webhook subscription ID for each — all with different lifetimes, all able to break independently. Half your "integration" code is really identifier bookkeeping.

Nylas Agent Accounts collapse all of that into one value. When you create an account (the feature's in beta), the response hands you a grant_id

, and that single string is the handle for everything the agent does — mail, calendar, contacts, attachments, and the webhooks reporting on all of them.

Every operation addresses the same path family, /v3/grants/{grant_id}/*

:

POST   /v3/grants/{grant_id}/messages/send         # send mail
GET    /v3/grants/{grant_id}/messages              # read the inbox
GET    /v3/grants/{grant_id}/threads/{thread_id}   # full conversation
GET    /v3/grants/{grant_id}/attachments/{id}/download
POST   /v3/grants/{grant_id}/events                # host a meeting
POST   /v3/grants/{grant_id}/events/{id}/send-rsvp # respond to one
GET    /v3/grants/{grant_id}/contacts
GET    /v3/grants/{grant_id}/rule-evaluations      # audit trail

This isn't an abstraction invented for agents — an Agent Account is literally just another grant, the same primitive used for connected Gmail and Outlook accounts. The supported endpoints reference puts it as "same endpoints, same auth, same payloads." Anything you built for connected accounts works against an agent's grant unchanged.

And the resources behind the ID are real: six system folders provisioned automatically (inbox

, sent

, drafts

, trash

, junk

, archive

), a primary calendar that speaks standard iCalendar, and outbound messages capped at 40 MB total.

The inbound side completes the picture. You subscribe once at the application level, and every notification — message.created

, event.updated

, message.bounce_detected

, and the rest — carries the grant_id

of the account it happened to:

{
  "type": "message.created",
  "data": {
    "object": {
      "object": "message",
      "id": "<MESSAGE_ID>",
      "grant_id": "<NYLAS_GRANT_ID>",
      "subject": "Hello from Nylas",
      "from": [{ "email": "sender@example.com", "name": "Sender" }],
      "snippet": "This is a sample message"
    }
  }
}

So the dispatch logic in your webhook handler is one lookup: grant_id

→ which agent → which handler. One detail to plan for: when a message body exceeds ~1 MB, the trigger arrives as message.created.truncated

with the body omitted — you fetch the full message through, naturally, the same grant_id

.

The handle appears at birth and disappears at death, with nothing extra to manage in between. Creation is a single call — POST /v3/connect/custom

with "provider": "nylas"

— and the response's data.id

is the grant_id

:

{
  "request_id": "5967ca40-a2d8-4ee0-a0e0-6f18ace39a90",
  "data": {
    "id": "b1c2d3e4-5678-4abc-9def-0123456789ab",
    "provider": "nylas",
    "grant_status": "valid",
    "email": "sales-agent@agents.yourcompany.com",
    "scope": [],
    "created_at": 1742932766
  }
}

No refresh token in that payload, because there's no OAuth provider behind the grant. Reconfiguration is also one call against the same ID — PATCH /v3/grants/{grant_id}

with a new workspace_id

moves the account under a different policy and rule set. And teardown is deleting the grant, which emits grant.deleted

through the same webhook subscription that reported its mail. Provision, operate, govern, destroy: four phases, one identifier.

Since one ID anchors everything, the schema almost writes itself. The agents table needs surprisingly few columns:

CREATE TABLE agents (
  id            UUID PRIMARY KEY,
  grant_id      TEXT NOT NULL UNIQUE,  -- the Nylas handle
  email         TEXT NOT NULL,
  workspace_id  TEXT,                  -- policy/rule inheritance
  purpose       TEXT,                  -- 'support', 'outreach', ...
  created_at    TIMESTAMPTZ
);

A few practices that fall out of the docs:

provider

field alongside the grant.provider: "nylas"

— that's the documented way to branch between "a human's mailbox we're acting on" and "a mailbox we own."grant_id

.grant.created

, grant.updated

, grant.deleted

, and grant.expired

tell you about fleet changes. The docs note agent grants rarely expire, because there's no OAuth token behind them to refresh — one whole failure class gone.Worth knowing where the boundary sits. Policies, rules, and lists — the guardrail resources — are application-scoped, with no grant ID in the path. They reach the agent indirectly: a workspace carries a policy_id

and rule_ids

, the grant carries a workspace_id

, and inheritance does the rest. So the grant is the handle for everything the agent does, while workspaces govern what it's allowed to do. The exception that proves the rule: GET /v3/grants/{grant_id}/rule-evaluations

brings the audit trail back under the grant, answering "which rules ran on this account's mail?"

The plan limits split along the same lines, and it's useful to know which scope each one attaches to. The send quota — 200 messages per account per day on the free plan, no daily cap by default on paid plans — is per account. Storage (3 GB on the free plan) is per organization, shared across every agent. Retention (30 days inbox, 7 days spam on the free plan) comes through the workspace's policy. Three limits, three different scopes, and only one of them keyed by the grant — which is exactly the kind of thing your data model should encode rather than discover in production.

One more thing that hangs off the grant rather than the application: protocol access. Set an app_password

on the grant and the same mailbox opens in Outlook, Apple Mail, or Thunderbird over IMAP and SMTP — useful when a human needs to supervise what the agent has been doing, and another case where the grant is the unit that carries the capability.

The single-handle design sounds like a small ergonomic win, but it compounds. Provisioning returns one value to persist; teardown deletes one grant; debugging starts from one ID that appears in every request path and every webhook payload.

Try this as a next step: take whatever per-user integration table you have today, count the identifier columns, and see how many survive a port to this model. If the answer is "one plus a workspace reference," your migration is mostly a column-drop. The overview is the right place to start reading.

── more in #developer-tools 4 stories · sorted by recency
── more on @nylas 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-grant-id-one-han…] indexed:0 read:5min 2026-06-15 ·