| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
The official CLI for Resend.
Built for humans, AI agents, and CI/CD pipelines.
██████╗ ███████╗███████╗███████╗███╗ ██╗██████╗ ██╔══██╗██╔════╝██╔════╝██╔════╝████╗ ██║██╔══██╗ ██████╔╝█████╗ ███████╗█████╗ ██╔██╗ ██║██║ ██║ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║╚██╗██║██║ ██║ ██║ ██║███████╗███████║███████╗██║ ╚████║██████╔╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝
curl -fsSL https://resend.com/install.sh | bashnpm install -g resend-clibrew install resend/cli/resendirm https://resend.com/install.ps1 | iexOr 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 doctorThis 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-skillsUse 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-cliInstall dependencies
pnpm installBuild locally
pnpm buildOutput: ./dist/cli.cjs
Use the dev script:
pnpm dev --versionOr run the built JS bundle:
node dist/cli.cjs --versionAfter editing source files, rebuild:
pnpm buildTo build a standalone native binary:
pnpm build:binOutput: ./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.
Authenticate by storing your API key locally. The key is validated against the Resend API before being saved.
resend loginWhen run in a terminal, the command checks for an existing key:
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_xxxxxxxxxxxxxOmitting --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 switchYou can also use the global --profile (or -p) flag on any command to run it with a specific profile.
resend domains list --profile productionSend 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, --subjectA 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.htmlWith 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.comOverriding 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 |
Run 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:
| 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.
Lists 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 --jsonRegisters 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.deliveredFetches 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 --jsonThe signing secret is not returned from get. To rotate secrets, delete the webhook and create a new one.
Updates 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.bouncedDeletes a webhook and stops deliveries.
In non-interactive mode, --yes is required to confirm.
resend webhooks delete wh_abc123 --yesTo pause delivery temporarily, prefer resend webhooks update <id> --status disabled.
Built-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.bouncedThese 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):
{ "error": { "message": "No API key found", "code": "auth_error" } }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:
Prints 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:
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
| Back | FazBrowse Home | New Git URL |