Command line
mailctl is the whole API from a shell: send a test in one
line, watch what happened to it, and script the rest with
--json.
npm i -g ./esendblue-cli.tgz
Node 18 or newer. The binary is mailctl.
Not on the public npm registry yet. Until it is, run
npm pack in packages/cli and install the
tarball it writes. Everything below is the same once it is on your
PATH.
Log in
echo "$ESENDBLUE_KEY" | mailctl login --key - --url https://api.esendblue.com
Pipe the key in; do not type it as an argument.
--key ep_… still works, but it stays in your shell
history and ps shows it to every other account on the
machine while the command runs. --key - reads it from
standard input, so it is never an argument at all.
The profile is written to ~/.config/mailctl/config.json,
readable only by you. Check what is active with:
mailctl whoami
mailctl health
The profile, and overriding it
Environment beats the saved file, and a flag beats both — so CI never has to write anything to disk.
| Name | What it does |
|---|---|
MAILCTL_API_KEY | The key. Set this in CI instead of running login. |
MAILCTL_API_URL | The API origin. |
MAILCTL_ORG_ID | Only with a dashboard session token. |
--key, --url, --org | Override the profile for one command. |
--json | Raw JSON instead of a table, for piping into jq. |
Send an email
mailctl emails send \
--from "Your Company <receipts@yourdomain.com>" \
--to customer@example.com \
--subject "Your receipt" \
--html "<p>Thanks for your order.</p>"
--to takes a comma-separated list. The other flags:
| Name | What it does |
|---|---|
--text | A plain-text part alongside the HTML. Worth sending. |
--template | A published template id instead of a body. |
--send-at | An ISO timestamp. The message is scheduled, and can be cancelled until it goes. |
--attach | A path. Read from disk and attached under its own filename. |
--tag | k=v, comma-separated for several. Your own labels, for filtering later. |
This is the fastest way to prove a new domain works before writing any code.
Find one, and see what happened
mailctl emails list --status bounced --limit 50
mailctl emails list --q customer@example.com
mailctl emails get <id>
mailctl emails cancel <id>
get shows the message and every event it collected —
queued, sent, delivered, opened, clicked, bounced. That timeline is the
answer to “what happened to this one email”.
Inbound
mailctl inbound list
Mail that arrived at a domain with receiving turned on. Nothing appears here until the MX record is published.
Contacts
mailctl audiences list
mailctl audiences create "Donors"
mailctl contacts list --audience <id> --q mumbai
mailctl segments list
mailctl topics list
Import and export
mailctl contacts import <audience-id> --file contacts.csv
mailctl contacts export --audience <id> --out contacts.csv
The CSV needs a column called email; every other column
becomes a property on the contact, which you can then segment on and use
as a {{variable}}.
Broadcasts
mailctl broadcasts list
mailctl broadcasts send <id> --at 2026-09-10T09:00:00Z
mailctl broadcasts cancel <id>
mailctl broadcasts metrics <id>
metrics gives recipients, delivered, bounced, failed,
opened and clicked — and clicks per link, which is the number that says
whether the email did its job.
Automations
mailctl automations list
mailctl automations activate <id>
mailctl automations pause <id>
mailctl automations runs <id>
mailctl events send cart.abandoned --email donor@example.com
events send is how your own systems drive an
event-triggered automation, and how a run parked on
wait_for_event is let go.
Domains and keys
mailctl domains add mail.yourcompany.com
mailctl domains list
mailctl domains verify <id>
mailctl keys list
mailctl keys create "checkout service" --scopes emails:send
mailctl keys revoke <id>
A created key is printed once. It goes to standard output, so it lands in your scrollback — and in the log, if you run this in CI. Pipe it straight where it belongs rather than reading it off the screen.
Suppressions
mailctl suppressions list
mailctl suppressions add bad@example.com
mailctl suppressions remove good@example.com
Hard bounces and complaints arrive here on their own and are honoured on every send. Remove one only when you are sure it was a mistake.
Webhooks
mailctl webhooks list
mailctl webhooks add https://yourapp.com/hooks/mail
mailctl webhooks delete <id>
Metrics and logs
mailctl usage --from 2026-09-01 --to 2026-09-30
mailctl metrics --from 2026-09-01 --to 2026-09-30
mailctl logs --limit 50
metrics reports delivery and engagement, with a breakdown
by receiving provider, the top bounce reasons, and the most clicked
links. logs lists the API requests your integration
actually made and what we answered — usually the fastest way to settle
whether a problem is yours or ours.
Scripting
--json turns any command's output into the raw API
response, so the CLI composes with everything else.
# every domain that is not verified yet
mailctl domains list --json | jq -r '.domains[] | select(.status != "verified") | .domain'
# suppress everything that bounced this week
mailctl emails list --status bounced --limit 200 --json \
| jq -r '.emails[].to[]' | sort -u \
| while read -r addr; do mailctl suppressions add "$addr"; done
Values printed as a table have their control characters removed first: a
contact name comes from whoever filled in the form that created it, and
a terminal does what escape sequences tell it. --json gives
you the untouched response.
Exit codes
| Name | What it does |
|---|---|
0 | The command did what it said. |
1 | Anything else — a rejected request, a missing flag, an unreachable API. The reason is on stderr, with the API's own error code. |
So set -e and && behave the way a
script expects.
Every command
| Name | What it does |
|---|---|
login, whoami, health | Save a profile, show the active one, check the API is reachable. |
emails | send, list, get, cancel |
inbound | list |
domains | list, add, verify, delete |
keys | list, create, revoke |
audiences | list, create |
contacts | list, import, export |
segments, topics | list |
broadcasts | list, send, cancel, metrics |
automations | list, activate, pause, runs |
events | send |
suppressions | list, add, remove |
webhooks | list, add, delete |
usage, metrics, logs | Volume, delivery and engagement, and the raw API request log. |
mailctl --help prints the same list. Building this into an
application instead? The Node SDK covers the
same surface.