This is the official CLI to work with Emailable from the command line.
Warning
This is prerelease software and is not yet considered production ready. Commands, flags, and output may change without notice between releases.
See the CLI docs.
macOS, Linux, WSL2:
curl -fsSL https://emailable.com/install-cli | bashWindows (PowerShell):
irm https://emailable.com/install-cli.ps1 | iexBoth scripts pick the right archive for your OS/arch, verify it against the
published checksums.txt, and install the binary (plus bundled man pages on
Unix). Override the version with EMAILABLE_VERSION=v0.2.0 or the install
prefix with EMAILABLE_PREFIX=$HOME/.local.
Homebrew (macOS):
brew install emailable/tap/emailableScoop (Windows):
scoop bucket add emailable https://github.com/emailable/scoop-bucket
scoop install emailableIn each snippet below, set ver/arch to the release you want (use
arch=arm64 on ARM). The checksums.txt step verifies the download before
installing — these are GitHub-hosted artifacts, not served from a signed repo.
Debian / Ubuntu:
ver=<version> arch=amd64
base="https://github.com/emailable/emailable-cli/releases/download/v$ver"
curl -fsSLO "$base/emailable_${ver}_linux_$arch.deb"
curl -fsSL "$base/checksums.txt" | sha256sum -c --ignore-missing
sudo apt install "./emailable_${ver}_linux_$arch.deb"Fedora / RHEL:
ver=<version> arch=amd64
base="https://github.com/emailable/emailable-cli/releases/download/v$ver"
curl -fsSLO "$base/emailable_${ver}_linux_$arch.rpm"
curl -fsSL "$base/checksums.txt" | sha256sum -c --ignore-missing
sudo dnf install "./emailable_${ver}_linux_$arch.rpm"Alpine:
ver=<version> arch=amd64
base="https://github.com/emailable/emailable-cli/releases/download/v$ver"
curl -fsSLO "$base/emailable_${ver}_linux_$arch.apk"
curl -fsSL "$base/checksums.txt" | sha256sum -c --ignore-missing
sudo apk add --allow-untrusted "./emailable_${ver}_linux_$arch.apk"go install github.com/emailable/emailable-cli@latestDownload the archive for your OS and architecture from the
releases page, verify it
against checksums.txt, extract, and drop the binary on your PATH:
tar -xzf emailable_<version>_<os>_<arch>.tar.gz
sha256sum -c checksums.txt --ignore-missing
mv emailable /usr/local/bin/The CLI supports two auth modes. Use OAuth for interactive workstations and API keys for CI, scripts, or AI agents that can't complete a browser flow.
emailable login runs the OAuth 2.0 device-authorization flow. It prints
a short user code and a verification URL; complete the prompt in your
browser and the CLI receives an access token.
emailable loginCredentials are stored at ~/.config/emailable/credentials.json. Run
emailable logout to remove the stored token. Access tokens are refreshed
automatically when close to expiry; a dim Refreshed access token. line is
printed to stderr when that happens (suppressed in --json mode).
There are two ways to use an API key. Credentials are deliberately not
accepted on the command line for everyday commands — a key in argv lands
in shell history and is visible to other users via ps. Use an env var or
save the key once via login.
Per-invocation (preferred for CI):
EMAILABLE_API_KEY=live_xxx... emailable account statusSaved (preferred for personal machines): emailable login accepts an
API key via stdin pipe or via the login-local --api-key flag. The key is
validated against /v1/account before being written to
~/.config/emailable/credentials.json, and supersedes any prior OAuth
credentials.
# Pipe from a password manager / secret store (key stays out of shell history)
op read "op://Vault/Emailable/api-key" | emailable login
# Or pass directly (lands in shell history — avoid for shared machines)
emailable login --api-key live_xxx...After saving, every subsequent command uses the stored key with no env
var or flag needed. Run emailable logout to remove it.
Resolution order when multiple sources are configured:
EMAILABLE_API_KEYenv var- Stored API key (
api_keyin~/.config/emailable/credentials.json) - Stored OAuth access token
emailable status prints the active environment, config path, and
credential source without making a network call — useful for agents
self-diagnosing a failure. (emailable account status is the separate
network-backed command that fetches the owner email + remaining credits.)
emailable status
emailable status --jsonVerify a single email address in real time:
emailable verify jarrett@emailable.com
emailable verify jarrett@emailable.com --jsonFlags (each maps to a GET /v1/verify parameter; omitted flags use the server's default):
--smtp=true|false— perform the SMTP step (default: server-sidetrue). Disabling speeds up responses but reduces accuracy.--accept-all— perform an Accept-All check. Heavily impacts response time.--timeout <seconds>— timeout to wait for response, in seconds (2–10)
Submit a batch verification job. Each input is either a literal email address,
a CSV or JSON file, or a plain-text file with one address per line. For CSV
and JSON inputs, the email column/key must be named email (case-insensitive);
otherwise pass --field <name> to point at the right one.
emailable batch verify a@example.com b@example.com
emailable batch verify emails.csv --field email
cat emails.txt | emailable batch verify -- Pass
-as a positional arg to read newline-delimited emails from stdin (plain-text format, like a.txtfile). May appear at most once and can be combined with other positional args.
Flags:
--field <name>— CSV column or JSON key holding the email (defaultemail)--wait— poll until the batch completes and print results inline--all— with--wait, print the full results table instead of a summary-o, --output <file>— with--wait, write the results to FILE (.csvor.json; format inferred from extension)--stream— emit one JSON event per line as the batch advances; implies--waitand--json(see NDJSON streaming)--url <url>— URL that will receive the batch results via HTTP POST--retries=true|false— retry verifications when mail servers return certain responses, increasing accuracy (default:true)--response-fields <list>— comma-separated list of fields to include in the response
emailable batch get 5cfcbfdeede34200693c4319
emailable batch get 5cfcbfdeede34200693c4319 --wait
emailable batch get 5cfcbfdeede34200693c4319 -o results.csvFlags:
--wait— poll until the batch completes (shows a progress bar)--partial— include partial results while the batch is still verifying (batches ≤ 1,000 emails only; mutually exclusive with--wait)--all— print the full results table inline instead of a summary-o, --output <file>— write the results to FILE (.csvor.json; format inferred from extension)--stream— emit one JSON event per line as the batch advances; implies--waitand--json(see NDJSON streaming)
Show account information and remaining credits:
emailable account statusEvery command accepts a persistent --json flag that switches output to
machine-readable JSON, making the CLI a reliable building block for scripts,
pipelines, and AI agents.
emailable verify jarrett@emailable.com --json
emailable batch get 5cfcbfdeede34200693c4319 --json
emailable account status --jsonPayloads pass through from the Emailable API unchanged — the CLI doesn't re-shape or add fields. See the API docs for the field reference. Error payloads and NDJSON stream events (below) are CLI-specific.
Pass a jq expression to --jq to filter the
JSON output in place — no external jq binary required (handy on Windows and
in minimal containers). --jq implies --json.
emailable verify jarrett@emailable.com --jq '.state'
emailable account status --jq '.available_credits'
emailable batch get 5cfc... --jq '.emails[] | select(.state == "deliverable") | .email'A string result is printed raw (unquoted, one per line), like jq -r, so it
drops straight into a script. Objects and arrays are printed as JSON. Combined
with --stream, the filter runs against each NDJSON event as it arrives (see
below).
batch verify --stream and batch get --stream emit one JSON object per
line on stdout while polling, instead of one large object at the end.
Useful for AI agents and long-running scripts that want to react to progress
without waiting for completion. --stream automatically turns on --wait
and --json, so neither needs to be passed explicitly.
emailable batch verify emails.csv --stream{"event":"submitted","id":"5cfc..."}
{"event":"progress","id":"5cfc...","processed":100,"total":1000}
{"event":"progress","id":"5cfc...","processed":500,"total":1000}
{"event":"complete","id":"5cfc...","status":"complete","reason_counts":{...},"emails":[...]}
Add --jq to filter each event as it streams. The filter sees the event
envelope (event, id, …), so guard on the event type; events the filter
doesn't match are skipped:
emailable batch verify emails.csv --stream \
--jq 'select(.event == "complete") | .emails[] | .email'On failure the CLI exits non-zero and writes a single line to stderr (stdout stays empty so pipes don't see partial output).
Human mode:
Error: Invalid email (HTTP 422)
Error: Too Many Requests (HTTP 429) (retry in 60s)
Pending: Your request is taking longer than normal. Please send your request again.
Error: dial tcp: connection refused
--json mode emits a flat JSON object — the API's response body when it's a
JSON object, otherwise a synthesized one matching the same shape. Every
error carries a stable code field that scripts and agents can branch on
without parsing the message:
{"message": "Invalid email", "status_code": 422, "code": "invalid_input"}Non-API errors (network, config, validation) omit status_code:
{"message": "dial tcp: connection refused", "code": "network"}When the server returns rate-limit headers (RateLimit-Limit,
RateLimit-Remaining, RateLimit-Reset — typically on 429), they're
attached as a sibling rate_limit field. reset is the documented Unix
timestamp, in seconds, when the window resets:
{
"message": "Too Many Requests",
"status_code": 429,
"code": "rate_limited",
"rate_limit": {"limit": 1000, "remaining": 0, "reset": 60}
}The CLI maps HTTP status / error type to a stable code. When the API
returns its own code field in the response body, the CLI passes it
through verbatim.
| Code | Meaning |
|---|---|
not_authenticated |
Missing or invalid credentials (HTTP 401) |
forbidden |
Authenticated but not allowed (HTTP 403) |
not_found |
Unknown resource (HTTP 404) |
invalid_input |
Bad request / validation failure (HTTP 400, 422) |
rate_limited |
Throttled by the server (HTTP 429) |
try_again |
Verification is still processing (HTTP 249) |
server_error |
Server-side failure (HTTP 5xx) |
network |
Connection / DNS / TLS failure |
unknown |
Anything else |
| Exit | Meaning |
|---|---|
0 |
Success |
1 |
Generic failure (unknown and anything unmapped) |
2 |
Authentication failure (not_authenticated, forbidden) |
3 |
Retry later (rate_limited, try_again) |
4 |
Invalid input or not found (invalid_input, not_found) |
5 |
Network or server failure (network, server_error) |
The HTTP client automatically retries transient responses (up to twice by
default). For 429, it honors RateLimit-Reset for the backoff window
(falling back to exponential when the header is absent or stale). For 249,
it retries briefly, then surfaces try_again with exit code 3 so scripts
know no verification result was produced.
Pass --debug (or set EMAILABLE_DEBUG=1) to dump every outgoing HTTP
request and response to stderr. The Authorization header is redacted so
the bearer token never leaks into logs.
emailable account status --debug
EMAILABLE_DEBUG=1 emailable verify hello@example.comPass --quiet (or -q) to suppress non-error human output — success
lines, hints, notices, progress bars and spinners. Errors still print, and
--json output is unaffected (quiet is a human-mode-only modifier).
Mirrors the convention in curl, docker, and gh.
emailable verify hello@example.com --quiet
emailable batch verify emails.csv --wait -qemailable version
emailable version --json
emailable --version # same as `emailable version`JSON output:
{
"version": "0.1.0",
"build_date": "2026-05-21",
"commit": "abc1234",
"dirty": false
}build_date, commit, dirty, and env are omitted when not applicable
(local checkouts without VCS info, the default API environment, etc).
The CLI checks GitHub once a day for a newer release of
emailable/emailable-cli and, when one is available, prints a single
dim line to stderr after the command's own output:
A new release of emailable is available: 0.1.0 → 0.2.0
https://github.com/emailable/emailable-cli/releases/latest
The check is unobtrusive by design:
- Runs asynchronously in a goroutine; never blocks command execution. After the command finishes the CLI waits at most 1 second for the check to return, then abandons it.
- Cached on disk at
$XDG_CACHE_HOME/emailable/update-check.json(default~/.cache/emailable/update-check.json) for 24 hours to avoid hammering GitHub. - All failures (offline, GitHub down, rate-limited, malformed cache, …) are silent — the notifier never affects exit code, stdout, or the command's behavior.
Skip conditions — the notice is suppressed automatically when:
--jsonis active (machine-readable output must stay clean)--quiet/-qis activeCIenv var is set (common-sense skip in CI)- stderr is not a TTY (no point printing a nudge to a logfile)
- The running build reports version
dev(local checkouts shouldn't nag) EMAILABLE_NO_UPDATE_NOTIFIERenv var is set to1/true/yes/on
To turn the notifier off entirely, export EMAILABLE_NO_UPDATE_NOTIFIER=1
in your shell profile.
If you installed via Homebrew (brew install emailable/tap/emailable), man
pages are installed automatically — man emailable works out of the box.
Pre-built release tarballs from the releases page ship the pages under
man/*.1; drop them into any directory on your MANPATH (run manpath to
see what that is — common locations include /usr/local/share/man/man1
and ~/.local/share/man/man1).
For development, regenerate the pages from source with:
emailable man --output ./man
# or
make manAll of the env vars the CLI honors, in one place:
| Variable | Effect |
|---|---|
EMAILABLE_API_KEY |
API key for non-interactive auth. Takes precedence over a stored API key or OAuth token. |
EMAILABLE_OUTPUT |
Default output format when --json isn't passed. Set to json to make every command emit JSON. |
EMAILABLE_DEBUG |
Any non-empty value dumps HTTP requests/responses to stderr (with Authorization redacted). Equivalent to --debug. |
NO_COLOR |
Standard no-color.org convention — any non-empty value suppresses ANSI colors. |
EMAILABLE_NO_UPDATE_NOTIFIER |
Any truthy value (1/true/yes/on) disables the daily "new release available" notifier. See Update notifier. |
CI |
When set, the update notifier is silently skipped (common-sense default in CI environments). |
XDG_CONFIG_HOME |
Base dir for the config and credentials files. Defaults to ~/.config; the CLI reads $XDG_CONFIG_HOME/emailable/config.json and stores credentials at $XDG_CONFIG_HOME/emailable/credentials.json. |
XDG_CACHE_HOME |
Where the update-check cache lives. Defaults to ~/.cache; the CLI stores the cache at $XDG_CACHE_HOME/emailable/update-check.json. |
Explicit flags always win over env vars; env vars win over the config file.
Non-secret preferences live in a JSON file with two scopes:
- Global:
~/.config/emailable/config.json(machine-wide defaults). - Project:
./.emailable/config.json(discovered by walking up from the current working directory). Overrides the global file per-field.
Both files are user-managed — the CLI reads them but never writes to them.
Credentials are deliberately stored separately in
~/.config/emailable/credentials.json.
Schema:
{
"output": "json"
}| Field | Effect |
|---|---|
output |
Default output format (human or json). Equivalent to EMAILABLE_OUTPUT. |
Per-field precedence (high → low): command-line flag → env var → project file → global file → built-in default.
Generate a completion script for your shell:
emailable completion bash # or zsh, fish, powershellSource the output from your shell's startup file. For example, to enable completion for the current zsh session:
source <(emailable completion zsh)After checking out the repo, install Go 1.22+ and run make build to produce
a local binary at bin/emailable. Run make test to run the test suite, or
make run ARGS="verify hello@example.com" to exercise the CLI without
installing it.
Common targets:
make build— compile tobin/emailablemake test— run tests with race detector and coveragemake fmt— format withgofmtmake lint— rungolangci-lintmake release VERSION=x.y.z— bumpplugin.json, commit, and tagvx.y.zmake release-snapshot— build a local snapshot release viagoreleaser
Run make release VERSION=x.y.z on a clean tree, then git push --follow-tags.
Pushing the tag triggers the release workflow.
Bug reports and pull requests are welcome on GitHub at https://github.com/emailable/emailable-cli.