Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
"cli",
"resend",
"email",
"api"
"api",
"tanstack-intent"
],
"engines": {
"node": ">=20"
},
"packageManager": "pnpm@10.32.1",
"files": [
"dist/cli.cjs"
"dist/cli.cjs",
"skills/"
],
"type": "module",
"bin": {
Expand All @@ -35,7 +37,8 @@
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:e2e": "vitest run --config vitest.config.e2e.ts",
"prepack": "pnpm build"
"sync-skill-version": "node scripts/sync-skill-version.mjs",
"prepack": "pnpm run sync-skill-version && pnpm build"
},
"dependencies": {
"@clack/prompts": "1.1.0",
Expand Down
14 changes: 14 additions & 0 deletions scripts/sync-skill-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { readFileSync, writeFileSync } from 'node:fs';

const version = JSON.parse(readFileSync('package.json', 'utf8')).version;
const path = 'skills/resend-cli/SKILL.md';
const content = readFileSync(path, 'utf8');
const pattern = /version: ".*?"/;

if (!pattern.test(content)) {
console.error(`Error: could not find version pattern in ${path}`);
process.exit(1);
}

const updated = content.replace(pattern, `version: "${version}"`);
writeFileSync(path, updated);
141 changes: 141 additions & 0 deletions skills/resend-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
name: resend-cli
description: >
Operate the Resend platform from the terminal — send emails, manage domains,
contacts, broadcasts, templates, webhooks, and API keys via the `resend` CLI.
Use when the user wants to run Resend commands in the shell, scripts, or CI/CD
pipelines. Always load this skill before running `resend` commands — it contains
the non-interactive flag contract and gotchas that prevent silent failures.
license: MIT
metadata:
author: resend
version: "1.4.1"
homepage: https://resend.com
source: https://github.com/resend/resend-cli
inputs:
- name: RESEND_API_KEY
description: Resend API key for authenticating CLI commands. Get yours at https://resend.com/api-keys
required: true
references:
- references/emails.md
- references/domains.md
- references/api-keys.md
- references/broadcasts.md
- references/contacts.md
- references/contact-properties.md
- references/segments.md
- references/templates.md
- references/topics.md
- references/webhooks.md
- references/auth.md
- references/workflows.md
- references/error-codes.md
---

# Resend CLI

## Agent Protocol

The CLI auto-detects non-TTY environments and outputs JSON — no `--json` flag needed.

**Rules for agents:**
- Supply ALL required flags. The CLI will NOT prompt when stdin is not a TTY.
- Pass `--quiet` (or `-q`) to suppress spinners and status messages.
- Exit `0` = success, `1` = error.
- Success JSON goes to stdout, error JSON goes to stderr:
```json
{"error":{"message":"...","code":"..."}}
```
- Use `--api-key` or `RESEND_API_KEY` env var. Never rely on interactive login.
- All `delete`/`rm` commands require `--yes` in non-interactive mode.

## Authentication

Auth resolves: `--api-key` flag > `RESEND_API_KEY` env > config file (`resend login --key`). Use `--profile` or `RESEND_PROFILE` for multi-profile.

## Global Flags

| Flag | Description |
|------|-------------|
| `--api-key <key>` | Override API key for this invocation |
| `-p, --profile <name>` | Select stored profile |
| `--json` | Force JSON output (auto in non-TTY) |
| `-q, --quiet` | Suppress spinners/status (implies `--json`) |

## Available Commands

| Command Group | What it does |
|--------------|-------------|
| `emails` | send, get, list, batch, cancel, update |
| `emails receiving` | list, get, attachments, forward, listen |
| `domains` | create, verify, update, delete, list |
| `api-keys` | create, list, delete |
| `broadcasts` | create, send, update, delete, list |
| `contacts` | create, update, delete, segments, topics |
| `contact-properties` | create, update, delete, list |
| `segments` | create, list, delete |
| `templates` | create, publish, duplicate, delete, list |
| `topics` | create, update, delete, list |
| `webhooks` | create, update, listen, delete, list |
| `auth` | login, logout, switch, rename, remove |
| `whoami` / `doctor` / `update` / `open` | Utility commands |

Read the matching reference file for detailed flags and output shapes.

## Common Mistakes

| # | Mistake | Fix |
|---|---------|-----|
| 1 | **Forgetting `--yes` on delete commands** | All `delete`/`rm` subcommands require `--yes` in non-interactive mode — otherwise the CLI exits with an error |
| 2 | **Not saving webhook `signing_secret`** | `webhooks create` shows the secret once only — it cannot be retrieved later. Capture it from command output immediately |
| 3 | **Omitting `--quiet` in CI** | Without `-q`, spinners and status text leak into stdout. Use `-q` to get clean JSON only |
| 4 | **Using `--scheduled-at` with batch** | Batch sending does not support `scheduled_at` — use single `emails send` instead |
| 5 | **Expecting `domains list` to include DNS records** | List returns summaries only — use `domains get <id>` for the full `records[]` array |
| 6 | **Sending a dashboard-created broadcast via CLI** | Only API-created broadcasts can be sent with `broadcasts send` — dashboard broadcasts must be sent from the dashboard |
| 7 | **Passing `--events` to `webhooks update` expecting additive behavior** | `--events` replaces the entire subscription list — always pass the complete set |

## Common Patterns

**Send an email:**
```bash
resend emails send --from "you@domain.com" --to user@example.com --subject "Hello" --text "Body"
```

**Domain setup flow:**
```bash
resend domains create --name example.com --region us-east-1
# Configure DNS records from output, then:
resend domains verify <domain-id>
resend domains get <domain-id> # check status
```

**Create and send a broadcast:**
```bash
resend broadcasts create --from "news@domain.com" --subject "Update" --segment-id <id> --html "<h1>Hi</h1>" --send
```

**CI/CD (no login needed):**
```bash
RESEND_API_KEY=re_xxx resend emails send --from ... --to ... --subject ... --text ...
```

**Check environment health:**
```bash
resend doctor -q
```

## When to Load References

- **Sending or reading emails** → [references/emails.md](references/emails.md)
- **Setting up or verifying a domain** → [references/domains.md](references/domains.md)
- **Managing API keys** → [references/api-keys.md](references/api-keys.md)
- **Creating or sending broadcasts** → [references/broadcasts.md](references/broadcasts.md)
- **Managing contacts, segments, or topics** → [references/contacts.md](references/contacts.md), [references/segments.md](references/segments.md), [references/topics.md](references/topics.md)
- **Defining contact properties** → [references/contact-properties.md](references/contact-properties.md)
- **Working with templates** → [references/templates.md](references/templates.md)
- **Setting up webhooks or listening for events** → [references/webhooks.md](references/webhooks.md)
- **Auth, profiles, or health checks** → [references/auth.md](references/auth.md)
- **Multi-step recipes** (setup, CI/CD, broadcast workflow) → [references/workflows.md](references/workflows.md)
- **Command failed with an error** → [references/error-codes.md](references/error-codes.md)
- **Resend SDK integration** (Node.js, Python, Go, etc.) → Install the [`resend`](https://github.com/resend/resend-skills) skill
- **AI agent email inbox** → Install the [`agent-email-inbox`](https://github.com/resend/resend-skills) skill
33 changes: 33 additions & 0 deletions skills/resend-cli/references/api-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# api-keys

Detailed flag specifications for `resend api-keys` commands.

---

## api-keys list

List all API keys (IDs and names only — tokens never included).

---

## api-keys create

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--name <name>` | string | Yes (non-interactive) | Key name (max 50 chars) |
| `--permission <perm>` | string | No | `full_access` (default) \| `sending_access` |
| `--domain-id <id>` | string | No | Restrict `sending_access` to one domain |

**Output:** `{"id":"...","token":"re_..."}` — token shown once only.

---

## api-keys delete

**Argument:** `<id>` — API key ID

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--yes` | boolean | Yes (non-interactive) | Skip confirmation |

**Alias:** `rm`
67 changes: 67 additions & 0 deletions skills/resend-cli/references/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# auth & utility

Detailed flag specifications for `resend auth` and utility commands.

---

## auth login

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--key <key>` | string | Yes (non-interactive) | API key (must start with `re_`) |

---

## auth logout

Removes the active profile's credentials (or all profiles if no `--profile`).

---

## auth list

Lists all profiles with active marker.

---

## auth switch

**Argument:** `[name]` — Profile name (prompts in interactive if omitted)

---

## auth rename

**Arguments:** `[old-name]` `[new-name]` — Prompts in interactive if omitted

---

## auth remove

**Argument:** `[name]` — Profile name (prompts in interactive if omitted)

---

## whoami

No flags. Shows authentication status (local only, no network calls).

---

## doctor

Checks: CLI Version, API Key, Domains, AI Agents.

Exits `0` if all pass/warn, `1` if any fail.

---

## update

Checks GitHub releases for newer version. Shows upgrade command.

---

## open

Opens `https://resend.com/emails` in the default browser.
81 changes: 81 additions & 0 deletions skills/resend-cli/references/broadcasts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# broadcasts

Detailed flag specifications for `resend broadcasts` commands.

---

## broadcasts list

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `--limit <n>` | number | 10 | Max results (1-100) |
| `--after <cursor>` | string | — | Forward pagination |
| `--before <cursor>` | string | — | Backward pagination |

---

## broadcasts create

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--from <address>` | string | Yes | Sender address |
| `--subject <subject>` | string | Yes | Email subject |
| `--segment-id <id>` | string | Yes | Target segment |
| `--html <html>` | string | One of html/html-file/text | HTML body (supports `{{{PROPERTY\|fallback}}}`) |
| `--html-file <path>` | string | One of html/html-file/text | Path to HTML file |
| `--text <text>` | string | One of html/html-file/text | Plain-text body |
| `--name <name>` | string | No | Internal label |
| `--reply-to <address>` | string | No | Reply-to address |
| `--preview-text <text>` | string | No | Preview text |
| `--topic-id <id>` | string | No | Topic for subscription filtering |
| `--send` | boolean | No | Send immediately (default: save as draft) |
| `--scheduled-at <datetime>` | string | No | Schedule delivery (only with `--send`) |

---

## broadcasts get

**Argument:** `<id>` — Broadcast ID

Returns full object with html/text, from, subject, status (`draft`|`queued`|`sent`), timestamps.

---

## broadcasts send

Send a draft broadcast.

**Argument:** `<id>` — Broadcast ID

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--scheduled-at <datetime>` | string | No | Schedule instead of immediate send |

**Note:** Dashboard-created broadcasts cannot be sent via API.

---

## broadcasts update

**Argument:** `<id>` — Broadcast ID (must be draft)

| Flag | Type | Description |
|------|------|-------------|
| `--from <address>` | string | Update sender |
| `--subject <subject>` | string | Update subject |
| `--html <html>` | string | Update HTML body |
| `--html-file <path>` | string | Path to HTML file |
| `--text <text>` | string | Update plain-text body |
| `--name <name>` | string | Update internal label |

---

## broadcasts delete

**Argument:** `<id>` — Broadcast ID

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--yes` | boolean | Yes (non-interactive) | Skip confirmation |

**Alias:** `rm`
Loading