A command-line interface for the Elastic Email API: send transactional emails and campaigns, manage templates, contacts, lists and segments, track delivery — interactively or fully scripted.
- Interactive mode. Run
elastic-emailwith no arguments to get a keyboard-driven TUI: browse everything, pick with arrows, and send email through a guided wizard. - Script-friendly. Every command supports
--json, exit codes are stable, and piped/CI output carries no banner or decorations. - Full sending toolkit. From transactional emails, saved templates, and campaigns to whole lists or segments.
- Node.js 20 or newer
- An Elastic Email account and API key (dashboard → Settings → API)
- A verified sending domain. Elastic Email only sends from verified domains, so your
--fromaddress must use one.
Install the elastic-email-cli package from npm:
npm install -g elastic-email-cli
elastic-email --helpor run it without installing:
npx elastic-email-cli --helpgit clone https://github.com/ElasticEmail/elasticemail-cli.git
cd elasticemail-cli
npm install
npm run build
npm link # makes the elastic-email command available globally
elastic-email --helpThe API key is resolved in this order: --api-key flag → ELASTIC_EMAIL_API_KEY environment variable → local config file.
elastic-email auth set-key # interactive, masked prompt
elastic-email auth set-key <key> # non-interactive (CI)
elastic-email auth set-key <key> --default-from sender@yourdomain.com
elastic-email auth status # where the key comes from + live connection test
elastic-email auth clear # remove the local config
⚠️ auth set-keystores the key in plaintext at~/.elastic-email-cli/config.jsonwith0600permissions. Convenient, but not an OS keychain - treat the file as a secret and prefer theELASTIC_EMAIL_API_KEYenvironment variable in shared or CI environments. The key is never printed to logs or errors (only a masked form).
A bare invocation in a terminal opens the interactive mode (also available as elastic-email tui):
elastic-email- ↑/↓ move · Enter open/select · ←/→ pages · Space multi-select · Esc back · q quit.
- Browse templates, lists (and their contacts), contacts, segments (and their contacts), delivery events, verification results, sender domains, statistics, and account info.
- Guided send wizard: sender (with verified-domain hints), recipients - addresses, or multi-selected lists/segments on Marketing-plan accounts - content (text, HTML, or a saved template), review, send.
- Verify an email address interactively; forget me (
fin Account info) removes the local config after confirmation. - Prompts for an API key on first run.
In pipes, redirects, and CI (no TTY), a bare invocation prints plain help instead - interactive mode never breaks scripted usage.
elastic-email auth set-key
elastic-email emails send --to you@example.com --subject "Hello" --text "It works!" --from me@yourdomain.com
elastic-email emails status <transactionid>
elastic-email templates create "Welcome" --html-file welcome.html --subject "Welcome!"
elastic-email emails send --to-list Newsletter --template "Welcome" --from me@yourdomain.comAdd --json to any command for clean, machine-readable output:
elastic-email account info --json
elastic-email emails send --to a@example.com --subject Hi --text Hello --from me@yourdomain.com --json | jq '.messageId'emails send --dry-run validates the input and prints the exact request that would be sent — no API call is made:
elastic-email emails send --to a@example.com --subject Hi --text Hello --from me@yourdomain.com --dry-runelastic-email auth set-key [key] # store the API key (interactive without [key])
elastic-email auth status # key source + live connection test
elastic-email auth clear # remove the local config (asks for confirmation)elastic-email emails send --to <email> --subject <s> --text <t> --from <email>
# send a transactional email
elastic-email emails send --to <email> --subject <s> --html "<b>Hi</b>" --from <email>
# HTML body (combine with --text for both parts)
elastic-email emails send --to "a@x.com,b@x.com" ...
# multiple recipients (or repeat --to)
elastic-email emails send --to <email> --template <name> --from <email>
# send a saved template (--subject optional)
elastic-email emails send --to-list <list> --template <name> --from <email>
# send to a whole list (creates an Active campaign)
elastic-email emails send # interactive compose form on a TTY
elastic-email emails send ... --dry-run # validate + preview without sending
elastic-email emails status <transactionid>
# delivery counts for a sent email
elastic-email emails status <transactionid> --recipients
# include per-recipient address lists--from is required for every send unless you configured a default sender
(elastic-email auth set-key <key> --default-from sender@yourdomain.com).
elastic-email templates list # sendable email templates
elastic-email templates list --all # every type (landing pages etc.)
elastic-email templates get <name> # metadata (--body prints the full body)
elastic-email templates create <name> --html-file <path>
# create an HTML template ("-" reads stdin)
elastic-email templates create <name> --html "<h1>Hi</h1>" --subject <s>
elastic-email templates delete <name> # asks for confirmation (--yes to skip)elastic-email contacts list # all contacts
elastic-email contacts get <email>
elastic-email contacts add <email> --first-name <n> --list <list>
elastic-email contacts add "a@x.com,b@x.com" --list <list>
# comma-separated batch
elastic-email contacts delete <email> # asks for confirmation (--yes to skip)
elastic-email lists # your contact lists
elastic-email lists contacts <name> # contacts that belong to a list
elastic-email lists create <name> --allow-unsubscribe
elastic-email lists delete <name> # contacts are kept; asks for confirmationelastic-email segments list # segments with their rules
elastic-email segments contacts <name> # contacts matching a segmentelastic-email suppressions list # all suppressed addresses
elastic-email suppressions list --type bounces --search <text>
elastic-email suppressions add <email> --type unsubscribes
elastic-email suppressions delete <email>
# allow sending again; asks for confirmationelastic-email events list # recent sends, opens, clicks, bounces...
elastic-email events list --from 2026-06-01T00:00:00 --limit 50
elastic-email verify <email> # deliverability check for one address
elastic-email verifications list # past verification resultselastic-email domains list # sender domains + SPF/DKIM/MX/DMARC status
elastic-email domains get <domain>
elastic-email account info # account name, plan, limits, account-wide BCC
elastic-email account stats # sending statistics (default: last 30 days)
elastic-email account stats --days 7Every list command (contacts list, templates list, segments list, events list,
suppressions list, lists, ...) paginates the same way - fetch the next page by
incrementing --page:
elastic-email contacts list --page 1 --page-size 50 # first page (default: page 1, 20 items)
elastic-email contacts list --page 2 --page-size 50 # next page
elastic-email templates list --page 3 --page-size 10
elastic-email contacts list --limit 10 --offset 20 # or raw limit/offset styleAn empty result means you've paged past the last item. In interactive mode use ←/→ to switch pages.
contacts delete, lists delete, templates delete, suppressions delete and
auth clear ask before they act:
$ elastic-email contacts delete jane@example.com
Delete contact jane@example.com? This cannot be undone. [y/N]Enter or n cancels, y proceeds. Pass --yes (or -y) to skip the question.
When there is no terminal to ask — CI, a pipe, or --json — the command
refuses rather than guessing, and nothing is deleted:
$ elastic-email contacts delete jane@example.com # in CI
Error: Refusing to delete contact jane@example.com without confirmation
— not an interactive terminal. Pass --yes to confirm.
So automation has to state the intent explicitly:
elastic-email contacts delete jane@example.com --yesThis also makes destructive steps greppable in a pipeline: every place that
deletes something carries a visible --yes.
| Exit | error.code (JSON) |
Meaning |
|---|---|---|
0 |
— | Success |
1 |
general |
Unexpected error |
2 |
missing_api_key |
No API key found |
3 |
invalid_input |
Invalid input: bad email, missing field, unknown flag |
4 |
api_error |
Elastic Email API / network error |
5 |
confirmation_required |
Destructive command blocked (no --yes, no terminal) |
130 |
interrupted |
Interrupted: Ctrl+C (at a prompt or while running) or SIGINT |
143 |
terminated |
Terminated by SIGTERM (e.g. a CI timeout) |
A destructive command refused for lack of confirmation exits 5, so automation can
stop and escalate for human approval instead of treating it as bad input and retrying.
With --json, errors are printed to stdout in a fixed shape, and the process
exits with the same code:
{
"error": {
"code": "confirmation_required",
"exitCode": 5,
"message": "Refusing to delete template \"Old\" without confirmation — not an interactive terminal. Pass --yes to confirm."
}
}code names are stable — branch on them rather than on message. A signal in
--json mode also produces this body (interrupted / terminated), so stdout
is never left empty.
ELASTIC_EMAIL_API_KEY # API key (overridden only by --api-key)
ELASTIC_EMAIL_CLI_CONFIG_DIR # config directory (default ~/.elastic-email-cli)elastic-email autocomplete # install instructions
elastic-email autocomplete zsh # or bash / fishnpm install
./bin/dev.js --help # run from TypeScript source, no build needed
npm run dev -- <command> # same via npm (the -- is required)
npm testArchitecture notes, project layout, and development gotchas live in CLAUDE.md. See CONTRIBUTING.md for how to report bugs and open pull requests, and please follow our Code of Conduct.
Feel free to contact us if you encounter any issues with the library. Please leave comments, concerns and requests on the Issues page.
MIT
