Skip to content

CLI

Available

The emithook CLI is a thin client over the same scoped management API (via @emithook/sdk) — it never speaks HTTP directly, so the console, CLI and MCP server stay in lockstep. Every command reads and writes JSON; add --json to any read command for machine output that pipes straight into jq and CI runbooks.

The full command surface is live: send, endpoint/destination/domain management, event and request inspection, inbound-email routing, observability, and an api escape hatch for anything the typed commands don't wrap yet.

Install & authenticate

bash
npm i -g @emithook/cli

Authenticate one of two ways. Interactively, emithook login runs an OAuth device grant — it prints a code, opens the console to confirm it, and stores the resulting session locally:

bash
emithook login          # device-grant flow against the console
emithook whoami         # confirm the active org + identity

For CI and scripts, set a scoped API key instead — EMITHOOK_API_KEY always wins over a stored login session, so the same command runs unchanged in a pipeline:

bash
export EMITHOOK_API_KEY=ek_live_…                       # scoped key; overrides any login session
export EMITHOOK_BASE_URL=https://api.emithook.com       # optional: management API base (self-host override)
export EMITHOOK_CONSOLE_URL=https://console.emithook.com # optional: console base used by the login device grant

EMITHOOK_BASE_URL points the SDK at a self-hosted management API; EMITHOOK_CONSOLE_URL points the login device grant at the matching console. Keys are ek_live_…/ek_test_… and carry the same read/write/admin scopes as everywhere else.

Command model

Commands are noun-verb — a resource group (endpoints, destinations, events, domains, aliases, auth) followed by an action. The common operational verbs also have flat aliases kept for muscle memory and short scripts: logs (= events list), replay (= events replay), login/logout/whoami (= auth …), keys current (= auth keys current).

bash
emithook help                 # list every command group
emithook help endpoints       # drill into one group
emithook endpoints --help     # same, via the --help flag on any command

Add --json to any read command to get the raw API envelope instead of the human table. IDs are the prefixed, ULID-based strings from the glossary (ep_, dst_, evt_, dom_, msg_, …) — pass them verbatim.

Send

bash
# to a registered destination or a validated HTTPS URL
emithook send <destination> --type <event_type> --payload <json> [--idempotency-key <key>]

# fan out to every destination bound to an application
emithook send app <app_id> --type <event_type> --payload <json>

# example
emithook send dst_acme --type invoice.created \
  --payload '{"id":"INV-1","amount":4999}' --idempotency-key inv_001

--idempotency-key makes a retried send safe — the same key inside the window returns the original result (see idempotency).

Outbound email

Send transactional email and inspect its delivery. See Send email.

bash
emithook email send --from <addr> --to <addr[,addr]> --subject <s> \
  (--html <h> | --text <t>) [--attach <path[,path]>] [--attach-url <url[,url]>] \
  [--idempotency-key <key>]
emithook email list [--status <s>] [--since <t>] [--until <t>] [--cursor <c>] [--limit <n>]

# example
emithook email send --from no-reply@acme.send.emithook.com \
  --to buyer@example.com --subject "Your invoice" --text "Thanks for your order."
emithook email list --status bounced --limit 20

--to takes one or more comma-separated recipients. --from's domain must match a verified sending identity. --status is one of sending/delivered/bounced/complained/suppressed/failed/dlq.

--attach reads each file locally and base64-encodes it; --attach-url hands a public https URL to Emithook to fetch at accept time. Both are comma-separated, and share one limit: 20 attachments and 10 MB decoded per request. See Send email.

Endpoints

Manage the inbound entries you hand to providers.

bash
emithook endpoints create --url <receive-url> \
  [--preset <name>] [--destinations <csv>] [--response-rules <json>]
emithook endpoints get <id>
emithook endpoints update <id> [--url <receive-url>] \
  [--preset <name>] [--clear-preset] [--status active|paused] \
  [--destinations <csv>] [--response-rules <json>] [--clear-response-rules]
emithook endpoints delete <id>
emithook endpoints set-secret <id> --secret <s>

# example
emithook endpoints create --url https://wh.emhk.in/acme/shopify-prod --preset shopify \
  --destinations dst_warehouse,dst_analytics

You create an endpoint by the receive URL itself — the URL you hand to the provider — and the server resolves it into a domain, slug and path. The host must be one of your ingest domains (emithook ingest-domains list); on a shared platform host the org segment is optional, so --url https://wh.emhk.in/shopify-prod works too.

On update, --url moves the whole route at once — so a URL with no path clears the path. Use --clear-preset / --clear-response-rules to remove a value, and --status paused to stop accepting deliveries without deleting the endpoint.

Destinations

The central outbound registry.

bash
emithook destinations list [--type <t>] [--validation <v>] [--cursor <c>] [--limit <n>]
emithook destinations create --name <name> --type <type> [--url <url>] [--config <json>]
emithook destinations validate <id>

# example
emithook destinations create --name Warehouse --type https \
  --url https://api.acme.dev/hooks
emithook destinations validate dst_warehouse   # runs the connectivity/credential check

Events & requests

events are delivered outbound events and their attempts; requests are the raw inbound webhooks the front door received (including unknown/dropped ones). Both are replayable, and both share the DLQ.

