Use the CLI

Install the Samva CLI, authenticate, select an execution context, and operate email from your terminal.

The samva CLI operates the email API from a terminal. Use it for direct work, scripts, and agent automation. Run samva <command> --help for command-specific flags and examples.

Install and complete commands

npm install -g @samva/cli
samva --help

The package prefers its platform-native executable and falls back to the packaged JavaScript CLI through Bun when npm omits optional dependencies.

The installed command includes shell completion generation:

# Bash
samva --completions bash >> ~/.bashrc

# Zsh
mkdir -p ~/.zsh/completions
samva --completions zsh > ~/.zsh/completions/_samva
# Add ~/.zsh/completions to fpath, then run `autoload -Uz compinit && compinit` in .zshrc.

# Fish
mkdir -p ~/.config/fish/completions
samva --completions fish > ~/.config/fish/completions/samva.fish

# sh emits the Bash-compatible completion script
samva --completions sh

Regenerate the script after upgrading the CLI.

Authenticate and choose a context

Use an API key for non-interactive automation. It is scoped to one organization.

export SAMVA_API_KEY="samva_sk_live_your_api_key"

For interactive OAuth, sign in and choose an organization:

samva login
samva org list
samva org use <slug>

SAMVA_API_KEY takes precedence over stored OAuth credentials. An API key already identifies its organization, so the CLI rejects --org and SAMVA_ORG while that key is set. With OAuth, the organization resolves as --org, then SAMVA_ORG, then the selected profile's organization.

samva login is interactive. In a non-TTY, use SAMVA_API_KEY for automation or explicitly choose the device flow with samva login --no-browser; --no-input, --json, and --jsonl reject login. If a stored OAuth record is malformed, run samva logout and then samva login; API-key calls do not read or decode stored OAuth credentials.

Profiles keep non-secret execution settings. OAuth credentials remain in the OS keyring.

samva profile create local --target local
samva profile use local
samva --profile local email doctor

The profile resolves as --profile, then SAMVA_PROFILE, then the active profile, then default. The API URL resolves as --api-url, then SAMVA_API_URL, then the profile target. The local target uses http://127.0.0.1:8787; production uses https://api.samva.dev.

Send, inspect, and wait

samva email send \
  --to ada@example.com \
  --subject "Welcome to Samva" \
  --text "Welcome"

printf 'Welcome' | samva email send \
  --to ada@example.com \
  --subject "Welcome to Samva" \
  --text -

render-email | samva email send \
  --to ada@example.com \
  --subject "Welcome to Samva" \
  --html -

samva email send \
  --to ada@example.com \
  --template-slug welcome-email \
  --template-data '{"name":"Ada"}' \
  --wait --timeout 2m

samva messages get <message-id>
samva messages events <message-id> --json

Inline email requires --subject and exactly one body source: --html or --text. Pass - as the value to read that body from stdin: --text - reads a plain-text body and --html - reads an HTML body. Only one body may come from stdin, so passing both --text - and --html - is rejected. Template sends use one of --template-id or --template-slug, with optional --template-data, and omit --subject.

--wait polls for delivery or read status, or a terminal failure. It defaults to two minutes. Use --timeout only with --wait. --wait cannot combine with --dry-run, since a dry run makes no API call and has nothing to wait on. A timeout or terminal delivery failure prints the last message state and exits non-zero.

Configure email and check readiness

samva email domains list
samva email domains add --domain example.com --dry-run
samva email senders list
samva email senders add --email hello@example.com --dry-run
samva email doctor --json

samva email doctor is read-only. It reports the resolved profile, target, API URL, auth source, organization access, sending domains, senders, and domain-verification readback. It does not inspect receiving configuration, send email, verify DNS, replay fixtures, or check provider readiness. See Verify your domain for DNS setup.

Each check is passed, failed, or unavailable. A failed check carries the underlying error detail; unavailable, such as no sending domain configured yet, is informational. The command always prints the full report, then exits 1 if any check failed and 0 otherwise. The --json result's data adds an aggregate ok boolean. Human output marks a passed check with , an unavailable one with !, and a failed one with .

Use the receiving resource commands when configuring inbound mail:

# Read the domain verification prerequisite for receiving.
samva email receiving status <domain-id>

