CLI
Install the mailfully CLI, authenticate once, and send mail, verify domains, inspect the message log, and read analytics without leaving the terminal.
The mailfully CLI wraps the same API the SDKs call, so anything you can do with an API key you can do from a shell script or a terminal session. It is built for two audiences: people setting an account up by hand, and scripts that need machine-readable output and dependable exit codes.
Prerequisites
- Node.js 24 or newer
- An API key from the dashboard
- A verified sending domain for live mail — test mode needs no domain
Install
npm install -g mailfully
The same package is also the Node.js SDK — install it as a project dependency instead (npm install mailfully) and you get the typed client rather than the command. The CLI is a terminal front-end over exactly that client, so the two never disagree about what the API accepts.
Verify it resolved:
mailfully --version
Quick start
mailfully login
mailfully send \
--from orders@mail.acme.com \
--to you@yourcompany.com \
--subject "Your order shipped" \
--text "Your order is on its way."
A successful send prints the message id and the environment the key belongs to:
Accepted: 01J9ZC3AB8XQ4RW2N7VKT5EMHD (live)
The environment is on every accept line on purpose. A test key and a live key look alike, and the moment you most need to know which one you used is the moment mail either does or does not reach a real person.
Authenticate
mailfully login verifies the key before storing it, so a typo fails immediately rather than on your next send. It writes to ~/.config/mailfully/config.json.
The CLI resolves a key in this order, first match wins:
--api-key <key>on any command- the
MAILFULLY_API_KEYenvironment variable - the key stored by
mailfully login
Use MAILFULLY_API_KEY in CI rather than committing a stored config. Check what you are pointed at with mailfully whoami, and clear a stored key with mailfully logout.
A mf_live_ key sends real mail to real people and bills for it. When you are experimenting, use a mf_test_ key — it routes to the mailbox simulator, never affects your reputation, and is never metered. See Test mode.
Commands
Send
mailfully send composes one message from flags. Bodies can be inline (--html, --text) or read from disk (--html-file, --text-file). --to, --cc, --bcc, --reply-to, --var, --tag, and --header are repeatable.
mailfully send \
--from orders@mail.acme.com \
--to you@yourcompany.com \
--template tmpl_01J9Z8QK2M \
--var order_id=A-4471 \
--tag category=order-shipped \
--idempotency-key "order-A-4471-shipped"
Pass --scheduled-at (ISO 8601, up to 90 days out) to schedule, and --type marketing for campaign mail — which accepts exactly one recipient across to/cc/bcc.
emails
| Command | What it does |
|---|---|
emails batch <file> | Send up to 100 messages from a JSON array of API-shape objects |
emails list | List sent mail, newest first |
emails get <id> | One message's detail |
emails events <id> | One message's event timeline |
emails cancel <id> | Cancel a scheduled message that has not been sent |
emails reschedule <id> | Move a scheduled message to a new time |
emails list filters on --status, --tag, --domain, --recipient, --search, --since, and --until. Paginate with --limit and --cursor, or pass --all to walk every page.
domains
| Command | What it does |
|---|---|
domains add <name> | Add a sending domain and print the DNS records to publish |
domains list | List this org's domains |
domains get <id> | One domain's detail and DNS records |
domains verify <id> | Re-check the domain's DNS now |
domains tracking <id> | Turn open/click tracking on or off |
templates
templates create, list, get, update, and delete. Send with a stored template using send --template <id> --var name=value. See Templates.
suppressions and analytics
suppressions list shows suppressed recipients — read-only, because adding and removing entries lives in the dashboard.
analytics daily, domains, tags, and reputation read the rollups. All four take --since and --until as ISO dates.
Script against it
Every command takes --json, which prints the API's raw response on stdout and nothing else. Human-readable hints and every error go to stderr, so a --json invocation is always safe to pipe:
mailfully emails list --status bounced --since 2026-09-01 --all --json \
| jq -r '.data[].to[]' | sort -u
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | The API refused the request, or the network failed |
2 | Usage error — an unknown flag or a missing argument |
3 | Accepted, but nothing will be sent |
Code 3 is the one worth handling deliberately. It means the API accepted the request and will dispatch no mail, because the recipients were suppressed — either a send whose recipients were all suppressed, or an emails batch where every row was canceled. A partial batch exits 0, because some mail did go out.
It is distinct from 1 on purpose: 1 is the API refusing you, 3 is the API accepting you and sending nothing. Without it, this would silently do the wrong thing:
mailfully send --from … --to … --subject … --text … && ./mark-as-notified.sh
Treat any nonzero code as failure rather than testing for 1 — more codes may be added in a minor release.
Error hints
When a send is refused, the CLI explains what will actually clear the refusal. This matters most for 429, which the API returns for six different reasons: only the per-second rate limiter clears by waiting. A daily quota, a send cap, or an account under human review will not, and the CLI says so rather than suggesting a retry that cannot work. See Rate limits and Errors.
Examples
# Verify a new sending domain
mailfully domains add mail.acme.com
mailfully domains verify dom_01J9Z8QK2M
# Ship a batch from a file
mailfully emails batch ./receipts.json
# Investigate a delivery
mailfully emails list --search you@yourcompany.com
mailfully emails events 01J9ZC3AB8XQ4RW2N7VKT5EMHD
# Watch deliverability
mailfully analytics daily --since 2026-09-01
mailfully analytics reputation
What's next
- Quickstart — the API in five minutes
- Verify a domain — what
domains addandverifyare doing - Test mode —
mf_test_keys - Node.js SDK — the library the CLI is built on
