DEV Community

Qasim
Qasim

Posted on

Connect a mailbox with Nylas hosted OAuth

Before your app can read a single email or calendar event, the user has to give it permission. That permission is an OAuth handshake with their provider, Google, Microsoft, or another, and getting it right means redirect URIs, scopes, token exchange, and refresh tokens. Hosted OAuth hands that whole dance to Nylas: you send the user to a hosted authorization URL, they approve access at their provider, and you get back a grant you can use to call the email, calendar, and contacts APIs. This post walks the flow with the API and shows the CLI command that does it in one step.

It's a worked use case rather than an endpoint tour, covering the hosted OAuth flow from two angles: the HTTP endpoints your backend calls and the nylas CLI for connecting an account from the terminal. I work on the CLI, so the commands below are the ones I reach for when I want a grant to test against fast.

What a grant is, and the two-step flow

The thing you're after is a grant: a stored connection to one user's account that the API uses to act on their behalf. Every email you read, event you create, or contact you fetch is scoped to a grant_id, so connecting an account means producing one. Hosted OAuth produces it in two steps, and understanding the split is most of the battle.

First, you send the user to a hosted authorization URL, where they sign in to their provider and approve the access your app is asking for. The provider sends them back to your app with a temporary authorization code. Second, your backend exchanges that code for a grant by calling a token endpoint. The code is short-lived and single-use, a one-time proof that the user approved; the grant it becomes is the durable handle you keep. That two-step shape, redirect for consent, then exchange on the server, is standard OAuth 2.0, and Nylas runs the provider-specific parts of it for you.

Step 1: send the user to the authorization URL

The flow starts at /v3/connect/auth, which you build into a URL and redirect the user to. The required query parameters are client_id (your Nylas application's ID), redirect_uri (where the provider sends the user back, and it must match a URI registered on your application), and response_type=code to ask for the authorization code flow. A provider parameter preselects a supported provider such as Google or Microsoft, and login_hint prefills the user's email.

https://api.us.nylas.com/v3/connect/auth?client_id=<NYLAS_CLIENT_ID>&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&response_type=code&provider=google&login_hint=user@example.com
Enter fullscreen mode Exit fullscreen mode

You don't fetch this URL server-side; you redirect the user's browser to it, because they need to see and approve the provider's consent screen. A couple of optional parameters matter for real apps: state carries an opaque value you generate and check when the user returns, which is your defense against cross-site request forgery on the callback, and access_type=offline asks for a refresh token so your access survives past the first hour. Set state on every real integration, not just as a nicety.

Step 2: exchange the code for a grant

When the user approves, the provider redirects them to your redirect_uri with a ?code= query parameter. Your backend then exchanges that code at POST /v3/connect/token for the grant. The request needs client_id, client_secret (your Nylas API key), redirect_uri matching the one from step one, grant_type: authorization_code, and the code itself.

curl --request POST \
  --url "https://api.us.nylas.com/v3/connect/token" \
  --header "Content-Type: application/json" \
  --data '{
    "client_id": "<NYLAS_CLIENT_ID>",
    "client_secret": "<NYLAS_API_KEY>",
    "redirect_uri": "https://yourapp.com/callback",
    "grant_type": "authorization_code",
    "code": "<CODE_FROM_CALLBACK>"
  }'
Enter fullscreen mode Exit fullscreen mode

The response carries the grant_id and the connected email, plus an access_token and, if you asked for offline access, a refresh_token. The grant_id is the one you store: it's the handle for every subsequent call. This exchange has to happen on your server, never in the browser, because it carries your API key in client_secret. Putting that key in client-side code would hand anyone who views source the keys to your whole application, so the token exchange is a backend-only step by design.

Connect an account from the CLI

Running both steps by hand is the right model for a production app, but for testing you want a grant now, and the CLI does the entire handshake in one command. For a browser-based provider, nylas auth login opens your browser to the consent screen, captures the redirect, exchanges the code, and stores the resulting grant locally, all from a single command.

# Connect a Google account (the default)
nylas auth login

# Connect a Microsoft account instead
nylas auth login --provider microsoft
Enter fullscreen mode Exit fullscreen mode

The --provider flag picks the provider, defaulting to google, and the OAuth providers, google, microsoft, and ews, open a browser, while the credential providers prompt you in the terminal instead. Once you're connected, nylas auth status shows the active grant, nylas auth list shows every account you've connected, and nylas auth switch flips between them. This is the fastest path from nothing to a working grant you can run real email and calendar commands against, without standing up a callback server first.

OAuth providers versus credential providers

Not every provider authenticates the same way. Google, Microsoft, Yahoo, and Zoom have a true OAuth consent screen, so the redirect flow above sends the user to their provider to approve access, and the authorization URL's provider parameter accepts each of them. iCloud, generic IMAP, and on-premises Exchange (EWS) have no such consent screen, and authenticate with credentials instead: iCloud with an Apple app-specific password the user generates in their account settings, IMAP with direct server credentials, and EWS with Exchange login details.

