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 --helpThe 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 shRegenerate 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 doctorThe 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> --jsonInline 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 --jsonsamva 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.comSchedule 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.
| Code | Meaning |
|---|---|
0 | Success |
1 | API, operational, or terminal delivery failure |
2 | Command syntax, validation, configuration, or confirmation failure |
4 | Authentication or organization-context failure, including an expired session |
124 | Wait or credential timeout |
130 | Interrupted with SIGINT |
143 | Terminated 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.