resend-cli
v1.4.1
Published
The official CLI for Resend
Readme
Resend CLI
The official CLI for Resend.
Built for humans, AI agents, and CI/CD pipelines.
██████╗ ███████╗███████╗███████╗███╗ ██╗██████╗
██╔══██╗██╔════╝██╔════╝██╔════╝████╗ ██║██╔══██╗
██████╔╝█████╗ ███████╗█████╗ ██╔██╗ ██║██║ ██║
██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║╚██╗██║██║ ██║
██║ ██║███████╗███████║███████╗██║ ╚████║██████╔╝
╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝Install
cURL
curl -fsSL https://resend.com/install.sh | bashNode.js
npm install -g resend-cliHomebrew (macOS / Linux)
brew install resend/cli/resendPowerShell (Windows)
irm https://resend.com/install.ps1 | iexOr download the .exe directly from the GitHub releases page.
Local development
Use this when you want to change the CLI and run your build locally.
Prerequisites
- Node.js 20+
Setup
Clone the repo
git clone https://github.com/resend/resend-cli.git cd resend-cliInstall dependencies
pnpm installBuild locally
pnpm buildOutput:
./dist/cli.cjs
Running the CLI locally
Use the dev script:
pnpm dev -- --versionOr run the built JS bundle:
node dist/cli.cjs --versionMaking changes
After editing source files, rebuild:
pnpm buildBuilding native binaries
To build a standalone native binary:
pnpm build:binOutput: ./dist/resend
Quick start
# Authenticate
resend login
# Send an email
resend emails send \
--from "[email protected]" \
--to [email protected] \
--subject "Hello from Resend CLI" \
--text "Sent from my terminal."
# Check your environment
resend doctorAuthentication
The CLI resolves your API key using the following priority chain:
| Priority | Source | How to set |
|----------|--------|------------|
| 1 (highest) | --api-key flag | resend --api-key re_xxx emails send ... |
| 2 | RESEND_API_KEY env var | export RESEND_API_KEY=re_xxx |
| 3 (lowest) | Config file | resend login |
If no key is found from any source, the CLI errors with code auth_error.
Commands
resend login
Authenticate by storing your API key locally. The key is validated against the Resend API before being saved.
resend loginInteractive mode (default in terminals)
When run in a terminal, the command checks for an existing key:
- No key found — Offers to open the Resend API keys dashboard in your browser so you can create one, then prompts for the key.
- Existing key found — Shows the key source (
env,config) and prompts for a new key to replace it.
The key is entered via a masked password input and must start with re_.
Non-interactive mode (CI, pipes, scripts)
When stdin is not a TTY, the --key flag is required:
resend login --key re_xxxxxxxxxxxxxOmitting --key in non-interactive mode exits with error code missing_key.
Options
| Flag | Description |
|------|-------------|
| --key <key> | API key to store (required in non-interactive mode) |
Output
On success, credentials are saved to ~/.config/resend/credentials.json with 0600 permissions (owner read/write only). The config directory is created with 0700 permissions.
# JSON output
resend login --key re_xxx --json
# => {"success":true,"config_path":"/Users/you/.config/resend/credentials.json"}Error codes
| Code | Cause |
|------|-------|
| missing_key | No --key provided in non-interactive mode |
| invalid_key_format | Key does not start with re_ |
| validation_failed | Resend API rejected the key |
Switch between teams and accounts
If you work across multiple Resend teams or accounts, the CLI handles that too.
Switch between profiles without logging in and out:
resend auth switchYou can also use the global --profile (or -p) flag on any command to run it with a specific profile.
resend domains list --profile productionresend emails send
Send an email via the Resend API. Provide all options via flags for scripting, or let the CLI prompt interactively for missing fields.
resend emails send \
--from "Name <[email protected]>" \
--to [email protected] \
--subject "Subject line" \
--text "Plain text body"Options
| Flag | Required | Description |
|------|----------|-------------|
| --from <address> | Yes | Sender email address (must be from a verified domain) |
| --to <addresses...> | Yes | One or more recipient email addresses (space-separated) |
| --subject <subject> | Yes | Email subject line |
| --text <text> | One of text/html/html-file | Plain text body |
| --html <html> | One of text/html/html-file | HTML body as a string |
| --html-file <path> | One of text/html/html-file | Path to an HTML file to use as body |
| --cc <addresses...> | No | CC recipients (space-separated) |
| --bcc <addresses...> | No | BCC recipients (space-separated) |
| --reply-to <address> | No | Reply-to email address |
Interactive mode
When run in a terminal without all required flags, the CLI prompts for missing fields:
# prompts for from, to, subject, and body
resend emails send
# prompts only for missing fields
resend emails send --from "[email protected]"Non-interactive mode
When piped or run in CI, all required flags must be provided. Missing flags cause an error listing what's needed:
echo "" | resend emails send --from "[email protected]"
# Error: Missing required flags: --to, --subjectA body (--text, --html, or --html-file) is also required — omitting all three exits with code missing_body.
Examples
Multiple recipients:
resend emails send \
--from "[email protected]" \
--to [email protected] [email protected] \
--subject "Team update" \
--text "Hello everyone"HTML from a file:
resend emails send \
--from "[email protected]" \
--to [email protected] \
--subject "Newsletter" \
--html-file ./newsletter.htmlWith CC, BCC, and reply-to:
resend emails send \
--from "[email protected]" \
--to [email protected] \
--subject "Meeting notes" \
--text "See attached." \
--cc [email protected] \
--bcc [email protected] \
--reply-to [email protected]Overriding the API key for one send:
resend --api-key re_other_key emails send \
--from "[email protected]" \
--to [email protected] \
--subject "Test" \
--text "Using a different key"Output
Returns the email ID on success:
{ "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" }Error codes
| Code | Cause |
|------|-------|
| auth_error | No API key found or client creation failed |
| missing_body | No --text, --html, or --html-file provided |
| file_read_error | Could not read the file passed to --html-file |
| send_error | Resend API returned an error |
resend doctor
Run environment diagnostics. Verifies your CLI version, API key, domains, and detects AI agent integrations.
resend doctorChecks performed
| Check | Pass | Warn | Fail | |-------|------|------|------| | CLI Version | Running latest | Update available or registry unreachable | — | | API Key | Key found (shows masked key + source) | — | No key found | | Domains | Verified domains exist | No domains or all pending verification | API key invalid | | AI Agents | Lists detected agents (or none) | — | — |
The API key is always masked in output (e.g. re_...xxxx).
Interactive mode
In a terminal, shows animated spinners for each check with colored status icons:
Resend Doctor
✔ CLI Version: v0.1.0 (latest)
✔ API Key: re_...xxxx (source: env)
✔ Domains: 2 verified, 0 pending
✔ AI Agents: Detected: Cursor, Claude DesktopJSON mode
resend doctor --json{
"ok": true,
"checks": [
{ "name": "CLI Version", "status": "pass", "message": "v0.1.0 (latest)" },
{ "name": "API Key", "status": "pass", "message": "re_...xxxx (source: env)" },
{ "name": "Domains", "status": "pass", "message": "2 verified, 0 pending" },
{ "name": "AI Agents", "status": "pass", "message": "Detected: Cursor" }
]
}Each check has a status of pass, warn, or fail. The top-level ok is false if any check is fail.
Detected AI agents
| Agent | Detection method |
|-------|-----------------|
| OpenClaw | ~/clawd/skills directory exists |
| Cursor | ~/.cursor directory exists |
| Claude Desktop | Platform-specific config file exists |
| VS Code | .vscode/mcp.json in current directory |
Exit code
Exits 0 when all checks pass or warn. Exits 1 if any check fails.
Global options
These flags work on every command and are passed before the subcommand:
resend [global options] <command> [command options]| Flag | Description |
|------|-------------|
| --api-key <key> | Override API key for this invocation (takes highest priority) |
| -p, --profile <name> | Profile to use (overrides RESEND_PROFILE env var) |
| --json | Force JSON output even in interactive terminals |
| -q, --quiet | Suppress spinners and status output (implies --json) |
| --version | Print version and exit |
| --help | Show help text |
Output behavior
The CLI has two output modes:
| Mode | When | Stdout | Stderr |
|------|------|--------|--------|
| Interactive | Terminal (TTY) | Formatted text | Spinners, prompts |
| Machine | Piped, CI, or --json | JSON | Nothing |
Switching is automatic — pipe to another command and JSON output activates:
resend doctor | jq '.checks[].name'
resend emails send --from ... --to ... --subject ... --text ... | jq '.id'Error output
Errors always exit with code 1 and output structured JSON to stdout:
{ "error": { "message": "No API key found", "code": "auth_error" } }Agent & CI/CD usage
CI/CD
Set RESEND_API_KEY as an environment variable — no resend login needed:
# GitHub Actions
env:
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
steps:
- run: |
resend emails send \
--from "[email protected]" \
--to "[email protected]" \
--subject "Deploy complete" \
--text "Version ${{ github.sha }} deployed."AI agents
Agents calling the CLI as a subprocess automatically get JSON output (non-TTY detection). The contract:
- Input: All required flags must be provided (no interactive prompts)
- Output: JSON to stdout, nothing to stderr
- Exit code:
0success,1error - Errors: Always include
messageandcodefields
Configuration
| Item | Path | Notes |
|------|------|-------|
| Config directory | ~/.config/resend/ | Respects $XDG_CONFIG_HOME on Linux, %APPDATA% on Windows |
| Credentials | ~/.config/resend/credentials.json | 0600 permissions (owner read/write) |
| Install directory | ~/.resend/bin/ | Respects $RESEND_INSTALL |
License
MIT
