CLI
smtpfast sends email and calls every API endpoint from a terminal or a script. It logs in through the browser, so you do not need to copy a key out of the dashboard.
Install
#With Node 18 or later. Standalone binaries are on the CLI's GitHub releases page.
npx smtpfast --help # run it without installing
npm install -g smtpfast # or install itSign Up
#smtpfast signup creates an account without the website. It emails an 8-digit code to the address, and entering the code creates the account and saves its first API key, as login does. The address goes through the same checks as the signup form, and the code expires after ten minutes. If the address already has an account, the email says so instead of carrying a code.
$ smtpfast signup
Email: [email protected]
Your name (optional): Ada
Sent a code to [email protected]. It works for 10 minutes.
Code from the email: 4831-0927
Created account [email protected] and team "Ada's Team". Saved key sf_a1b2c...9f0e as profile "default".
The account is pending activation. Until it is active, the API key can only check and ask for activation.A new account is pending until it is activated, the same as one made on the website: pay for a plan, request the free plan, or use an invite code. Run smtpfast activate, or see activating an account. Until then the key only works for activation. The account has no password; to use the dashboard later, set one with Forgot password on the login page.
An agent can do it without prompts, in two steps:
smtpfast signup --email [email protected] --send-only --json # prints signup_id
smtpfast signup --signup-id <signup_id> --code 4831-0927 # or pipe the code on stdin
smtpfast activate --plan growth # or --free-trial "..." or --invite <code>Set Up With smtpfast init
#npx smtpfast init walks through the setup: it logs you in or signs you up when there is no key, activates a new account, adds a domain, sets its DNS records through Domain Connect when the DNS host supports it (Cloudflare today) or prints them, waits until the domain is verified, and offers a test email to your own address. A step that is already done is skipped, so running it again is safe.
$ npx smtpfast init
[1/6] Account
[2/6] Activation
[3/6] Domain
Domain to send from, like mail.example.com (Enter to skip): mail.example.com
Added mail.example.com.
[4/6] DNS records
The DNS for mail.example.com is on Cloudflare. Approve the records there: https://dash.cloudflare.com/...
[5/6] Verification
mail.example.com is verified.
[6/6] Test email
Send a test email to [email protected]? [Y/n]Without a terminal it asks nothing. Pass --domain, and --test-email to send the test email, which goes to the address signup saved in the profile or to --to. --no-domain-connect prints the records instead of opening the Domain Connect link, --no-wait checks the domain once, --timeout 30m waits longer, and --json prints a summary of what was done.
smtpfast init --domain mail.example.com --test-email --json
smtpfast init --domain mail.example.com --no-domain-connect --timeout 30m --test-email --to [email protected]Log In
#smtpfast login prints a code and opens the approval page. Sign in, check that the page shows the same code, tick the box that says so, pick the team and approve. The CLI then receives a new API key and saves it in ~/.config/smtpfast/config.json, readable only by you.
$ smtpfast login
Open https://smtpfa.st/cli/device and check that it shows this code:
BCDF-GHJK
Waiting for approval...
Logged in to team Acme, plan growth. Saved key sf_a1b2c...9f0e as profile "default".The key is a normal API key named CLI on <hostname> (<date>). It is listed under API keys, counts towards the ten keys a team can hold, and you revoke it there. The code expires after ten minutes and works once.
smtpfast login --scopes email:send,email:read,domain:read # ask for fewer scopes
smtpfast login --no-browser # print the link, do not open it
smtpfast login --profile staging # keep several accounts
echo "$KEY" | smtpfast login # store a key you already have
smtpfast login --api-key "$KEY" # the same, as a flagWho Can Approve
#Team owners and admins, the same people who can create API keys. The key gets the scopes the CLI asked for, by default the default API key scopes. Billing scopes are granted only when the approver owns the team, because only an owner's key can use them. Key and team management are never granted unless the CLI asks for them, and the approval page spells out what they allow: a key with apikey:manage can create more keys, and those keep working after it is revoked.
Approve only a login you started yourself. Anyone can start one and send you the link, and approving it gives them a key to your team. The approval page shows the device name and the network the request came from, and warns when that network is not yours.
Device Login API
#The CLI uses two endpoints that need no API key; other tools can use them the same way. They follow RFC 8628 (OAuth device authorization) with JSON bodies. Starting a login is limited to 10 a quarter hour per network.
/v1/cli/deviceStart a login. Show user_code and verification_uri (or verification_uri_complete) to the person, keep device_code secret, and poll POST /v1/cli/token every interval seconds.
Parameters
| Parameter | Type | Description |
|---|---|---|
| client_name | string | Shown on the approval page, up to 60 characters. Default: SMTPfast CLI |
| hostname | string | The machine's name, used in the key's name |
| scopes | string[] | Scopes for the key. Default: the default API key scopes |
Request Body
{
"client_name": "SMTPfast CLI 0.15.0",
"hostname": "build-01",
"scopes": ["email:send", "email:read", "domain:read", "domain:write"]
}Response
{
"device_code": "Qm9iYnlzLWNvZGUt.dGhpcy1pcy1ub3QtYS1yZWFsLWRldmljZS1jb2RlLW9r",
"user_code": "BCDF-GHJK",
"verification_uri": "https://smtpfa.st/cli/device",
"verification_uri_complete": "https://smtpfa.st/cli/device?code=BCDF-GHJK",
"expires_in": 600,
"interval": 5
}/v1/cli/tokenPoll for the key. Until the code is approved the answer is 400 with an error: authorization_pending (poll again), slow_down (wait the interval it returns), expired_token, access_denied or invalid_grant. Once approved it returns the new key, once.
Parameters
| Parameter | Type | Description |
|---|---|---|
| device_coderequired | string | From POST /v1/cli/device |
Request Body
{
"device_code": "Qm9iYnlzLWNvZGUt.dGhpcy1pcy1ub3QtYS1yZWFsLWRldmljZS1jb2RlLW9r"
}Response
{
"id": "cm2k9x0ab0001",
"name": "CLI on build-01 (2026-10-29)",
"key": "sf_a1b2c3d4e5f6...",
"prefix": "sf_a1b2c3d4e",
"scopes": ["domain:read", "domain:write", "email:read", "email:send"],
"created_at": "2026-10-29T14:02:11.000Z",
"team": { "id": "cm1team0001", "name": "Acme" }
}HTTP/1.1 400 Bad Request
{
"error": "authorization_pending",
"error_description": "Waiting for someone to approve the code."
}Signup API
#smtpfast signup uses two endpoints that need no API key. Starting a signup answers the same whether or not the address has an account. Requests are limited per network, per address and overall, and a code allows five tries.
/v1/cli/signupEmail a signup code to the address. Keep signup_id for the next call.
Parameters
| Parameter | Type | Description |
|---|---|---|
| emailrequired | string | The address for the account |
| name | string | Up to 100 characters |
| newsletter | boolean | The monthly product update email. Default: true, as on the signup form |
| client_name | string | Shown in the email, up to 60 characters |
| hostname | string | The machine's name, used in the first key's name |
Request Body
{
"email": "[email protected]",
"name": "Ada",
"hostname": "build-01"
}Response
{
"signup_id": "q8Zr0mWf3kV1yTn6bLp2sAhE",
"email": "[email protected]",
"expires_in": 600,
"code_length": 8
}/v1/cli/signup/verifyCheck the code and create the account, pending until it is activated. Returns its first API key, once, with the default key scopes. Errors are 400 invalid_code (with attempts_remaining) or expired_code, 403 signup_refused, 409 account_exists, 429 too_many_attempts and 503 unavailable.
Parameters
| Parameter | Type | Description |
|---|---|---|
| signup_idrequired | string | From POST /v1/cli/signup |
| coderequired | string | The 8 digits from the email, with or without the dash |
Request Body
{
"signup_id": "q8Zr0mWf3kV1yTn6bLp2sAhE",
"code": "4831-0927"
}Response
{
"id": "cm2k9x0ab0001",
"name": "CLI on build-01 (2026-10-29)",
"key": "sf_a1b2c3d4e5f6...",
"prefix": "sf_a1b2c3d4e",
"scopes": ["email:send", "email:read", "domain:read", "domain:write", "..."],
"created_at": "2026-10-29T14:02:11.000Z",
"team": { "id": "cm1team0001", "name": "Ada's Team" },
"account": { "id": "cm1user0001", "email": "[email protected]", "status": "pending", "plan": "free" },
"activation": { "required": true, "docs": "https://smtpfa.st/docs/headless#activate" }
}