Resend CLI

Send emails, manage domains, contacts, broadcasts, templates, webhooks, and API keys from the terminal.

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 | bash

Node.js

npm install -g resend-cli

Homebrew (macOS / Linux)

brew install resend/cli/resend

PowerShell (Windows)

irm https://resend.com/install.ps1 | iex

Or download the .exe directly from the GitHub releases page.

Quickstart

# 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

Agent skills

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

Local development

Use this when you want to change the CLI and run your build locally.

Prerequisites

Setup

  1. Clone the repo

    git clone https://github.com/resend/resend-cli.git
    cd resend-cli
    
  2. Install dependencies

    pnpm install
    
  3. Build locally

    pnpm build
    

    Output: ./dist/cli.cjs

Running the CLI locally

Use the dev script:

pnpm dev --version

Or run the built JS bundle:

node dist/cli.cjs --version

Making changes

After editing source files, rebuild:

pnpm build

Building native binaries

To build a standalone native binary:

pnpm build:bin

Output: ./dist/resend


Authentication

The CLI resolves your API key using the following priority chain:

PrioritySourceHow to set
1 (highest)--api-key flagresend --api-key re_xxx emails send ...
2RESEND_API_KEY env varexport RESEND_API_KEY=re_xxx
3 (lowest)Config fileresend 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 login

Interactive 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.

Enter the key via a masked password input. Your key 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_xxxxxxxxxxxxx

Omitting --key in non-interactive mode exits with error code missing_key.

Options

FlagDescription
--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

CodeCause
missing_keyNo --key provided in non-interactive mode
invalid_key_formatKey does not start with re_
validation_failedResend 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 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 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 <sender@example.com>" \
  --to delivered@resend.dev \
  --subject "Subject line" \
  --text "Plain text body"

Options

FlagRequiredDescription
--from <address>YesSender email address (must be from a verified domain)
--to <addresses...>YesOne or more recipient email addresses (space-separated)
--subject <subject>YesEmail subject line
--text <text>One of text/html/html-filePlain text body
--html <html>One of text/html/html-fileHTML body as a string
--html-file <path>One of text/html/html-filePath to an HTML file to use as body
--cc <addresses...>NoCC recipients (space-separated)
--bcc <addresses...>NoBCC recipients (space-separated)
--reply-to <address>NoReply-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 "you@example.com"

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 "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.

Examples

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"

Output

Returns the email ID on success:

{ "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" }

Error codes

CodeCause
auth_errorNo API key found or client creation failed
missing_bodyNo --text, --html, or --html-file provided
file_read_errorCould not read the file passed to --html-file
send_errorResend API returned an error

resend doctor

Run environment diagnostics. Verifies your CLI version, API key, domains, and detects AI agent integrations.

resend doctor

Checks performed

CheckPassWarnFail
CLI VersionRunning latestUpdate available or registry unreachable
API KeyKey found (shows masked key and source)No key found
DomainsVerified domains existNo domains or all pending verificationAPI key invalid
AI AgentsLists 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 Desktop

JSON 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

AgentDetection method
OpenClaw~/clawd/skills directory exists
Cursor~/.cursor directory exists
Claude DesktopPlatform-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.


Webhooks

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:

  • Set up a POST endpoint to unsubscribe users when an email bounces or they mark your email as spam.
  • Notify yourself when you get a new subscriber using the contact.created event.
  • Use an email.received webhook to set up an inbox for your agent and notify it when a new email is received.

Event types

CategoryEvents
Emailemail.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clicked, email.failed, email.scheduled, email.suppressed, email.received
Contactcontact.created, contact.updated, contact.deleted
Domaindomain.created, domain.updated, domain.deleted

Use all with --events to subscribe to every event.

Subcommands

  • list
  • create
  • get
  • update
  • delete
  • listen

Aliases

  • webhooks lslist
  • webhooks rmdelete

resend webhooks list

Lists existing webhooks.

Running resend webhooks with no subcommand runs list.

FlagDescription
--limit <n>Max webhooks to return (1100, 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 create

Registers 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.

FlagDescription
--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 get

Fetches 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.

Commands
resend emails send --from you@domain.com --to user@example.com --subject 'Hello' --text 'Body'
resend doctor --json
resend domains create --name example.com --region us-east-1
resend broadcasts create --from news@domain.com --subject 'Update' --segment-id <id> --html '<h1>Hi</h1>' --send
resend emails list --limit 5 --after <cursor-id> --json