CLI (rly)

rly gives you (or your agent) direct access to email from the terminal: send, receive, search, reply, and manage mailboxes without opening a browser. It ships as a self-contained native binary — no Node toolchain required once installed — and talks only to the ReplyLayer API over HTTPS.

This page covers installation, authentication, a tour of the everyday commands, configuration, and how to verify a signed release. The machine contract — exit codes, --strict semantics, and the --json output shapes an agent branches on — lives on the agent CLI reference. For the exhaustive command-and-flag reference, see the CLI guide in the public rly repository.

Install

npm i -g rly

Requires Node.js 22 or newer to install. The package carries a prebuilt native binary, so rly itself runs without a Node runtime afterward.

PyPI (pipx)

pipx install rly
rly --help

The rly PyPI package installs a bundled native launcher — no Node toolchain needed. Installing it exposes both the rly and replylayer commands (same launcher, two names); examples here use the short form. On Debian/Ubuntu systems that enforce PEP 668, pip install rly may fail with externally-managed-environment — use pipx install rly, or run pip install rly inside a virtual environment.

pnpm

pnpm add -g rly

Supported platforms

The prebuilt native binary targets:

OSArchitecturesMinimum
Linuxx86_64, aarch64glibc 2.28+
macOSarm64, x86_64macOS 14.0+
Windowsx64

The bundled binary is glibc-linked, so Alpine / musl base images are not supported. On a musl host, install via npm on host Node 22+ instead.

From source

The CLI source is public at https://github.com/replylayer/rly. Building from source needs Node.js 22+ on your PATH:

git clone https://github.com/replylayer/rly.git
cd rly
npm install
npm run build
npm link

If you can't run a recent Node toolchain, prefer the bundled binary (npm i -g rly or pipx install rly) — it carries its own runtime.

Authenticate

rly resolves an API key from three sources, highest precedence first:

SourceHowNotes
--api-key <key> flagHighestAvoid in shared shells / CI — the key lands in process arguments and shell history.
REPLYLAYER_API_KEY env varMiddleRecommended for CI/automation. Never persisted to disk.
~/.replylayer/credentials fileLowestWritten by rly auth login; mode 0600, plaintext.
# Interactive — prompts for the key and stores it (mode 0600)
rly auth login

# Environment variable — nothing is written to disk
export REPLYLAYER_API_KEY=rly_live_...

# Confirm which source is in effect (the key value is never printed)
rly auth status
rly config show

Keys are formatted rly_live_<public-id>.<secret> (older rl_live_ keys still work). Admin keys have full account access; agent keys are scoped to specific mailboxes. For the full key model — admin vs. agent scoping, session-vs-Bearer, and the create/verify/revoke rotation pattern — see Authentication.

A brand-new account starts with rly signup --phone +<country-code>... and two one-time checks: email and SMS. Complete them with rly auth verify --code ... and rly auth verify-phone --code .... If the SMS does not arrive, use rly auth resend-phone; before verification, --phone on that command corrects a typo and sends a new challenge. The Quickstart walks the full first-send path.

Command tour

Ten commands cover most day-to-day use. Each accepts --json for machine output, and -h / --help prints the flags for any command.

# 1. Create a mailbox (your sending/receiving identity)
rly mailbox create support-bot

# 2. Mint a scoped agent key bound to that mailbox
rly api-key create --role agent --label support-bot-key --mailbox support-bot

# 3. Add a recipient (sandbox accounts can only send to confirmed recipients —
#    this validates the address, then emails a confirmation link)
rly recipients add [email protected]

# 4. Send an email (synchronous — the response carries the scan verdict)
rly send --from support-bot --to [email protected] \
  --subject "Hello" --body "Your order has shipped."

# 5. List messages in a mailbox
rly inbox list --mailbox support-bot

# 6. Read a message (non-mutating — does NOT mark it read)
rly inbox read <message-id>

# 7. Reply to a message
rly reply <message-id> --body "We're looking into this."

# 8. Wait for the next arrival (long-poll; pass --since to skip the backlog)
rly inbox wait --mailbox support-bot --timeout 60

# 9. Draft, review, then send (the scanner runs at create and again at send)
rly draft create --mailbox support-bot --to [email protected] \
  --subject "Update" --body "Your ticket is resolved."
rly draft send <draft-id>

# 10. Preflight your send budget (works with an agent-scoped key)
rly account quota

Notes on the tour:

  • inbox read never advances read state — an agent inspecting a message won't silently mark it read. Advance read state explicitly with rly inbox mark-read <message-id>.
  • send and reply exit 0 even when the scanner blocks or holds a message — read the JSON status for the outcome, or pass --strict to map a non-delivered outcome to a distinct non-zero exit. The message lifecycle and verdict vocabulary live at Messages; the exit-code and --strict contract lives at the agent CLI reference.
  • account quota reports your effective daily cap, sends remaining, and reset time; the tier and sandbox limits behind those numbers are documented under Limits.

Beyond the tour, the CLI also manages the recipient allowlist and inbound firewall (recipient allowlist, inbound firewall), the do-not-contact list (suppressions), scheduled sends (scheduled send), attachment previews (attachments), custom domains, webhooks, and legal holds. The rly repository guide is the exhaustive reference.

