---
title: "Keys, members and roles"
description: "Manage API keys and read what they did, invite and manage members, write roles, check the credential you are using, and make disposable inboxes."
url: "https://nextmail.sh/docs/cli/workspace"
area: "CLI"
category: "Commands by area"
---

# Keys, members and roles

Manage API keys and read what they did, invite and manage members, write roles, check the credential you are using, and make disposable inboxes.

## Overview

These commands decide who and what can reach the workspace. `nextmail keys` manages API keys and reads what each one did, `nextmail members` manages the people in the workspace and their invitations, and `nextmail roles` defines what a member or a key may do. `nextmail me` describes the key or sign-in you are calling with, and `nextmail languages` lists the languages a translated send accepts. Disposable inboxes need no sign-in at all: `nextmail temp` is the everyday way to use one, and `nextmail temp-mail` is every call of the API behind it. `nextmail api` reaches any endpoint the other commands do not.

- A key command takes the key id, the 24 hex characters after `oe_live_`, as `keys list` shows it. A member command takes the account id, `userId` in `members list`, never an email address. A role command takes a `role_` id from `roles list`, because roles have no lookup by name.
- The namespaces answer to `key`, `member`, `role`, `language` and `tempMail` as well. The usual verb aliases work, such as `ls`, `show`, `new`, `edit` and `rm`. In `members`, whose verbs are `add` and `remove`, `new` and `create` lead to `add`, and `rm`, `del` and `delete` lead to `remove`.
- `nextmail <command> --help` lists every argument and flag with its type, the scope the call needs, its method and path, and what comes back. Add `--json` for the same page as data.

## Every command

