CLI

The Groundmail CLI sends email and manages domains, webhooks, templates and more from the terminal or a coding agent.

npm install -g groundmail-cli
# or, without installing:
npx -y groundmail-cli@latest --help

Sign in

groundmail login

login prints a link and a short code. Open the link, sign in (or sign up), check that the code matches your terminal, review what the CLI is asking for, pick the workspace and approve.

The CLI then holds OAuth tokens instead of a permanent key:

  • an access token that lasts one hour, refreshed automatically;
  • a refresh token that rotates on every use and expires after 90 days without use, kept in your OS keychain (macOS Keychain, Windows Credential Manager, or the Secret Service on Linux).

Without a keychain (a container, CI, a headless server), the refresh token is stored in ~/.config/groundmail/config.json, readable by your user only. Set GROUNDMAIL_NO_KEYCHAIN=1 to choose the file on purpose.

Sign-in works over SSH and from coding agents: without a terminal it prints the link and exits, and running groundmail login again after you approve finishes the sign-in.

Least privilege with --scope

Ask only for what you need. The approval page shows the scopes, and the tokens can do nothing else:

groundmail login --scope emails:send
groundmail login --scope emails:send,emails:read,domains:read

Without --scope the sign-in has full access. See Authentication for the scope list.

Workspaces

Each workspace you sign in to is saved separately:

groundmail login --workspace "Acme"   # preselects Acme on the approval page
groundmail workspaces list
groundmail workspaces use Acme

Who am I?

groundmail whoami

Prints the person, the workspace, how the CLI is signed in (OAuth or an API key) and its scopes.

Tokens for agents and scripts

groundmail auth token prints a short-lived access token from your sign-in. Use it wherever an API key goes:

curl https://api.groundmail.dev/emails \
  -H "Authorization: Bearer $(groundmail auth token --scope emails:send)" \
  -H "Content-Type: application/json" \
  -d '{"from":"you@yourdomain.com","to":"user@example.com","subject":"Hi","text":"Hello"}'

--scope narrows the token below what you approved (never above). The token lasts one hour; run the command again for a new one. Add --json for { access_token, token_type, scope }.

Sign out

groundmail logout              # the current workspace
groundmail logout --all

Logout revokes the sign-in on the server (its access and refresh tokens stop working at once) and removes it from your computer.

You can also see and revoke every CLI and agent signed in to a workspace in the dashboard under Settings → CLIs & agents.

CI and API keys

In CI, set an API key instead of signing in. GROUNDMAIL_API_KEY always wins over a saved sign-in:

export GROUNDMAIL_API_KEY=gm_your_key_here
groundmail emails send --from ops@yourdomain.com --to user@example.com --subject "Deployed" --text "Done"

Create a key with only the access the job needs:

groundmail keys create --name CI --scopes emails:send --allowed-domains yourdomain.com

For tests that should never deliver, create a test key (gm_test_…) with --test. Sends with it run every check and fire webhooks, print "test": true, and deliver nothing. groundmail emails list --test lists only test emails (--live only live ones). Simulator addresses such as bounce@simulator.groundmail.dev give a specific outcome with any key. See Testing.

groundmail keys create --name CI-tests --scopes emails:send,emails:read --test
GROUNDMAIL_API_KEY=gm_test_… groundmail emails send --from ops@yourdomain.com --to bounce@simulator.groundmail.dev --subject "Bounce test" --text "Simulated"

Import from another provider

Beta. Import suppressions, templates, sending domains and webhooks from SendGrid, Postmark or Mailgun. The provider key is read from an environment variable, sent for the import only, and never stored.

export SENDGRID_API_KEY=SG.…            # or POSTMARK_SERVER_TOKEN (+ POSTMARK_ACCOUNT_TOKEN), MAILGUN_API_KEY
groundmail migrate sendgrid --dry-run   # preview: nothing is written
groundmail migrate sendgrid             # import everything
groundmail migrate mailgun --only suppressions,templates --region eu

The summary (anything that needs attention, and the DNS changes for each domain) prints to stderr; the full report, including the code change and a prompt for your coding agent, prints to stdout as JSON.

Imports are kept, so you can read a report again later:

groundmail migrate list                               # recent imports
groundmail migrate report <id>                        # the report as JSON (summary on stderr)
groundmail migrate report <id> --csv > report.csv     # what was imported, skipped and failed

See Import from SendGrid, Postmark or Mailgun.

Validate an address

Check one address with Groundmail validation before you accept it. Prints the result as JSON and exits 2 when the verdict is invalid, so scripts can branch on it.

groundmail validate ada@gmial.com
# {"email":"ada@gmial.com","domain":"gmial.com","verdict":"risky","reasons":["possible_typo"],"suggestion":"ada@gmail.com","disposable":false,"role":false}

Upgrading from an older CLI

Earlier versions of groundmail login saved a permanent API key. Those keys keep working. The next groundmail login replaces the saved key with OAuth tokens and revokes the old key.