Simulator

Outbound simulation uses the normal send command, so it works in every CLI version. The dedicated inbound command requires an installed build that includes simulator client support; the email simulator guide carries the current availability note and the complete outcome contract.

One Sandbox account can run the four exact outbound scenarios in the same 24-hour period. They bypass only destination-concentration controls and still consume normal daily/cumulative usage allowance.

rly send --from <mailbox> --to [email protected] \
  --subject "simulator check" --body "exercise delivery" --json

rly simulate inbound --mailbox <mailbox> --scenario clean --label ci-run-42 --json
rly simulate inbound --mailbox <mailbox> --scenario prompt_injection_quarantined --json

rly webhook test <webhook-id> --event message.delivered

The inbound command prints available, quarantined, or pending. Branch on that returned status. A pending response means processing continues after the command returns; use the label to identify the eventual message in rly inbox list.

Global options

FlagDescription
-V, --versionPrint the version number.
--api-url <url>API base URL. Defaults to https://api.replylayer.ai (env: REPLYLAYER_API_URL).
--api-key <key>API key, overriding the stored credential (env: REPLYLAYER_API_KEY).
--jsonEmit JSON instead of formatted tables.
-h, --helpShow help for any command.

--json makes every command machine-readable — output goes to stdout, errors are a single JSON object on stderr with a stable code:

rly --json inbox list --mailbox support-bot | jq '.messages[0].subject'

The full --json output shapes, the stable error code field, and the exit-code semantics are the agent CLI contract; the error codes themselves are catalogued at Errors.

Configuration & environment

VariableDescription
REPLYLAYER_API_KEYAPI key (alternative to rly auth login).
REPLYLAYER_API_URLAPI base URL. Override-only — defaults to production, so customers on production set nothing.
REPLYLAYER_MAILBOXDefault mailbox for a fresh send --from / inbox list / inbox wait / draft create. An explicit flag always wins.

REPLYLAYER_API_URL (or --api-url) is only needed to target a non-production API — a staging account, a self-hosted deployment, or a local server. Point it at the host your operator gave you, and prefer a stable hostname. A 401 on the first authenticated call after switching keys usually means the URL is still pointed at the wrong environment.

Two commands inspect the effective setup with no auth and no network call:

rly config show   # resolved API URL, credential source, proxy env, config dir
rly doctor        # runs environment + connectivity checks (rly doctor --offline skips network)

config show reports the credential source only — never the key value. doctor surfaces conditions worth knowing about, such as a loosened-permission credential file or a custom CA in your trust store (below).

Verify a signed release

Every stable release publishes a GPG-signed checksum manifest (SHA256SUMS + SHA256SUMS.sig), per-platform SPDX SBOMs, and SLSA build provenance on the public rly repository Releases page. macOS binaries are additionally Apple-notarized. The bundled native binaries are delivered inside the npm and PyPI packages; the published manifest lists each one by SHA-256 so you can verify what you installed.

The public signing key and the step-by-step "Verifying a release" walkthrough live in that repo — the key is distributed via its KEYS.txt file, not public keyservers (so gpg --recv-keys won't find it):

# Import the signing key from the rly repo (NOT a keyserver)
curl -fsSL https://raw.githubusercontent.com/replylayer/rly/main/KEYS.txt | gpg --import

# Verify the manifest signature
gpg --verify SHA256SUMS.sig SHA256SUMS

# Check your platform's asset against the manifest (Linux; use `shasum -a 256 -c` on macOS)
sha256sum -c <(grep linux-x64 SHA256SUMS)

A Good signature line followed by a "not certified with a trusted signature" warning is expected and benign — the signature verified; you simply haven't marked the key as locally trusted.

The per-platform SBOM is an SPDX-2.3 document describing the final binary by its SHA-256, listing the bundled runtime and dependencies. It is produced from the actual build artifact, not a lockfile scan, and folded into the signed manifest.

Security & network behavior

  • The stored credential is a plaintext file (~/.replylayer/credentials, owner-only 0600). Treat it as a secret on disk; rly doctor warns when the file exists or its permissions have loosened.
  • The key is never printed. config show, doctor, and auth status report only the resolution source. Proxy URLs that embed credentials are redacted in all diagnostic output.
  • No telemetry, no silent auto-update. The CLI makes no outbound connections other than HTTPS to the API base — no analytics, no update checks, no third-party calls. Installed binaries never update themselves; pull a new release explicitly.
  • Custom CA / TLS interception. The CLI honors NODE_EXTRA_CA_CERTS. Adding a CA lets whoever holds the matching key terminate and inspect TLS to the API — legitimate behind a corporate proxy, but a deliberate man-in-the-middle. Point it only at a CA bundle you trust; rly doctor reports when it is set.
  • HTTP(S) proxies. rly doctor detects and reports HTTPS_PROXY / HTTP_PROXY / NO_PROXY (credentials redacted), but the CLI does not currently route requests through them — behind a mandatory proxy, requests go direct and doctor flags this so the cause is visible.

Where to go next