| Command | What it does |
| --- | --- |
| nextmail me get | Describe the API key or browser sign-in you are calling with: its scopes, the role that caps it, its workspace and who it may send as. Needs no scope |
| nextmail me ping | Check that the credential authenticates, for a health check. Needs no scope |
| nextmail me rotate | Give the API key you are calling with a new secret, shown once. Asks you to confirm |
| nextmail keys list | List the workspace’s API keys, newest first, with status, scopes, role, send scope and last use. Never a secret |
| nextmail keys get <id> | Read one key, without its secret |
| nextmail keys create --name <value> | Mint a key and receive its secret once, in `token` |
| nextmail keys update <id> | Rename a key, replace its scopes or send scope, or switch it off and on with `--no-enabled` and `--enabled` |
| nextmail keys delete <id> | Remove a revoked key from the list, keeping its history. Asks you to confirm |
| nextmail keys rotate <id> | Give a key a new secret, shown once, and stop the old one at once. Asks you to confirm |
| nextmail keys revoke <id> | Revoke a key for good, with an optional `--reason`. Asks you to confirm |
| nextmail keys list-requests <id> | Read one key’s request log: method, path, status, error code, duration, IP and user agent |
| nextmail keys list-activity <id> | Read what happened to one key: created, changed, rotated, switched off and on, revoked, deleted, and every refused call |
| nextmail keys list-workspace-requests | Read the request log of every key you can see, or of the ones `--key-ids` names |
| nextmail keys list-workspace-activity | Read what happened to every key you can see, or to the ones `--key-ids` names |
| nextmail roles list | List the workspace’s roles, seeded ones first, with how many members and keys hold each |
| nextmail roles get <id> | Read one role with its permissions and live usage counts |
| nextmail roles create --name <value> --permissions <a,b> | Create a custom role, with an optional `--description` |
| nextmail roles update <id> | Rename a role, change its description, or replace its whole permission list |
| nextmail roles delete <id> | Delete a role and move whoever holds it to the role in `--reassign-to`. Asks you to confirm |
| nextmail roles list-permissions | List the permission vocabulary, with a label, a group and whether a key can hold each one |
| nextmail members list | List everybody with access, the owner first, with their role, permissions and the addresses and domains each may use |
| nextmail members get <user-id> | Read one member by account id |
| nextmail members add --email <value> --role-id <value> | Invite somebody with a role, and with addresses or whole domains through `--address-ids`, `--domain-ids` and `--access` |
| nextmail members update <user-id> --role-id <value> | Move a member to another role. Their address and domain grants stay as they are |
| nextmail members remove <user-id> | Take somebody out of the workspace with every address grant they hold. Asks you to confirm |
| nextmail members grant-address <user-id> --address-id <value> | Give a member one address, or change their `--access` to it |
| nextmail members revoke-address <user-id> <address-id> | Take one address back from a member. Asks you to confirm |
| nextmail members list-invitations | List the invitations nobody has accepted yet, expired ones included |
| nextmail members revoke-invitation <invitation-id> | Withdraw an invitation, so its link stops working. Asks you to confirm |
| nextmail members resend-invitation <invitation-id> | Send an invitation again, with a new link and 14 more days |
| nextmail languages list | List every language a translated send accepts, in the order a picker should show them. Needs no scope |
| nextmail temp new [--name <local-part>] [--domain <domain>] [--ttl <minutes>] | Create a disposable inbox and print only its address. Needs no sign-in |
| nextmail temp list | List the disposable inboxes this CLI created, without reading the network |
| nextmail temp read [inbox] [message-id] | List the mail in an inbox, or print one message as readable text |
| nextmail temp watch [inbox] [--first] | Print each new message as it lands, checking every 3 seconds |
| nextmail temp delete [inbox] [--yes] | Move the mail in an inbox to the bin now, and forget its token. Asks you to confirm |
| nextmail temp-mail list-domains | List the domains a disposable inbox can be created on. Needs no credential |
| nextmail temp-mail create | Create a disposable inbox and its inbox token, which the CLI saves. Needs no credential |
| nextmail temp-mail get <inbox-id> | Read an inbox’s expiry, extensions left and message count |
| nextmail temp-mail extend <inbox-id> | Push the expiry up to an hour further out, within 24 hours of the inbox being created |
| nextmail temp-mail delete <inbox-id> | Move the mail in an inbox to the bin now. The lease runs on. Asks you to confirm |
| nextmail temp-mail list-messages <inbox-id> | List one page of the messages, newest first, each with a short plain-text snippet |
| nextmail temp-mail get-message <inbox-id> <message-id> | Read one message with its stored body |
| nextmail temp-mail delete-message <inbox-id> <message-id> | Move one message, attachments and all, to the bin. Asks you to confirm |
| nextmail temp-mail list-attachments <inbox-id> <message-id> | List a message’s attachments by name, type and size, with no bytes |
| nextmail api <method> <path> | Call any REST endpoint with your sign-in, its verification codes and its confirmations |
| nextmail members grant-domain <user-id> --domain-id <value> | Give a member a whole domain, every address on it including ones added later, or change their `--access` to it |
| nextmail members revoke-domain <user-id> <domain-id> | Take a whole domain back from a member. Asks you to confirm |
| nextmail keys stats | Read what the keys did inside a window: the mail they sent, refused calls, requests and failures, the routes called most and the status codes. `--key-ids`, `--since`, `--until` and `--grain` narrow it |
| nextmail security list-events | Read the security log of the workspace, newest first: invitations, members, roles, the two-factor requirement, passwords set for an address and directory sync, each with who did it |

> Every flag is in the help of its command, for example `nextmail keys create --help`, `nextmail members add --help` or `nextmail temp new --help`.

## API keys

Reading keys needs `keys:read`, and every change needs `keys:manage`. A browser sign-in is never granted `keys:write` or `keys:manage`, so creating, changing, rotating, revoking and deleting keys takes an API key that holds `keys:manage`, or the web app (`nextmail open api-keys`). A browser sign-in with `keys:read` reads keys only for the workspace owner, and a member’s sign-in is refused with 403 `owner_only`.

