The official CLI for Resend. Built for humans, AI agents, and CI/CD pipelines.
$ npx -y skills add resend/resend-cli --agent claude-code
Repo: resend/resend-cli
What's inside
The official CLI for Resend.
Built for humans, AI agents, and CI/CD pipelines.
โโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโ
โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโ โโโโโโ โโโ
โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโ
โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโ
โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโ
curl -fsSL https://resend.com/install.sh | bash
npm install -g resend-cli
brew install resend/cli/resend
irm https://resend.com/install.ps1 | iex
Or download the .exe directly from the GitHub releases page.
# Authenticate
resend login
# Send an email
resend emails send \
--from "you@example.com" \
--to delivered@resend.dev \
--subject "Hello from Resend CLI" \
--text "Sent from my terminal."
# Check your environment
resend doctor
This CLI ships with an agent skill that teaches AI coding agents (Cursor, Claude Code, Windsurf, etc.) how to use the Resend CLI effectively, including non-interactive flags, output formats, and common pitfalls.
To install skills for Resend's full platform (API, CLI, React Email, email best practices) from the central skills repository:
npx skills add resend/resend-skills
Use this when you want to change the CLI and run your build locally.
Clone the repo
git clone https://github.com/resend/resend-cli.git
cd resend-cli
Install dependencies
pnpm install
Build locally
pnpm build
Output: ./dist/cli.cjs
Use the dev script:
pnpm dev --version
Or run the built JS bundle:
node dist/cli.cjs --version
After editing source files, rebuild:
pnpm build
To build a standalone native binary:
pnpm build:bin
Output: ./dist/resend
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.
resend loginAuthenticate by storing your API key locally. The key is validated against the Resend API before being saved.
resend login
When run in a terminal, the command checks for an existing key:
env, config) and prompts for a new key to replace it.Enter the key via a masked password input. Your key must start with re_.
When stdin is not a TTY, the --key flag is required:
resend login --key re_xxxxxxxxxxxxx
Omitting --key in non-interactive mode exits with error code missing_key.
| Flag | Description |
|---|---|
--key <key> | API key to store (required in non-interactive mode) |
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"}
| 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 |
If you work across multiple Resend teams or accounts, the CLI handles that, too.
Switch between profiles without logging in and out:
resend auth switch
You can also use the global --profile (or -p) flag on any command to run it with a specific profile.
resend domains list --profile production
resend emails sendSend 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 <sender@example.com>" \
--to delivered@resend.dev \
--subject "Subject line" \
--text "Plain text body"
| 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 |
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 "you@example.com"
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 "you@example.com"
# Error: Missing required flags: --to, --subject
A body (--text, --html, or --html-file) is also required โ omitting all three exits with code missing_body.
Multiple recipients:
resend emails send \
--from "you@example.com" \
--to delivered@resend.dev bounced@resend.dev \
--subject "Team update" \
--text "Hello everyone"
HTML from a file:
resend emails send \
--from "you@example.com" \
--to delivered@resend.dev \
--subject "Newsletter" \
--html-file ./newsletter.html
With CC, BCC, and reply-to:
resend emails send \
--from "you@example.com" \
--to delivered@resend.dev \
--subject "Meeting notes" \
--text "See attached." \
--cc manager@example.com \
--bcc delivered+1@resend.dev \
--reply-to noreply@example.com
Overriding the API key for one send:
resend --api-key re_other_key emails send \
--from "you@example.com" \
--to delivered@resend.dev \
--subject "Test" \
--text "Using a different key"
Returns the email ID on success:
{ "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" }
| 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 doctorRun environment diagnostics. Verifies your CLI version, API key, domains, and detects AI agent integrations.
resend doctor
| Check | Pass | Warn | Fail |
|---|---|---|---|
| CLI Version | Running latest | Update available or registry unreachable | โ |
| API Key | Key found (shows masked key and 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).
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 Desktop
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.
| 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 |
Exits 0 when all checks pass or warn. Exits 1 if any check fails.
With the Resend CLI, you can manage webhook endpoints so your app receives real-time event notifications.
Payloads are signed with Svix headers (svix-id, svix-timestamp, svix-signature). Verify them in your app with the Resend SDK.
For example: resend.webhooks.verify({ payload, headers, webhookSecret })
There are many events that you can listen for in your application.
For example, you can:
contact.created event.email.received webhook to set up an inbox for your agent and notify it when a new email is received.| Category | Events |
|---|---|
email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clicked, email.failed, email.scheduled, email.suppressed, email.received | |
| Contact | contact.created, contact.updated, contact.deleted |
| Domain | domain.created, domain.updated, domain.deleted |
Use all with --events to subscribe to every event.
listcreategetupdatedeletelistenwebhooks ls โ listwebhooks rm โ deleteresend webhooks listLists existing webhooks.
Running resend webhooks with no subcommand runs list.
| Flag | Description |
|---|---|
--limit <n> | Max webhooks to return (1โ100, default 10) |
--after <cursor> | Return webhooks after this cursor (webhook ID; next page) |
--before <cursor> | Return webhooks before this cursor (previous page) |
Only one of --after or --before may be used. The API response includes has_more when more pages exist.
resend webhooks list
resend webhooks list --limit 25
resend webhooks list --after wh_abc123 --json
resend webhooks createRegisters a new endpoint.
The endpoint must use HTTPS. The signing_secret in the response is shown once. Store it immediately to verify incoming payloads.
In interactive mode, the CLI can prompt for endpoint and events. In non-interactive mode (pipes, CI, --json), --endpoint and --events are required.
| Flag | Description |
|---|---|
--endpoint <url> | HTTPS URL that receives webhook POSTs |
--events <events...> | Event names (comma- or space-separated), or all |
resend webhooks create --endpoint https://app.example.com/hooks/resend --events all
resend webhooks create --endpoint https://app.example.com/hooks/resend --events email.sent email.bounced
resend webhooks create --endpoint https://app.example.com/hooks/resend --events email.sent,email.delivered
resend webhooks getFetches one webhook by ID.
Omit the ID in a terminal to pick from a list.
resend webhooks get wh_abc123
resend webhooks get wh_abc123 --json
The signing secret is not returned from get. To rotate secrets, delete the webhook and create a new one.
resend webhooks updateUpdates the webhook:
At least one of --endpoint, --events, or --status is required.
| Flag | Description |
|---|---|
--endpoint <url> | New HTTPS URL |
--events <events...> | New event list, or all |
--status <status> | enabled or disabled |
Disabled status pauses delivery without deleting the webhook.
resend webhooks update wh_abc123 --status disabled
resend webhooks update wh_abc123 --endpoint https://new-app.example.com/hooks/resend
resend webhooks update wh_abc123 --events email.sent email.bounced
resend webhooks deleteDeletes a webhook and stops deliveries.
In non-interactive mode, --yes is required to confirm.
resend webhooks delete wh_abc123 --yes
To pause delivery temporarily, prefer resend webhooks update <id> --status disabled.
resend webhooks listenBuilt-in local development helper. It:
Your tunnel must forward to the same port as --port, e.g. ngrok http 4318.
| Flag | Description |
|---|---|
--url <url> | Public URL (tunnel) that reaches this machine โ required |
--port <port> | Local server port (default 4318) |
--events <events...> | Events to subscribe to (default: all) |
--forward-to <url> | Also POST each payload to this URL (Svix headers preserved) |
# Terminal 1: tunnel to the listen port
ngrok http 4318
# Terminal 2: use the HTTPS URL ngrok gives you
resend webhooks listen --url https://xxxx.ngrok-free.app
resend webhooks listen --url https://xxxx.ngrok-free.app --forward-to localhost:3000/api/webhooks/resend
resend webhooks listen --url https://xxxx.ngrok-free.app --port 8080 --events email.sent email.bounced
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 |
The CLI has two output modes:
| Mode | When | Stdout | Stderr |
|---|---|---|---|
| Interactive | Terminal (TTY) | Formatted text | Spinners, prompts, human-readable errors |
| Machine | Piped, CI, or --json | Success JSON only | JSON errors; optional warnings (e.g. flags) |
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'
Errors always exit with code 1. The format on stderr depends on output mode (same rules as the table above):
--json, or -q): structured JSON so stdout stays success-only for scripting (jq, etc.):{ "error": { "message": "No API key found", "code": "auth_error" } }
--json / -q): a human-readable line such as Error: No API key found (still on stderr).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 "deploy@example.com" \
--to "delivered@resend.dev" \
--subject "Deploy complete" \
--text "Version ${{ github.sha }} deployed."
Agents calling the CLI as a subprocess automatically get JSON output (non-TTY detection). The contract:
2> or combined capture if you need both)0 success, 1 errormessage and code fieldsresend commands prints the full command tree as JSON (subcommands, options, descriptions).resend commandsPrints the CLI command tree as JSON for scripting and AI agents. In an interactive terminal, pass global --json if you need machine output; when stdout is piped, JSON is used automatically.
--dry-run is only implemented where agents most often need to validate a complex payload before a high-impact send:
resend emails send ... --dry-run โ validates inputs and prints { "dryRun": true, "request": { ... } } without sending. Attachments appear as filename and byteLength only.resend broadcasts create ... --dry-run โ same for the broadcast create payload.Other write commands (batch, broadcasts send, webhooks, contacts, etc.) do not support --dry-run yet. If that would help your workflow, open an issue โ likely next candidates are emails batch (large JSON files) and broadcasts send (confirm id + schedule before delivery).
| 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 |
MIT
.github/
DISCUSSION_TEMPLATE/
help.yml
ideas.yml
ISSUE_TEMPLATE/
1.bug_report.yml
config.yml
pull_request_template.md
scripts/
pr-title-check.js
workflows/
lint.yml
pin-dependencies-check.yml
post-release.yml
pr-title-check.yml
release.yml
smoke.yml
sync-prs-to-linear.yml
sync-skills.yml
test-credential-backends.yml
test-install-unix.yml
test-install-windows.yml
test-react-email-binary.yml
test-telemetry-windows.yml
test.yml
typecheck.yml
.gitignore
biome.json
install.ps1
install.sh
LICENSE
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
README.md
renovate.json
scripts/
build.mjs
verify-bundle.mjs
skill-evals/
resend-cli/
evals.json
skills/
resend-cli/
references/
api-keys.md
auth.md
automations.md
broadcasts.md
contact-properties.md
contacts.md
domains.md
emails.md
error-codes.md
logs.md
segments.md
suppressions.md
templates.md
topics.md
webhooks.md
workflows.md
SKILL.md
src/
cli.ts
commands/
api-keys/
create.ts
delete.ts
index.ts
list.ts
utils.ts
auth/
index.ts
list.ts
login.ts
logout.ts
remove.ts
rename.ts
switch.ts
automations/
create.ts
delete.ts
get.ts
index.ts
list.ts
open.ts
runs/
get.ts
index.ts
list.ts
stop.ts
update.ts
utils.ts
broadcasts/
create.ts
delete.ts
get.ts
index.ts
list.ts
open.ts
send.ts
update.ts
utils.ts
commands.ts
completion.ts
contact-properties/
create.ts
delete.ts
get.ts
index.ts
list.ts
update.ts
utils.ts
contacts/
add-segment.ts
create.ts
delete.ts
get.ts
imports/
create.ts
get.ts
index.ts
list.ts
utils.ts
index.ts
list.ts
remove-segment.ts
segments.ts
topics.ts
update-topics.ts
update.ts
utils.ts
docs.ts
doctor.ts
domains/
claim/
create.ts
get.ts
index.ts
verify.ts
create.ts
delete.ts
get.ts
index.ts
list.ts
update.ts
utils.ts
verify.ts
emails/
attachment.ts
attachments.ts
batch.ts
cancel.ts
get.ts
index.ts
list.ts
receiving/
attachment.ts
attachments.ts
forward.ts
get.ts
index.ts
list.ts
listen.ts
utils.ts
send.ts
update.ts
utils.ts
events/
create.ts
delete.ts
get.ts
index.ts
list.ts
open.ts
send.ts
update.ts
utils.ts
logs/
get.ts
index.ts
list.ts
open.ts
utils.ts
oauth-grants/
index.ts
list.ts
revoke.ts
utils.ts
open.ts
segments/
contacts.ts
create.ts
delete.ts
get.ts
index.ts
list.ts
utils.ts
suppressions/
add.ts
batch/
add.ts
index.ts
remove.ts
utils.ts
delete.ts
get.ts
index.ts
list.ts
utils.ts
templates/
create.ts
delete.ts
duplicate.ts
get.ts
index.ts
list.ts
open.ts
publish.ts
update.ts
utils.ts
topics/
create.ts
delete.ts
get.ts
index.ts
list.ts
update.ts
utils.ts
update.ts
webhooks/
create.ts
delete.ts
get.ts
index.ts
list.ts
listen.ts
resolve-upstream.ts
update.ts
utils.ts
whoami.ts
lib/
actions.ts
attachments.ts
browser.ts
cli-exit.ts
client.ts
command-suggestions.ts
completion.ts
config.ts
corrupted-credentials-error.ts
credential-backends/
file.ts
linux.ts
macos.ts
windows.ts
credential-store.ts
domains.ts
esbuild/
escape-string-for-regex.ts
load-esbuild.ts
rendering-utilities-exporter.ts
file-lock.ts
files.ts
help-text.ts
json.ts
logo.ts
oauth-background.ts
oauth.ts
output.ts
pagination.ts
prompts.ts
react-email-bundler.ts
react-email-renderer.ts
react-email.ts
safe-terminal-text.ts
spinner.ts
table.ts
telemetry.ts
tty.ts
update-check.ts
user-agent.ts
version.ts
with-retry.ts
with-timeout.ts
write-file-atomic.ts
utils/
bounded-set.ts
with-timeout.ts
tests/
commands/
api-keys/
create.test.ts
delete.test.ts
list.test.ts
utils.test.ts
auth/
list.test.ts
login.test.ts
logout.test.ts
remove.test.ts
switch.test.ts
broadcasts/
create.test.ts
delete.test.ts
get.test.ts
list.test.ts
open.test.ts
send.test.ts
update.test.ts
commands.test.ts
completion.test.ts
contact-properties/
create.test.ts
delete.test.ts
get.test.ts
list.test.ts
update.test.ts
contacts/
add-segment.test.ts
create.test.ts
delete.test.ts
get.test.ts
imports/
create.test.ts
get.test.ts
list.test.ts
list.test.ts
remove-segment.test.ts
segments.test.ts
topics.test.ts
update-topics.test.ts
update.test.ts
docs.test.ts
doctor.test.ts
domains/
claim/
create.test.ts
get.test.ts
verify.test.ts
create.test.ts
delete.test.ts
get.test.ts
list.test.ts
update.test.ts
utils.test.ts
verify.test.ts
emails/
attachment.test.ts
attachments.test.ts
batch.test.ts
cancel.test.ts
get.test.ts
receiving/
attachment.test.ts
attachments.test.ts
forward.test.ts
get.test.ts
list.test.ts
listen.test.ts
send.test.ts
update.test.ts
logs/
get.test.ts
list.test.ts
open.test.ts
oauth-grants/
list.test.ts
revoke.test.ts
utils.test.ts
open.test.ts
segments/
create.test.ts
delete.test.ts
get.test.ts
list.test.ts
suppressions/
add.test.ts
batch.test.ts
delete.test.ts
get.test.ts
list.test.ts
utils.test.ts
templates/
create.test.ts
open.test.ts
update.test.ts
topics/
create.test.ts
delete.test.ts
get.test.ts
list.test.ts
update.test.ts
webhooks/
create.test.ts
delete.test.ts
get.test.ts
list.test.ts
listen-forward.test.ts
resolve-upstream.test.ts
... 41 moreA collection of skills for AI coding agents following the Agent Skills format. Available as a plugin for Claude Code, Cursor, and OpenAI Codex. Includes an MCP server for tool access.
FAQ
resend-cli is a Claude Code plugin with 1 hand-picked skill for cli tools work, indexed on Flowy. Install it with the command on its page. It includes resend-cli. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.