Source: https://cli.nylas.com/guides/gmail-headless-server-auth

# Authenticate Gmail on a Headless Server or VM

Gmail OAuth opens a browser consent screen, which a headless server or VM cannot show. Configure a Nylas API key, attach a grant you connected once on a machine with a browser, and the CLI sends and reads Gmail over HTTPS with automatic token refresh.

Written by [Caleb Geene](https://cli.nylas.com/authors/caleb-geene) Director, Site Reliability Engineering

Updated June 8, 2026

> **TL;DR:** To authenticate Gmail on a headless server, skip `nylas auth login` (it needs a browser). The simplest path sets three environment variables (`NYLAS_API_KEY`, `NYLAS_GRANT_ID`, and `NYLAS_DISABLE_KEYRING=true`) and runs CLI commands directly, with nothing written to disk. Create the grant once where a browser exists.

## How do I authenticate to Gmail from a headless server or VM?

The Nylas CLI reads credentials straight from the environment on a headless server. Set `NYLAS_API_KEY`, `NYLAS_GRANT_ID`, and `NYLAS_DISABLE_KEYRING=true`, then run any command with no setup step, no `nylas auth login`, no browser, and nothing written to disk. Gmail access tokens refresh automatically every 3,600 seconds on the Nylas side.

This splits the work into a one-time browser step and a repeatable headless step. You connect the Gmail account once through hosted OAuth (on your laptop or the Nylas dashboard), which returns a grant ID. The server reuses that grant ID forever, sending and reading mail over HTTPS on port 443 without ever opening a browser, holding a Gmail password, or touching SMTP ports 25, 465, or 587.

## Why does `nylas auth login` fail on a headless server?

`nylas auth login` starts Google's OAuth 2.0 authorization-code flow, which redirects to a browser consent screen so the user can approve scopes. A headless server or VM has no display and no browser to render that screen, so the flow has nowhere to send the redirect and the command cannot complete. This is a property of OAuth, not a CLI limitation.

The usual headless workarounds for raw Gmail are heavyweight. Google service accounts need domain-wide delegation configured by a Workspace admin and only work inside one Workspace domain, per [Google's service-account documentation](https://developers.google.com/identity/protocols/oauth2/service-account). App passwords over SMTP still work for accounts with 2-Step Verification, but [Google disabled the older “less secure app” password access in September 2024](https://workspaceupdates.googleblog.com/2023/09/winding-down-google-sync-and-less-secure-apps-support.html), Workspace admins often block app passwords by policy, and SMTP exposes no read, search, or calendar operations. The API-key approach below sidesteps both.

## How do I authenticate Gmail with environment variables?

Three environment variables authenticate the CLI with no setup command: `NYLAS_API_KEY` carries the application key, `NYLAS_GRANT_ID` selects the Gmail mailbox, and `NYLAS_DISABLE_KEYRING=true` tells the CLI to skip the OS keyring, which a headless Linux box usually lacks. Nothing is written to disk, so the same container image runs unchanged across hosts.

The grant ID comes from a one-time browser step. Connect the mailbox once with hosted OAuth on the dashboard, or run `nylas auth login --provider google` on a workstation and read the grant ID back with `nylas auth show`. On a fresh server with no stored config, `NYLAS_GRANT_ID` is required: the CLI errors with `E006 “No grant ID provided”` rather than guess a grant it has never seen:

```bash
# Credentials arrive from a secrets manager, not the image
export NYLAS_API_KEY="$(cat /run/secrets/nylas_api_key)"
export NYLAS_GRANT_ID="$(cat /run/secrets/nylas_grant_id)"
export NYLAS_DISABLE_KEYRING=true

# EU applications only (US is the default endpoint)
export NYLAS_API_BASE_URL=https://api.eu.nylas.com

# Use the CLI directly — no auth config, no auth add
nylas email list --limit 5 --json
```

Get the key from [the Nylas dashboard](https://dashboard-v3.nylas.com/register?utm_source=https%3A%2F%2Fcli.nylas.com%2F&utm_medium=website&utm_campaign=cli&utm_id=cli) under your application's API Keys. The free plan includes 5 connected grants and up to 10,000 API requests per month. The CLI defaults to the US endpoint; for an EU application, add a fourth variable, `NYLAS_API_BASE_URL=https://api.eu.nylas.com`, which overrides the base URL on every request and keeps the setup fully stateless.

## How do I keep the API key and grant ID out of the server image?

Treat the API key as a credential, not configuration. Inject it at runtime from a secrets manager or an orchestrator secret, never bake it into a Docker layer, an AMI, or a committed file. A leaked Nylas API key authorizes access to every grant in the application, so the blast radius of a hardcoded key is the whole mailbox set, not one inbox.

Mount the key as a file or environment secret and read it at process start. Docker mounts secrets under `/run/secrets/`, and Kubernetes injects them as files or environment variables. The environment-variable path keeps the key out of every writable layer because the CLI reads it directly from `NYLAS_API_KEY`, with no on-disk credential store to bake in:

```bash
# Key and grant arrive from a Secret, never the image
export NYLAS_API_KEY="$(cat /run/secrets/nylas_api_key)"
export NYLAS_GRANT_ID="$(cat /run/secrets/nylas_grant_id)"
export NYLAS_DISABLE_KEYRING=true

# Rotate by swapping the secret and restarting the process —
# no image rebuild and no auth config to re-run
nylas email list --limit 1 --json
```

For the full containerized pattern, including keeping the key out of image layers in a CI pipeline, see the [Deploy Agent Accounts in Docker & CI](https://cli.nylas.com/guides/deploy-agent-accounts-docker) guide.

## How do I run headless Gmail auth in Docker?

The official `ghcr.io/nylas/cli:latest` image runs the CLI headless out of the box. It executes as an unprivileged `nylas` user with `NYLAS_DISABLE_KEYRING=true` already set, so a container needs no auth config, no mounted config file, and no browser. Pass the key and grant as `-e` variables on `docker run` and the same tag works against any mailbox.

Each `docker run` is one self-contained command. The credentials come from the host environment or a secrets manager, never the image layers, which keeps the published image free of any tenant's key:

```bash
# List the Gmail inbox (US endpoint)
docker run --rm \
  -e NYLAS_API_KEY="$NYLAS_API_KEY" \
  -e NYLAS_GRANT_ID="$NYLAS_GRANT_ID" \
  ghcr.io/nylas/cli:latest \
  email list --limit 5 --json

# EU application: add the base-URL override
docker run --rm \
  -e NYLAS_API_KEY="$NYLAS_API_KEY" \
  -e NYLAS_GRANT_ID="$NYLAS_GRANT_ID" \
  -e NYLAS_API_BASE_URL="https://api.eu.nylas.com" \
  ghcr.io/nylas/cli:latest \
  email list --limit 5 --json
```

Pass `NYLAS_API_KEY` and `NYLAS_GRANT_ID` on every run, and add `NYLAS_API_BASE_URL` for an EU application. The image targets non-interactive commands: the web interfaces (`nylas air`, `nylas chat`) bind to `localhost` inside the container and are not reachable through published ports, so stick to `email`, `calendar`, and `auth` subcommands. The [Nylas CLI Docker reference](https://github.com/nylas/cli/blob/main/docs/development/docker.md) documents the full image and tags.

## How do I verify the headless Gmail connection?

Run `nylas email list --limit 1` to confirm the key and grant resolve. It makes one API call against the mailbox named by `NYLAS_GRANT_ID` and returns a message, which proves authentication without sending any mail. A bad key or grant surfaces here first: `E006` means `NYLAS_GRANT_ID` is unset, and a `404` means the grant ID does not exist in this application. Use `email list` rather than `nylas auth whoami`, which reports a stored default grant and does not read `NYLAS_GRANT_ID`.

```bash
# Confirm the key and grant resolve (env-var auth)
nylas email list --limit 1 --json
```

Then send one test message to prove end-to-end delivery. The `--yes` flag skips the interactive confirmation that would otherwise hang a headless shell, and the message goes out over HTTPS through the Nylas API rather than any local mail transfer agent:

```bash
nylas email send \
  --to "you@gmail.com" \
  --subject "Headless auth works" \
  --body "Sent from a headless server with no browser and no SMTP." \
  --yes
```

## Headless Gmail authentication methods compared

Four approaches exist for reaching Gmail from a server with no browser, and they differ sharply on setup cost and who can use them. Service accounts are Workspace-only and need admin delegation; app passwords need 2-Step Verification and are often blocked in Workspace tenants; raw OAuth simply cannot run headless. The API-key-plus-grant path is the only one that is low-setup and works for any Gmail account, Workspace or consumer.

| Method | Browser on server? | Works headless? | Token refresh | Setup |
| --- | --- | --- | --- | --- |
| OAuth browser flow (`auth login`) | Required | No | Automatic | N/A |
| Google service account | No | Workspace only | Self-managed | High (admin delegation) |
| App password + SMTP | No | Yes, with 2FA | None (static) | Often blocked in Workspace |
| **Nylas API key + grant** | **Once, elsewhere** | **Yes** | **Automatic** | **Low (env vars)** |

The API-key approach also extends past Gmail without new code: the same grant model covers Outlook, Exchange, Yahoo, iCloud, and IMAP, so one headless auth pattern serves every mailbox the Nylas API supports.

## How do I troubleshoot headless Gmail auth?

Four issues cause nearly every headless failure: a keyring error on a box with no Secret Service, an unset grant ID, a grant that does not exist in the application, and an EU application hitting the default US endpoint. The fastest probe is `nylas email list --limit 1`, which exercises the key, the grant, and the endpoint in one call.

A keyring or Secret Service error at startup means the host has no desktop keyring, which is normal on a headless Linux server; set `NYLAS_DISABLE_KEYRING=true`. An `E006 “No grant ID provided”` means `NYLAS_GRANT_ID` is unset. A `404` on a grant that works from your laptop usually means an EU application is reaching the default US endpoint:

```bash
# Headless box with no desktop keyring
export NYLAS_DISABLE_KEYRING=true

# Grant must be set on a fresh box, or the CLI errors E006
export NYLAS_GRANT_ID="$NYLAS_GRANT_ID"

# EU application reaching the US endpoint → override it
export NYLAS_API_BASE_URL=https://api.eu.nylas.com

# One call that exercises key, grant, and endpoint
nylas email list --limit 1 --json
```

If the call returns `404` “not found”, the grant ID is wrong: a typo in `NYLAS_GRANT_ID`, or a grant that does not exist in this application. Verify it against the Nylas dashboard, where every connected grant is listed. Note that `nylas auth status` prints `Region: us` even when `NYLAS_API_BASE_URL` points at the EU host — the request still goes to EU; only that status field is not env-aware.

## What to set up next

With Gmail authenticated headlessly, wire it into the workflows that need it:

- [Send Email from the Command Line Without SMTP](https://cli.nylas.com/guides/send-email-from-terminal) — the full sending reference for bash scripts, cron jobs, and CI runners.
- [Deploy Agent Accounts in Docker & CI](https://cli.nylas.com/guides/deploy-agent-accounts-docker) — keep the API key out of image layers and rotate it without a redeploy.
- [Send Email from a Linux Sandbox](https://cli.nylas.com/guides/send-email-manus-sandbox) — the same API-key pattern applied to an SMTP-blocked agent sandbox.
- [Nylas CLI Command Reference](https://cli.nylas.com/docs/commands) — every `auth`, `email`, and `calendar` command with flags and examples.

## Frequently asked questions

### How do I authenticate to Gmail from a headless server or VM?

Set `NYLAS_API_KEY`, `NYLAS_GRANT_ID`, and `NYLAS_DISABLE_KEYRING=true` in the environment, then run CLI commands directly with no setup step and nothing written to disk. The grant is created once on a machine with a browser. The server never opens a browser, and tokens refresh automatically server-side.

### Why does `nylas auth login` not work on a server?

It starts Google's OAuth browser consent flow, and a headless box has no browser to render the consent screen or receive the redirect. Use API-key authentication with a pre-created grant instead, which needs no browser on the server.

### Do I need a Google service account for headless Gmail?

No. Service accounts require domain-wide delegation set by a Workspace admin and only work inside one Workspace domain. The API-key-plus-grant approach works for both Workspace and consumer Gmail accounts with no admin setup.

### How do I rotate the API key on a running server?

Swap the secret behind `NYLAS_API_KEY` and restart the process. There is no image rebuild and no command to re-run, because the CLI reads the key from the environment on every invocation, and the grant stays connected.