- `keys create`, `keys rotate` and `me rotate` print the key’s secret, in `token`, once, and the CLI then warns that it is never shown again. Every read shows `maskedKey` instead.
- Left out, a new key holds `emails:send` only, and takes the role, send scope and expiry of the key creating it. `--domain-allowlist` and `--address-allowlist` set who it may send as, and `--expires-in-minutes` takes 5 to 5,256,000, which is ten years.
- A key never makes or reaches a key wider than itself. Scopes, role, expiry, mode and send scope all have to sit inside the calling key, or the call is refused with 403 `beyond_caller_authority`, and `param` names what was too wide. A key narrowed to some domains or addresses only sees the keys inside its own send scope, and any other is a 404.
- `keys update` replaces what you send: `--scopes`, `--address-allowlist` and `--domain-allowlist` each take the whole new list, and a flag you leave out stays as it was. `--no-enabled` switches a key off, so every call with it is refused with `inactive_api_key`, and `--enabled` restores it exactly. That stops a key in a way you can undo.
- `keys revoke` is for good: the key can never be switched back on, rotated or changed. `keys delete` only removes a revoked key, and any other is refused with 409 `not_revoked`. A deleted key’s request log and activity stay, under Deleted key.
- `keys rotate` has no overlap window, so the old secret stops working the moment the new one comes back. When the key is the one your saved profile uses, the CLI saves the new secret to that profile, so it keeps working. A key from `NEXTMAIL_API_KEY` or `--api-key` cannot be saved, so the CLI tells you to store the new token wherever the old key was kept.

The request log records every call a key made: method, path, status, error code, duration, IP and user agent, never a body or a query string. Nothing is pruned, so it reaches back to a key’s first call, and calls made with a browser sign-in are not in it. The activity log records every change to a key, and every call that presented the key and was refused, as `auth_failed`, with who made each change in `actor`.

- `list-requests` and `list-activity` read one key. `list-workspace-requests` and `list-workspace-activity` read every key you can see, or up to 50 that `--key-ids` names, deleted keys included.
- `--since` and `--until` keep a window and take an ISO 8601 time such as `2026-09-01T00:00:00Z`. `--failed-only` keeps the calls answered with a status of 400 or more.

## Your credential, and languages

`nextmail me get` is the first command to run when a call is refused. It needs no scope, so any valid key or sign-in can describe itself.

- `scopes` is what the credential may do right now: the scopes it was created with, cut down by the role it was issued under, worked out on every request. `grantedScopes` is what it was created with, and `roleId` names the role. A scope in `grantedScopes` and missing from `scopes` was removed by the role. That is the usual reason for a 403 `insufficient_scope` on a key that seems to hold the scope, and the fix is to change the role rather than mint another key.
- `domainAllowlist` and `addressAllowlist` say who it may send as. Both null means any address the workspace owns.
- With a browser sign-in it describes the sign-in: `object` is `oauth_token`, `clientId` names this CLI’s connected app, and `expiresAt` is when your approval ends, or null when it never does.
- `me ping` answers `ok: true` with the same scope detail but without the allowlists, which suits a health check. A revoked, expired, switched off or mistyped key fails with a 401 and exit code `3`.
- `me rotate` gives the key you are calling with a new secret. It needs `keys:write`, which a browser sign-in never holds, so it takes an API key. Everything else about the key stays, the old secret stops working at once, and a saved profile gets the new one, as with `keys rotate`. A lost answer can leave the key with a secret nobody saw, and it then needs a new one from the web app.
- `nextmail whoami` shows the same answer formatted for people.

`nextmail languages list` prints the whole language table in one answer, around two hundred rows, with each language’s code, English name, own name, flag and whether it is written right to left. The code, the English name or the own name all work as the target of a translated send. It needs a sign-in but no scope. `nextmail ai languages` prints the same table with a `--search` flag, and signed out it prints the table bundled with the CLI.

## Members and roles

A member holds two things that are never merged. Their role says what they may do, and their address and domain grants say which mail they may do it to, each grant with its own access: `member` reads and sends, and `viewer` only reads. A send needs both, so a role with `emails:send` and a `viewer` grant on an address still cannot send from it. A whole domain covers every address on it, including ones made later.