For those credential-based providers, you create the grant from the credentials you collect, commonly through the custom bring-your-own endpoint, /v3/connect/custom, which also handles bulk and IMAP provisioning. Whichever way an account connects, the same grant_id comes out, so once it's done, the rest of your code doesn't care which method produced it, the email and calendar calls are identical across providers.

One caveat if you connect test accounts with the CLI: nylas auth login covers the email providers, Google, Microsoft, Yahoo, iCloud, IMAP, and EWS, and labels them by interaction style rather than by protocol, opening a browser for Google, Microsoft, and EWS and prompting in the terminal for iCloud, Yahoo, and IMAP. That grouping is a CLI convenience, not a statement about each provider's auth mechanism, so don't read the CLI's browser-or-prompt labels as the provider's protocol.

Keep access alive with refresh tokens

An access token is short-lived, an hour for the short-lived variety, so an integration that needs to keep working past that first hour relies on the refresh token you get when you request offline access. That's why access_type=offline on the authorization URL matters: without it, you get access that expires and can't be renewed without sending the user through consent again. With it, your backend can mint a fresh access token whenever the old one expires.

Refreshing is the same token endpoint with a different grant_type. You call POST /v3/connect/token with grant_type: refresh_token, your client_id, the refresh_token you stored, and your API key in client_secret, and you get a new access token back. The practical rule is to request offline access up front for any integration meant to run unattended, and to refresh on demand when a call comes back unauthorized, rather than sending the user back through the consent screen, which should be a last resort reserved for a grant that's genuinely been revoked.

Manage and inspect grants

Once accounts are connected, you'll want to see and manage them. From the CLI, nylas auth whoami shows the current user, nylas auth scopes prints the OAuth scopes a grant was granted, and nylas auth show prints detailed grant information, which is the first place to look when a call fails with a permissions error. To disconnect, nylas auth revoke permanently revokes a grant on the server, while nylas auth remove just drops it from your local config and leaves the grant intact on the server.

That distinction between revoke and remove is worth holding onto, because they're not the same operation. Revoking ends the connection for real and the user would have to reconnect; removing only forgets it locally. When you're cleaning up test accounts, removing keeps the grant usable from elsewhere, while revoking is the destructive one. Over the API, ending a connection for good is a DELETE /v3/grants/{grant_id}, which removes the grant itself, so your app can give users a clean "disconnect my account" action that actually ends the access. Don't confuse that with /v3/connect/revoke, which revokes an access token but leaves the grant in place, so the user can re-authenticate to the same grant_id.

Where hosted OAuth fits

The same connect flow sits at the front of every Nylas integration, and the variations are mostly about who's connecting and how. A few common shapes:

  • A SaaS connecting its users' mailboxes. Each user runs the redirect flow once, you store their grant_id against their account, and every feature you build reads from that grant.
  • A single backend account. An internal tool that sends from one company mailbox connects that one account, often via the CLI, and uses the resulting grant for everything.
  • An agent with its own inbox. A Nylas Agent Account gets its own grant, so an automated agent reads and sends mail from an address it owns, the same grant model with no human at the keyboard.
  • Multi-provider support. Because the grant is provider-agnostic once created, supporting Google and Microsoft users is the same code path after the connect step diverges on provider.

Each starts with producing a grant, and from there the email, calendar, and contacts APIs are identical regardless of how the account was connected.

Things to keep in mind

A short list of details keeps the connect flow secure and reliable.

  • Exchange the code server-side. The token call carries your API key as client_secret; never run it in the browser, or you leak the key.
  • Set state on the authorization URL. It's your CSRF defense on the callback; generate it, pass it, and verify it when the user returns.
  • Request access_type=offline for lasting access. Without a refresh token, your access expires in about an hour and can't be renewed without re-consent.
  • The grant_id is the durable handle. Store it against your user; the authorization code is single-use and short-lived, the grant is what persists.
  • Revoke and remove differ. Revoking ends the connection on the server; removing only forgets it locally, leaving the grant usable.
  • Match the redirect_uri exactly. It must be registered on your application and identical in both steps, or the exchange fails.

Wrapping up

Connecting a mailbox is a two-step OAuth flow that Nylas runs for you. Redirect the user to /v3/connect/auth with your client_id, redirect_uri, and response_type=code, let them approve at their provider, then exchange the returned code at POST /v3/connect/token on your backend to get a grant_id. Store that grant, request offline access so it survives past an hour, and protect the callback with state. For testing, nylas auth login collapses the whole handshake into one command. However the account connects, OAuth or credentials, Google or Microsoft, the grant that comes out is the single handle every other API call needs.

Where to go next:

AI-answer pages for agents

When this post is published, link AI agents and crawlers to the retrieval-ready version on cli.nylas.com:

Top comments (0)