# Preview without credentials or an API call.
samva email receiving enable <domain-id> \
  --domain example.com --address replies@example.com --dry-run

# Configure receiving. This ordinary mutation acts immediately and does not use --yes.
samva email receiving enable <domain-id> \
  --domain example.com --address replies@example.com

Schedule email and run campaigns

samva scheduled-messages create --to ada@example.com --template-slug welcome-email \
  --template-data '{"name":"Ada"}' --at 2026-08-01T09:00:00Z \
  --timezone America/New_York --idempotency-key welcome-ada
samva scheduled-messages get <scheduled-message-id>
samva scheduled-messages cancel <scheduled-message-id> --yes

samva campaigns create --name "August launch" --template-slug august-launch \
  --include-tags newsletter
samva campaigns runs create <campaign-id> --at 2026-08-01T09:00:00Z \
  --idempotency-key august-launch
samva campaigns runs recipients <campaign-id> <run-id> --status failed

--at is an absolute ISO 8601 instant. --timezone is display and audit metadata for scheduled messages. Campaign definitions freeze after their first run. Use --yes to cancel non-interactively.

For resource-specific behavior, see Schedule an email and Send an email campaign.

Test a webhook endpoint

# Validate and preview without credentials, an API call, or an endpoint delivery.
samva webhooks test <webhook-id> --data @payload.json --dry-run

# Send a signed request to the webhook's configured endpoint.
samva webhooks test <webhook-id> --data '{"event":"webhook.test"}' --json

--data accepts an inline JSON object or @file. Omit it to send Samva's default test event. The command acts by default because testing an endpoint sends a request. A non-2xx response, timeout, or transport failure exits 1. In machine modes, stdout still contains the complete test result (including statusCode only when an HTTP response exists), while stderr contains the typed webhook_test_failed diagnostic. The result is complete, so it does not use partial: true.

Automate safely

# Validate and print a request without an API call.
samva email send --to ada@example.com --subject "Hello" --text "Hi" --dry-run --json

# Stream every page as JSON Lines.
samva messages list --all --jsonl

# Suppress successful human output.
samva --quiet profile use local

--dry-run validates supported mutations and previews their intended request. email send --dry-run does not use credentials or call the API. Another preview can read state when needed to show the resulting change. Destructive operations prompt in a TTY. In automation, use --yes to confirm or --no-input to disallow prompts and browser launches. Without confirmation, the command fails without a state change.

--json writes one versioned result envelope to stdout. --jsonl writes one versioned result envelope per line and is required for --all pagination. --quiet suppresses successful output. The three modes are mutually exclusive. Machine output keeps stdout to result data only; warnings and failures are written to stderr. Assignment spellings such as --json=true are usage errors.

{
  "schemaVersion": 1,
  "type": "result",
  "command": "email.send",
  "data": { "id": "msg_abc123", "status": "sent" }
}

For resource lists, --jsonl preserves that outer envelope on every line and places the resource itself in data. --all changes traversal only, not the record schema. Empty lists emit no lines; otherwise parse one line at a time and do not expect an array.

Use --page and --limit for a bounded page. Lists are paginated by default.

CodeMeaning
0Success
1API, operational, or terminal delivery failure
2Command syntax, validation, configuration, or confirmation failure
4Authentication or organization-context failure, including an expired session
124Wait or credential timeout
130Interrupted with SIGINT
143Terminated with SIGTERM

In machine modes, a command failure emits a JSON diagnostic on stderr and does not mix it with stdout data:

{
  "schemaVersion": 1,
  "type": "error",
  "command": "messages.wait",
  "reason": "message_wait_timeout",
  "message": "Timed out waiting for message msg_abc123; it remains sent.",
  "next": "samva messages wait msg_abc123"
}

Lifecycle timeout, terminal delivery failure, and a later observation failure first preserve the last authoritative Message in a result envelope with partial: true on stdout, then emit the error envelope on stderr. Save data.id and resume with samva messages wait <message-id>. email doctor is a report rather than an execution failure: it writes its full result envelope with data.ok: false and exits 1 when a check fails.

A command that needs a subcommand, such as a bare samva or a group like samva email, prints its help to stdout and exits 2, so a script never mistakes a missing subcommand for success.

Next steps

On this page