- `members list` puts the workspace owner first, marked `isOwner`, so leave that row out when counting seats. The owner holds every permission and cannot be invited, changed or removed, and neither can anybody already in the workspace be invited again: both are 422 `member_is_owner`.
- People who hold address grants but were never given a role come back with `implied: true`, and their role is inferred from their grants. `members update` gives them a real one.
- `members add` sends an invitation, even to somebody who already has an account. Nothing is granted until they accept, and then exactly the role, addresses and domains it carries. Inviting the same address again within ten minutes is 409 `invitation_too_soon`, and after that it refreshes the waiting invitation rather than sending a second.
- `resend-invitation` sends a new link good for 14 more days and retires the old one, which also renews an expired invitation. `revoke-invitation` withdraws one, and an invitation that was already accepted is 409 `invitation_accepted`, so remove the member instead.
- `members update` changes the role and nothing else. `grant-address` gives one address or changes the access to it, so running it again with another `--access` changes the grant rather than adding a second. `revoke-address` takes one address back and leaves the rest. Revoking the last grant of an implied member removes them from the workspace.
- `members remove` ends somebody’s access to the workspace, their membership and every grant, and reports how many address grants went in `addressesRevoked`. Their account and the mail they sent are untouched.

A role is also a ceiling for the API keys issued under it. What a key may do is its own scopes cut down by its role’s permissions, worked out on every request.

- `roles list` shows the seeded roles first, in the order Owner, Admin, Member, Viewer, Developer and Billing, then custom roles by name. A workspace holds up to 24 custom roles, and past that `roles create` is 422 `workspace_limit_reached`.
- A role stores the permissions its permissions imply, so `templates:write` also stores `templates:read`, and `roles:write` brings `roles:read` and `members:read`. Read the list back from the answer rather than assuming it.
- `roles update --permissions` replaces the whole list, so read the role, change the list and send all of it. `--description null` clears the note. A change is live on the next call of every member and key that holds the role.
- Every role but Owner can be renamed, rewritten and deleted, seeded ones included, and a deleted seeded role does not come back. The owner role answers an edit with 409 `role_immutable` and a delete with 409 `role_undeletable`.
- While any member, API key or waiting invitation holds a role, `roles delete` needs `--reassign-to` with the role that takes them over, or it is refused with 409 `role_in_use`. Revoked keys still point at their role, so a role whose `apiKeys` count is 0 can still need it. The answer reports `reassigned` people and `keysReassigned` keys.
- `roles list-permissions` lists the whole vocabulary with a label and a group for each. A few, such as `billing:write` and `workspace:manage`, come back with `scope: false`: a role may hold them, but no key can.

> A key holding `roles:write` can edit the role that caps it and widen itself on its next call, so keep that scope off keys that only need to read. With a browser sign-in, `members:write` and `roles:write` are granted only when the approval covers the whole workspace rather than some domains or addresses.

## Disposable inboxes

A disposable inbox needs no account and no sign-in. It is reached with its own inbox token, which begins `oe_inbox_` and comes back when the inbox is created, and again, as a new token with the later expiry, after each extension. Use `nextmail temp` day to day, and `nextmail temp-mail` when you need a field or a step that `temp` does not show, such as the extensions left, an extension, or the attachments of a message.

- Both keep the token in `~/.nextmail/temp-mail.json`, readable by you alone. `temp new` and `temp-mail create` save it, `temp list` shows inboxes made either way, and both deletes forget it. A saved inbox can be named by its address wherever a command asks for its id.
- For an inbox this CLI did not create, pass the token with `--inbox-token`. With no saved or passed token, the command stops with exit code `3` before anything is sent.
- The two create commands name their flags differently: `temp new` takes `--name`, `--domain` and `--ttl`, and `temp-mail create` takes `--local-part`, `--domain` and `--ttl-minutes`. A local part is up to 64 letters, digits, dots, dashes or underscores, starting and ending with a letter or digit, and names such as `postmaster` are refused. The lease is 1 to 1440 minutes, 60 by default.
- Nothing is counted or rate limited, so there is no ceiling on how many inboxes you create. Nothing holds an address either: a name you choose may be read by somebody else who chose it too, so leave the name out when the mail should reach you alone.
- `temp-mail extend` adds up to an hour, never past 24 hours after the inbox was created, and at most 23 times, and prints a new token that carries the later expiry. The CLI saves the new token in `temp-mail.json`, as it does after `create`, so the commands that follow use it and keep working past the old expiry. `extensionsLeft` counts only the 23 calls, not the 24 hours, and at 0 the answer is 422 `extension_limit` for good.
- `temp-mail list-messages` reads 1 to 50 messages a page, 50 by default, each with a plain-text `snippet` of up to 400 characters that often holds a one-time code. Nothing past a page is dropped, and `--all` walks every page.
- Reading a message does not mark it seen, and its body is never cut, so `truncated` is always false. `temp-mail list-attachments` gives the name, type and size of each attachment, and no bytes.
- Deleting an inbox moves the mail it shows to the bin at once and forgets its token here. The lease itself runs on until its expiry, and nothing holds the address back.