bash
emithook events list [--status <s>] [--endpoint <id>] [--event-type <t>] \
  [--since <iso>] [--until <iso>] [--cursor <c>] [--limit <n>] [--follow]   # alias: logs
emithook events get <id>
emithook events replay <id>                                                  # alias: replay
emithook requests list [--endpoint <id>] [--outcome <o>] [--since <iso>] \
  [--until <iso>] [--cursor <c>] [--limit <n>]
emithook requests get <id>
emithook requests replay <id>
emithook dlq redrive [--endpoint <id>] [--since <iso>]                       # bulk-replay dead-lettered events

# tail failing deliveries for one endpoint (polls; prints each event once)
emithook events list --status failed --endpoint ep_razorpay --follow

# bulk-replay everything that dead-lettered since a cutoff
emithook dlq redrive --endpoint ep_shopify --since 2026-07-01T00:00:00Z

--follow turns events list into a live tail; replays are flagged webhook-replayed: true on the receiver.

Domains

Verify custom webhook and email domains.

bash
emithook domains list
emithook domains add <domain> [--kind webhook|email|email-send]
emithook domains update <id> --kind <email|email-send> [--acknowledge-mx-conflict]
emithook domains verify <id>
emithook ingest-domains list          # the shared platform ingest domains you can attach to

# example
emithook domains add hooks.acme.dev --kind webhook
emithook domains add notify.acme.dev --kind email-send   # sends only; no MX, never receives
emithook domains verify dom_acme      # re-check DNS after adding the records

--kind email receives mail (and can additionally be upgraded to send); --kind email-send sends only — no MX is issued, no alias can route on it, and it verifies on ownership + DKIM alone. See Send-only email domains.

domains update converts between those two in place. It is refused while an alias still receives on the domain, and converting back to email publishes an MX that redirects the domain's mail — hence --acknowledge-mx-conflict. See Switching between receiving and send-only.

Inbound email

Route inbound email through platform or custom-domain aliases, then read the received messages, raw MIME, and attachments.

bash
emithook aliases list [--cursor <c>] [--limit <n>]
emithook aliases create --kind platform|custom --local-part <lp> [--domain-id <id>] \
  [--destinations <csv>] [--allowed-senders <csv>] [--max-size-bytes <n>]
emithook aliases get <id>
emithook aliases update <id> [--destinations <csv>] [--allowed-senders <csv>] \
  [--clear-allowed-senders] [--max-size-bytes <n>] [--clear-max-size]
emithook aliases delete <id>

# read the mailbox for an alias
emithook aliases messages <alias> [--cursor <c>]
emithook aliases messages get <alias> <messageId>
emithook aliases messages raw <alias> <messageId> [--out <file>]
emithook aliases messages attachment <alias> <messageId> <index> [--out <file>]

# example
emithook aliases create --kind custom --local-part support --domain-id dom_acme \
  --destinations dst_helpdesk --allowed-senders '@acme.com'
emithook aliases messages support --cursor eyJ…
emithook aliases messages attachment support '<abc@buyer.com>' 0 --out invoice.pdf

Omit --out to stream the raw message or attachment to stdout; provide it to write the bytes to a file.

Observability

bash
emithook metrics get <key>            # pre-aggregated per-endpoint / per-destination rollup
emithook pull <endpoint> [--cursor <c>] [--limit <n>]   # cursor-based pull delivery for firewalled consumers

# example
emithook metrics get ep_shopify --json
emithook pull ep_firewalled --limit 100

Auth

bash
emithook auth login                   # alias: login
emithook auth logout                  # alias: logout
emithook auth whoami                  # alias: whoami — active org + identity
emithook auth keys current            # alias: keys current — inspect the active key (masked value + env)

auth keys current inspects the credential in use and never prints the full secret. auth logout clears the stored login session; it does not touch EMITHOOK_API_KEY (unset that env var yourself).

Shell completion

Generate a completion script for your shell and load it. Emit to stdout, then install per your shell's convention:

bash
# zsh — write into a directory on your $fpath, then restart the shell
emithook completion zsh > "${fpath[1]}/_emithook"

# bash
emithook completion bash > /etc/bash_completion.d/emithook

# fish
emithook completion fish > ~/.config/fish/completions/emithook.fish

api passthrough

For any endpoint the typed commands don't wrap yet, api sends a raw authenticated request through the same client and prints the response:

bash
emithook api <METHOD> <path> [--data <json>] [--query <qs>] [--json]

# example
emithook api GET /v1/endpoints
emithook api GET /v1/events --query 'status=failed&limit=10'
emithook api POST /v1/endpoints --data '{"url":"https://wh.emhk.in/acme/x"}'

It carries your active credentials and base URL, so you never re-plumb auth. Use --json for the unformatted body.

Output & scripting

Add --json to any read command for machine output, then pipe through jq:

bash
emithook events list --status dlq --json | jq -r '.data[].id'

Paginated lists return the envelope { "data": [...], "next_cursor": "…" }; pass next_cursor back via --cursor (with --limit up to 100) to page — see pagination.

Exit codes

CodeMeaning
0Success.
1Runtime / API error (the API's error.message is printed to stderr).
2Usage error (missing/invalid arguments).

The same operations are available through the API and the MCP server, so terminal, code, and AI agents share one model. See the Glossary for the entity names used in flags.

Emithook · a Finnoto product