Guide
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 Director, Site Reliability Engineering
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. 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, 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:
# 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 --jsonGet the key from the Nylas dashboard 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:
# 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 --jsonFor the full containerized pattern, including keeping the key out of image layers in a CI pipeline, see the Deploy Agent Accounts in Docker & CI 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:
# 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 --jsonPass 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 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.
# Confirm the key and grant resolve (env-var auth)
nylas email list --limit 1 --jsonThen 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:
nylas email send \
--to "you@gmail.com" \
--subject "Headless auth works" \
--body "Sent from a headless server with no browser and no SMTP." \
--yesHeadless 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:
# 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 --jsonIf 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 — the full sending reference for bash scripts, cron jobs, and CI runners.
- Deploy Agent Accounts in Docker & CI — keep the API key out of image layers and rotate it without a redeploy.
- Send Email from a Linux Sandbox — the same API-key pattern applied to an SMTP-blocked agent sandbox.
- Nylas CLI Command Reference — every
auth,email, andcalendarcommand 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.