> Mail in a disposable inbox comes from strangers, to an address anyone could name. Its sender is never verified and nothing in it is scanned, so treat its links, HTML and attachments with care.

## Any endpoint, and the security namespace

`nextmail api <method> <path>` sends one request through the same transport as every other command, so your profile or key, token renewal, verification codes and confirmations all apply. A path on its own is a `GET`, and a JSON answer prints formatted. `nextmail api /keys/self` is the call behind `me get`.

- `-d`, `--data` takes the body as inline JSON, from a file with `@path`, or from stdin with `-`. `-q`, `--query` and `-H`, `--header` take `key=value` and can be repeated, and `-o`, `--out` saves the answer to a file as it came.
- A `DELETE`, and any call a resource command would confirm, such as revoking or rotating a key, asks you to confirm first, and unattended it needs `--yes`.
- A failed request prints the API error and exits with the matching code.

`nextmail security list-events` reads the security log of the workspace and needs `members:read`. The other verbs of the `security` namespace are not listed in `nextmail --help`, because `nextmail verify` drives them. `step-up-status`, `begin-step-up` and `verify-step-up` are the calls `verify` makes: `verify --status` reads the status, and `verify` asks for a code, prompts you for it and checks it. They exist for a browser sign-in. With an API key each is refused with 400 `step_up_not_applicable`, and `nextmail verify` says a key never needs a code.

## Examples

**Mint a key for a script and sign it in**

```
nextmail keys create --name 'Billing sender' --scopes emails:send \
  --domain-allowlist billing.acme.com --expires-in-minutes 129600 --json \
  | jq -r .token | nextmail login --with-token --profile billing
nextmail whoami --profile billing
```

Run it with an API key that holds `keys:manage`, for example through `NEXTMAIL_API_KEY`. The secret goes from the answer straight into a new profile, so it never lands on screen or in a file. The key can send only from `billing.acme.com`, and it expires in 90 days.

**Audit keys and their failed calls**

```
nextmail keys list --all | jq -r 'select(.status != "active") | [.name, .status, .lastUsedAt] | @tsv'
nextmail keys list-workspace-requests --failed-only --since 2026-09-26T00:00:00Z --all \
  | jq -r '[.createdAt, .keyName, .status, .errorCode, .method, .path] | @tsv'
```

**Retire a key**

```
id=4c1b257a66287fd113bd89d0
nextmail keys update "$id" --no-enabled
nextmail keys list-activity "$id" --since 2026-09-27T00:00:00Z --all | jq -r 'select(.type == "auth_failed") | .createdAt'
nextmail keys revoke "$id" --reason 'Contractor offboarded' --yes
nextmail keys delete "$id" --yes
```

Switching the key off first can be undone with `--enabled`. Every call that still presents it is refused and shows up in its activity as `auth_failed`, which tells you what still depends on it. Revoking cannot be undone, and only a revoked key can be deleted.

**Create a role and invite somebody with it**

```
nextmail roles list-permissions --json | jq -r '.[] | [.group, .id, .label] | @tsv'
role=$(nextmail roles create --name Support --permissions threads:write,emails:send,templates:read \
  --description 'Answers help@ and nothing else.' --json | jq -r .id)
nextmail members add --email sam@acme.com --role-id "$role" \
  --domain-ids 93542ff8-2baa-4f2f-841d-5ceaa074ab0d --access member
nextmail members list-invitations
```

