Authentication
Sign in with your browser or an API key, keep several profiles, and verify a code before a sensitive change.
Two ways to sign in
Run nextmail login in a terminal and it asks which you want. Either way the sign-in is saved as a profile, and every later command uses the active one.
| Command | Acts as | Verification codes |
|---|---|---|
| nextmail login | You, in the workspace and with the access you approve | Asked before a few sensitive changes |
| nextmail login --with-token | The workspace, with the scopes the key holds | Never asked |
- Only a browser sign-in can use
ai compose,ai summarizeand the MCP commands. - A browser sign-in lasts until the approval you chose expires, or until you sign out. A key works until it is revoked.
Browser sign-in
nextmail loginregisters a new app for this sign-in, namedNextmail CLI on <your computer>, and opens the Nextmail approval page in your browser. If the browser does not open, use the link it prints.- Sign in if you need to, then choose the workspace, the access the CLI gets (read, read and send, full, or your own set of permissions), the domains or addresses it reaches, and how long the approval lasts.
- Approve. The browser hands the approval back to the terminal on its own, and you can close the tab. The CLI prints who you are signed in as, the workspace, and when the approval expires.
nextmail loginnextmail login --scopes emails:send,threads:readnextmail login --profile work- The CLI waits 10 minutes for you to approve. Choosing Not now on the approval page cancels the sign-in, with exit code
10. --scopespreselects permissions on the approval page, and you can still change them there.- When the profile already holds a sign-in, a terminal asks before replacing it. Unattended it refuses, unless you pass
--forceor--yes. Replacing a browser sign-in revokes the old one. - A browser sign-in needs a person to approve it. With
--no-input, in CI, or when neither stdin nor stderr is a terminal,loginstops with exit code2before it registers anything, and points at--with-token.
Each browser sign-in is its own connected app, listed in Account → Command line with the access you approved, where you can change or remove it. nextmail open cli opens that page.
Underneath is the OAuth flow the MCP server uses: a public client with PKCE, a one-time code, and an access token that lasts an hour and is refreshed for you. The browser returns to 127.0.0.1 on a random port, and only the code of this sign-in is accepted there.
Over SSH, or without a browser
When the CLI cannot open a browser on this machine, it prints the link instead: over SSH, on Linux without a display, or when you pass --no-browser. Open the link in a browser on any device and approve. The page then shows a sign-in code, which you paste into the terminal.
$ nextmail login --no-browserOpen this link in a browser on any device to sign in: https://api.nextmail.sh/auth/mcp/authorize?response_type=code&client_id=…Paste the code from your browser- A code works only for the sign-in that printed the link, so a code from another tab is refused.
- Pasting the whole address the browser ended on works too.
- The code can also be piped in on stdin, while stderr shows the link.
API keys
An API key signs a script in with no browser, and is never asked for a code. Create one in Settings → API keys (nextmail open api-keys) with only the scopes the script needs. The CLI checks the key with GET /keys/self before saving it, and takes oe_live_ and oe_test_ keys. Mail sent with a test key is never delivered.
nextmail login --with-token < ~/.config/nextmail/keyecho "$NEXTMAIL_KEY" | nextmail login --with-token --profile cinextmail login --token oe_live_…--token works too, but the key lands in your shell history, so the CLI warns you and suggests --with-token. Two ways use a key without saving it:
NEXTMAIL_API_KEYin the environment is used by every command that sees it, instead of the active profile. When it makes a change while a profile is saved, the CLI warns once on stderr.--api-key <key>is used for that one command.
When there is more than one credential, the first of these wins: --api-key, the profile named by --profile, NEXTMAIL_API_KEY, the profile named by NEXTMAIL_PROFILE, then the active profile. A flag always beats the environment.
Profiles
A profile is one saved sign-in, of either kind. The first is called default. Sign more in with --profile, which leaves the active profile as it is unless none is active, and switch between them:
nextmail login --profile worknextmail profile listnextmail profile use worknextmail inbox --profile defaultNEXTMAIL_PROFILE=work nextmail statusnextmail profile currentnextmail profile remove workprofile listshows each profile with its kind, workspace and user or key, and marks the active one. Its JSON never includes a token or a key.profile currentprints only the name on stdout, so$(nextmail profile current)works in a script.profile remove <name>is the same asnextmail logout --profile <name>, and a name that is not saved exits with code5from either.- A profile name is up to 64 letters, digits, dots, dashes and underscores.
profile useis alsoprofile switch. Removing or signing out of the active profile leaves no profile active, and the next command that needs a sign-in points atnextmail profile use <name>.
Which API a sign-in talks to
A saved profile remembers the API it signed in to, and its credential is only ever sent there. A --base-url or NEXTMAIL_BASE_URL that names another origin stops the command with exit code 2 before anything is sent, and says how to sign in to that origin as a profile of its own.
nextmail login --profile other --base-url https://api.example.comnextmail inbox --profile other- A key from
NEXTMAIL_API_KEYor--api-keyis not a saved profile, so it goes to the origin in--base-urlorNEXTMAIL_BASE_URL, or tohttps://api.nextmail.shwhen neither is set. - Commands that send no credential follow
--base-urlandNEXTMAIL_BASE_URLwhatever profile is active:temp new, methods that need no key,docsandopen. - A disposable inbox keeps the API origin that created it, and its inbox token is only ever sent there.
temp read,temp watch,temp deleteand thetemp-mailcommands that use a saved token stop with exit code2when--base-urlorNEXTMAIL_BASE_URLnames another origin. A token passed with--inbox-tokenfollows them, like a key passed with--api-key. - Plain
httpis refused for every origin exceptlocalhost, a127.x.x.xaddress and::1, with exit code2: the API, the web app, the sign-in, token and revoke requests, and the MCP server. Usehttpsfor anything else. An origin on0.0.0.0or[::], where a server listens, stops with exit code2whatever the scheme, and the error names the loopback address to use instead. - A request path that would leave the API origin, such as
nextmail api //example.com/x, stops with exit code2andinvalid_pathbefore anything is sent.
What each sign-in cannot do
A browser sign-in acts as you, but some things are never approved for an app, whatever access you choose:
- Managing API keys.
keys:writeandkeys:manageare never granted, so creating, rotating and revoking keys needs an API key that holdskeys:manage, or the web app.nextmail me rotaterotates the key you are calling with, so it takes an API key. - Other workspaces. A sign-in acts for the one workspace you approved it for:
workspaces createandworkspaces set-activenever move it, andworkspaces deletereaches only that workspace, and only when you own it. - Your free address. An app is approved for a business workspace, and the personal workspace that holds the free address is never offered, the same rule the API follows.
- Members and roles, unless the approval covers the whole workspace.
members:writeandroles:writeare dropped from an approval limited to some domains or addresses.
An API key has a limit of its own. ai compose, ai summarize and every nextmail mcp command except config go through the MCP server, which takes a browser sign-in, so with a key they stop with exit code 4 and say why.
Verification codes
With a browser sign-in, a few changes ask for a verification code first, as they do in the web app. The CLI asks when it needs one: it emails you a six-digit code, or, when two-factor sign-in is on, asks for a code from your authenticator app or one of your backup codes. Once the code is right the command runs, and that sign-in is not asked again for 60 minutes. An API key is never asked.
| Command | Asks for a code |
|---|---|
| webhooks create, update | Always |
| rules create, update | Always |
| roles update, delete | Always |
| members add, update, remove | Always |
| members grant-address, revoke-address | Always |
| members grant-domain, revoke-domain | Always |
| domains delete, delete-address | Always |
| domains update-address | When it sets --destination |
| domains add-address-forwards, update-address-forward, delete-address-forward, resend-address-forward-consent | Always |
| domains set-address-login | Always |
| domains create-app-password | Always |
| domains send-mail-app-setup | Always |
| domains set-dns-zone, sync-dns | Always |
| dns-connections delete | Always |
| audiences delete | For an audience you created |
| app-host set | When it replaces a host the workspace already has |
| app-host delete | Always |
| forms delete | Always |
| encryption publish-key | Always |
| exports start, download | Always |
| billing set-pay-as-you-go, set-pay-as-you-go-limit, confirm-pay-as-you-go, start-checkout, open-portal, save-invoice-details | Always |
| workspaces delete | Always |
| mcp call | When the tool is one the API Authentication page lists |
| api | When the operation it calls is one of the above |
| billing start-add-on-checkout, cancel-add-on | Always |
| sending resume-stream | Always |
| automations delete | Always |
$ nextmail webhooks create --url https://acme.com/hooks/nextmailWe emailed a code to a•••@acme.com.Verification code: 482913Verified. You will not be asked again for 60 minutes.- Type
rat the prompt to have the email sent again. A wrong code says how many tries are left. - After the code is accepted the command runs once more, never twice.
--yesconfirms a deletion, but never skips a code.- Unattended (with
--jsonor--no-input, in CI, or without a terminal) nobody can type the code, so the command stops with exit code4and changes nothing. - A code allows 5 tries, and after the fifth wrong one the CLI offers a new code. Each sign-in can ask for 5 codes an hour and 20 a day.
- Ten wrong codes for one sign-in within 24 hours pause its verification, and twenty within 24 hours across all the apps you connected pause every app’s, while codes entered on the website still work. The CLI then says when verification resumes and stops with exit code
4andstep_up_paused, without offering another code, and the email explaining it names the app. - Emptying an audience never asks for a code.
Run nextmail verify before a script or an AI client does something sensitive. It asks for the code now, and for the next 60 minutes every command on that profile runs without one, nextmail mcp call and the local MCP bridge included.
nextmail verifynextmail verify --statusnextmail verify --status --jsonnextmail verify --forceThe 60 minutes belong to one sign-in. Another profile, or an AI client that signed in on its own, is asked for its own code, and signing out ends them at once. --force asks for a new code and starts a fresh 60 minutes. An AI client that cannot ask you for a code can be allowed from the website instead: choose Allow changes for 60 minutes in its menu in Account → Connected apps (nextmail open apps).
Expiry, sign-out and revocation
- The access token behind a browser sign-in lasts an hour. The CLI refreshes it before it runs out and saves the new one, so you never see it happen.
- Each refresh token works once. Two commands that renew the same profile at the same moment get the same new pair, so neither signs the other out. An old one used more than 30 seconds after the CLI replaced it, for example from a copy of
config.jsonon another machine, makes the server revoke that sign-in’s tokens and its verification, and the profile has to sign in again, so sign in on each machine rather than copying the file. - The approval lasts as long as you chose on the approval page. When it ends, or when the app is removed in Account → Command line, the CLI can no longer act for you and asks you to run
nextmail loginagain. nextmail logoutrevokes a browser sign-in on the server, which removes it from Account → Command line, then forgets it on this device, even when the server cannot be reached.--profile <name>picks the profile, and a name that is not saved exits with code5.--allsigns out of every profile, and cannot be combined with--profile.- Signing out of an API key only forgets it here. The key keeps working until you revoke it, with
nextmail keys revoke <id>or in the web app.
Where sign-ins are kept
Everything lives in ~/.nextmail, or in the folder NEXTMAIL_CONFIG_DIR names. The folder is readable by you alone (0700), and so is every file in it (0600). Each file is written to a temporary file and renamed into place, so a crash never leaves half of one, and every change is made under a lock file, so commands running side by side never lose a profile.
| File | What it holds |
|---|---|
| config.json | Your profiles: API keys, access and refresh tokens, and which profile is active |
| temp-mail.json | The disposable inboxes this CLI created, with their inbox tokens |
| update-check.json | When npm was last asked about a new release, and what it said |
Tokens and keys are stored as plain text in files only your user can read, so treat the folder as you would an SSH key. A file the CLI cannot make sense of is never read as signed out in silence: it warns once with the path, and keeps a copy beside it (config.json.bak) before writing a new one. A file it cannot read at all, for example because of its permissions, stops the command with an error that names it.
Google Workspace