The role comes back holding `threads:read` and `emails:read` as well, because the permissions it names imply them. Sam gets the role and the whole domain only once they accept. With a browser sign-in, `members add` asks for a verification code first.

**Move a teammate, then delete their old role**

```
old=role_8b1f4c2e9a7d3b60e5f1a2c4
new=role_2c7e9a1f4b8d3e60c5a7f1b9
user=$(nextmail members list --all | jq -r 'select(.email == "sam@acme.com") | .userId')
nextmail members update "$user" --role-id "$new"
nextmail roles get "$old" --json | jq '{name, members, apiKeys}'
nextmail roles delete "$old" --reassign-to "$new" --dry-run
nextmail roles delete "$old" --reassign-to "$new" --yes
```

`members` and `apiKeys` are counted when you ask, so they show what the delete will move. The dry run prints the `DELETE` with `reassignTo` in its query without sending it. With a browser sign-in, the update and the delete each ask for a verification code, so run `nextmail verify` first when a script does this.

**Check delivery with a disposable inbox**

```
address=$(nextmail temp new --ttl 15)
nextmail send --from hello@acme.com --to "$address" --subject 'Delivery check' --text 'Your code is 482913' --yes
nextmail temp watch "$address" --first --json | jq -r .snippet | grep -oE '[0-9]{6}'
nextmail temp delete "$address" --yes
```

`temp new` prints only the address, so it fits in a shell variable, and `temp watch --first` stops at the first message. Point a sign-up form at the address instead of `nextmail send` to catch its confirmation code the same way.

## Scopes, confirmations and errors

| Scope | Commands |
| --- | --- |
| keys:read | `keys list`, `get`, `list-requests`, `list-activity`, `list-workspace-requests`, `list-workspace-activity` |
| keys:manage | `keys create`, `update`, `delete`, `rotate`, `revoke` |
| keys:write | `me rotate` |
| roles:read | `roles list`, `get`, `list-permissions` |
| roles:write | `roles create`, `update`, `delete` |
| members:read | `members list`, `get`, `list-invitations`, `security list-events` |
| members:write | `members add`, `update`, `remove`, `grant-address`, `revoke-address`, `revoke-invitation`, `resend-invitation` |
| None, with any key or sign-in | `me get`, `me ping`, `languages list` |
| None, and no sign-in | `temp`, `temp-mail list-domains` and `create`. The other `temp-mail` commands take the inbox token |

- A sign-in or key without the scope stops with exit code `4`, names the missing scope and says how to get it.
- These ask you to confirm: `keys delete`, `rotate` and `revoke`, `me rotate`, `roles delete`, `members remove`, `revoke-address` and `revoke-invitation`, `temp delete`, and `temp-mail delete` and `delete-message`. Answering no exits with code `10` and changes nothing. Unattended and without `--yes`, they stop with exit code `2` before anything is sent.
- With a browser sign-in, `roles update` and `roles delete`, and `members add`, `update`, `remove`, `grant-address` and `revoke-address`, also ask for a verification code, unless this sign-in verified one in the last 60 minutes. `--yes` never skips it, and unattended nobody can type it, so the command stops with exit code `4`. Run `nextmail verify` first. An API key is never asked.
- `--dry-run` prints the request a change would send, with its body, and exits with code `0` without sending it or asking you to confirm.
- A list reads one page. `--limit` takes 1 to 100 and the server sends 25 when it is left out, except in `temp-mail list-messages`, which takes 1 to 50 and sends 50. `--cursor` takes the `nextCursor` of the page before. `--all` reads every page, `--max <n>` stops after that many items, and `--ndjson`, or `--all` in a pipe, prints one JSON object per line. With `--json`, a list prints one `{ items, hasMore, nextCursor }` document.
- `roles list-permissions`, `languages list`, `temp-mail list-domains` and `temp-mail list-attachments` return everything at once, as a plain array, with no pages.
- A refusal exits with the code of its status: `3` for a 401, such as a revoked key, `4` for a 403, such as `beyond_caller_authority` or `owner_only`, `5` for a 404, `6` for a 409, such as `not_revoked`, `role_in_use` or `invitation_too_soon`, `7` for a 400 or a 422, such as `member_is_owner` or `workspace_limit_reached`, and `8` for a 429, such as `send_quota_exceeded` or `upload_rate_limited`.
- A change that would do something twice is never retried after a network failure: `keys create` and `rotate`, `me rotate`, `roles create` and `delete`, `members add`, `remove`, `revoke-address` and `resend-invitation`, and `temp-mail create`, `extend`, `delete` and `delete-message`. Check before you run one again. Reads, and changes that land the same way twice, such as `keys update`, `keys revoke`, `roles update`, `members update` and `grant-address`, are retried on their own.

## Where to go next

- [API keys in the SDK](https://nextmail.sh/docs/sdk/keys.md): The same calls from TypeScript, with every field of the answer.
- [Members in the SDK](https://nextmail.sh/docs/sdk/members.md): Invitations, grants and the two axes of access.
- [Roles in the SDK](https://nextmail.sh/docs/sdk/roles.md): Writing roles, implied permissions and deleting a role in use.
- [Disposable inboxes in the API](https://nextmail.sh/docs/api/temp-mail.md): The inbox token, the lease and every limit.
- [Scopes](https://nextmail.sh/docs/api/scopes.md): What each scope allows, and how a role caps a key.
- [Verification codes](https://nextmail.sh/docs/cli/authentication.md): When a browser sign-in is asked for a code, and how to verify ahead.

## Writing to support

| Command | What it does |
| --- | --- |
| nextmail support send --topic <value> --message <value> | Write to the people who build Nextmail. The reply goes by email to the workspace owner for an API key, or to you after a browser sign-in |

- It needs no scope. One address can send 3 messages in 10 minutes, and the next one is refused with 429 `support_rate_limited`.

## Exports

`nextmail exports` makes a zip of the whole workspace, as the Export page does: every conversation as mbox files with its attachments, contacts, audiences, calendar events, templates, rules, labels, notes and addresses.

| Command | What it does |
| --- | --- |
| nextmail exports preview | See whether an export can start now and how much it would hold |
| nextmail exports list | List the exports, newest first, with their status and size |
| nextmail exports get <id> | Read one export: its status, its progress and, once ready, its size and file name |
| nextmail exports start | Start an export. It is made in the background, once a day |
| nextmail exports download <id> | Fetch the zip of a ready export into `--out <file>` |

- Only the workspace owner, or a member whose role holds every permission, can export. A key limited to particular addresses or domains is refused.
- `start` and `download` ask a browser sign-in for a verification code. An API key is never asked.

## Billing

`nextmail billing` reads and changes the plan of the workspace as Billing does: the plan and its usage, pay as you go, and every charge with its invoice. Nothing is paid from the command line: `start-checkout` and `open-portal` print a link, and the person pays, changes the card or cancels on the page it opens.

| Command | What it does |
| --- | --- |
| nextmail billing get | Read the plan, how it is billed, its usage and pay as you go |
| nextmail billing list-plans | List the plans with their prices and what they include |
| nextmail billing get-usage | Read sends and AI actions per day, and extras per month, with `--days <n>` or `--days all` |
| nextmail billing list-alerts | List what the billing banner of the app would say now |
| nextmail billing get-pay-as-you-go | Read pay as you go: whether extras are billed, the limit and what they cost so far |
| nextmail billing set-pay-as-you-go --enabled | Turn pay as you go on, or off with `--no-enabled`. The first time it prints a link to add a card |
| nextmail billing confirm-pay-as-you-go | Check that the card was saved after that link, and turn pay as you go on |
| nextmail billing set-pay-as-you-go-limit --limit-cents <value> | Set the monthly limit of pay as you go, in US cents |
| nextmail billing start-checkout --plan <value> | Print the link to a checkout for a paid plan, with `--interval month` or `year` |
| nextmail billing open-portal | Print the link to the billing portal, where the plan is changed or cancelled |
| nextmail billing list-invoices | List the charges with the state of their invoices |
| nextmail billing get-invoice <orderId> | Print the link to the invoice of a charge |
| nextmail billing save-invoice-details <orderId> | Save the name and address billed on a charge and start its invoice |
| nextmail billing list-countries | List the country codes a billing address takes |

- Only the workspace owner reaches billing: an API key, or a browser sign-in of the owner. A key limited to particular addresses or domains is refused.
- Reading needs `billing:read` and every change needs `billing:write`. Every change asks a browser sign-in for a verification code. An API key is never asked.

## Your account and workspaces

`nextmail workspaces` and `nextmail account` act on the person behind the credential rather than on a workspace: an API key acts for the owner of its workspace, and a browser sign-in for you. Reading needs `account:read` and every change needs `account:write`.

| Command | What it does |
| --- | --- |
| nextmail workspaces list | List the workspaces your account can open, with the one the app opens |
| nextmail workspaces get-active | Read the workspace the app opens for you |
| nextmail workspaces set-active <workspace-id> | Choose the workspace the app opens. The CLI keeps acting on the workspace of its credential |
| nextmail workspaces create --name <value> | Create a workspace you own, on the Free plan |
| nextmail workspaces delete <id> --confirm <value> | Delete the workspace the credential belongs to, with everything in it. Asks you to confirm |
| nextmail account get-notifications | Read which email you get, and the workspaces muted on your phone |
| nextmail account set-email-notification <category> --enabled | Turn `activity` or `product` email on, or off with `--no-enabled` |
| nextmail account set-push-muted <workspace-id> --muted | Mute a workspace on your phone, or unmute it with `--no-muted` |
| nextmail account set-photo <data> | Set your profile photo from a file, or from stdin with `-`: PNG, JPEG, WebP or GIF up to 5 MB |
| nextmail account remove-photo | Remove your profile photo. Asks you to confirm |
| nextmail account get-username | Read your username and the free address it gives you |
| nextmail account check-username <username> | Check whether you could choose a username |
| nextmail account set-username <username> | Choose your username for good. Asks you to confirm |
| nextmail account list-connected-apps | List the apps you connected and what each may reach |
| nextmail account revoke-connected-app <client-id> | Remove a connected app, so it has to ask again. Asks you to confirm |
| nextmail account list-invitations | List the invitations to join a workspace that wait for you |
| nextmail account accept-invitation <invitation-id> | Join the workspace of an invitation, and open it in the app with `--activate` |
| nextmail account decline-invitation <invitation-id> | Turn an invitation down for good. Asks you to confirm |
| nextmail account list-sessions | List the browsers and phones you are signed in on, with the place and the last use of each |
| nextmail account revoke-session <session-id> | Sign one browser or phone out. Asks you to confirm |
| nextmail account revoke-sessions | Sign every browser and phone out, and with `--connected-apps` remove the connected apps too. Asks you to confirm |
| nextmail account list-security-events | Read your security log, newest first: sign-ins, devices signed out, and password, two-factor and passkey changes |
| nextmail account list-passkeys | List the passkeys that can sign in to your account |
| nextmail account rename-passkey <passkey-id> <name> | Give a passkey a name you will recognise |
| nextmail account remove-passkey <passkey-id> | Remove a passkey, so it can no longer sign in. Asks you to confirm |

- `workspaces delete` takes only the workspace the credential belongs to and needs its owner. `--confirm` is the name of the workspace, and a browser sign-in is asked for a verification code. The first workspace an account made cannot be deleted.
- A browser sign-in reaches your account once you gave the CLI `account:read` or `account:write`, when you signed in or later in Account settings, Command line (`nextmail open cli`).
- `account revoke-sessions` leaves no browser or phone signed in, because the CLI is not one of them. With `--connected-apps` every connected app goes too, except the sign-in the CLI itself is using.
- A passkey is added in the web app, in Account → General → Security (`nextmail open security`), on the device that holds it.
