diff --git a/package.json b/package.json index b5a5924..dd3a1a5 100644 --- a/package.json +++ b/package.json @@ -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": { @@ -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", diff --git a/scripts/sync-skill-version.mjs b/scripts/sync-skill-version.mjs new file mode 100644 index 0000000..8a52cab --- /dev/null +++ b/scripts/sync-skill-version.mjs @@ -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); diff --git a/skills/resend-cli/SKILL.md b/skills/resend-cli/SKILL.md new file mode 100644 index 0000000..01947ac --- /dev/null +++ b/skills/resend-cli/SKILL.md @@ -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 ` | Override API key for this invocation | +| `-p, --profile ` | 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 ` 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 +resend domains get # check status +``` + +**Create and send a broadcast:** +```bash +resend broadcasts create --from "news@domain.com" --subject "Update" --segment-id --html "

Hi

" --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 diff --git a/skills/resend-cli/references/api-keys.md b/skills/resend-cli/references/api-keys.md new file mode 100644 index 0000000..e7b7a39 --- /dev/null +++ b/skills/resend-cli/references/api-keys.md @@ -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 ` | string | Yes (non-interactive) | Key name (max 50 chars) | +| `--permission ` | string | No | `full_access` (default) \| `sending_access` | +| `--domain-id ` | string | No | Restrict `sending_access` to one domain | + +**Output:** `{"id":"...","token":"re_..."}` — token shown once only. + +--- + +## api-keys delete + +**Argument:** `` — API key ID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +**Alias:** `rm` diff --git a/skills/resend-cli/references/auth.md b/skills/resend-cli/references/auth.md new file mode 100644 index 0000000..d3fb779 --- /dev/null +++ b/skills/resend-cli/references/auth.md @@ -0,0 +1,67 @@ +# auth & utility + +Detailed flag specifications for `resend auth` and utility commands. + +--- + +## auth login + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--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. diff --git a/skills/resend-cli/references/broadcasts.md b/skills/resend-cli/references/broadcasts.md new file mode 100644 index 0000000..b36f156 --- /dev/null +++ b/skills/resend-cli/references/broadcasts.md @@ -0,0 +1,81 @@ +# broadcasts + +Detailed flag specifications for `resend broadcasts` commands. + +--- + +## broadcasts list + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## broadcasts create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--from
` | string | Yes | Sender address | +| `--subject ` | string | Yes | Email subject | +| `--segment-id ` | string | Yes | Target segment | +| `--html ` | string | One of html/html-file/text | HTML body (supports `{{{PROPERTY\|fallback}}}`) | +| `--html-file ` | string | One of html/html-file/text | Path to HTML file | +| `--text ` | string | One of html/html-file/text | Plain-text body | +| `--name ` | string | No | Internal label | +| `--reply-to
` | string | No | Reply-to address | +| `--preview-text ` | string | No | Preview text | +| `--topic-id ` | string | No | Topic for subscription filtering | +| `--send` | boolean | No | Send immediately (default: save as draft) | +| `--scheduled-at ` | string | No | Schedule delivery (only with `--send`) | + +--- + +## broadcasts get + +**Argument:** `` — Broadcast ID + +Returns full object with html/text, from, subject, status (`draft`|`queued`|`sent`), timestamps. + +--- + +## broadcasts send + +Send a draft broadcast. + +**Argument:** `` — Broadcast ID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--scheduled-at ` | string | No | Schedule instead of immediate send | + +**Note:** Dashboard-created broadcasts cannot be sent via API. + +--- + +## broadcasts update + +**Argument:** `` — Broadcast ID (must be draft) + +| Flag | Type | Description | +|------|------|-------------| +| `--from
` | string | Update sender | +| `--subject ` | string | Update subject | +| `--html ` | string | Update HTML body | +| `--html-file ` | string | Path to HTML file | +| `--text ` | string | Update plain-text body | +| `--name ` | string | Update internal label | + +--- + +## broadcasts delete + +**Argument:** `` — Broadcast ID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +**Alias:** `rm` diff --git a/skills/resend-cli/references/contact-properties.md b/skills/resend-cli/references/contact-properties.md new file mode 100644 index 0000000..fbd6ba2 --- /dev/null +++ b/skills/resend-cli/references/contact-properties.md @@ -0,0 +1,56 @@ +# contact-properties + +Detailed flag specifications for `resend contact-properties` commands. + +--- + +## contact-properties list + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## contact-properties create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--key ` | string | Yes (non-interactive) | Property key name | +| `--type ` | string | Yes (non-interactive) | `string` \| `number` | +| `--fallback-value ` | string | No | Default in templates | + +Reserved keys: `FIRST_NAME`, `LAST_NAME`, `EMAIL`, `UNSUBSCRIBE_URL` + +--- + +## contact-properties get + +**Argument:** `` — Property UUID + +--- + +## contact-properties update + +**Argument:** `` — Property UUID + +| Flag | Type | Description | +|------|------|-------------| +| `--fallback-value ` | string | New fallback | +| `--clear-fallback-value` | boolean | Remove fallback (mutually exclusive with above) | + +Key and type are immutable after creation. + +--- + +## contact-properties delete + +**Argument:** `` — Property UUID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +**Warning:** Removes property from ALL contacts permanently. diff --git a/skills/resend-cli/references/contacts.md b/skills/resend-cli/references/contacts.md new file mode 100644 index 0000000..5d6da50 --- /dev/null +++ b/skills/resend-cli/references/contacts.md @@ -0,0 +1,100 @@ +# contacts + +Detailed flag specifications for `resend contacts` commands. + +--- + +## contacts list + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## contacts create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--email ` | string | Yes | Contact email | +| `--first-name ` | string | No | First name | +| `--last-name ` | string | No | Last name | +| `--unsubscribed` | boolean | No | Globally unsubscribe | +| `--properties ` | string | No | Custom properties JSON | +| `--segment-id ` | string[] | No | Add to segment(s) | + +--- + +## contacts get + +**Argument:** `` — Contact UUID or email address (both accepted) + +--- + +## contacts update + +**Argument:** `` — Contact UUID or email address + +| Flag | Type | Description | +|------|------|-------------| +| `--unsubscribed` | boolean | Set unsubscribed | +| `--no-unsubscribed` | boolean | Re-subscribe | +| `--properties ` | string | Merge properties (set key to `null` to clear) | + +--- + +## contacts delete + +**Argument:** `` — Contact UUID or email address + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +**Alias:** `rm` + +--- + +## contacts segments + +List segments a contact belongs to. + +**Argument:** `` — Contact UUID or email + +--- + +## contacts add-segment + +**Argument:** `` — Contact UUID or email + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--segment-id ` | string | Yes (non-interactive) | Segment ID to add to | + +--- + +## contacts remove-segment + +**Arguments:** `` `` + +--- + +## contacts topics + +List contact's topic subscriptions. + +**Argument:** `` — Contact UUID or email + +--- + +## contacts update-topics + +**Argument:** `` — Contact UUID or email + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--topics ` | string | Yes (non-interactive) | JSON array: `[{"id":"topic-uuid","subscription":"opt_in"}]` | + +Subscription values: `opt_in` | `opt_out` diff --git a/skills/resend-cli/references/domains.md b/skills/resend-cli/references/domains.md new file mode 100644 index 0000000..d3569bd --- /dev/null +++ b/skills/resend-cli/references/domains.md @@ -0,0 +1,79 @@ +# domains + +Detailed flag specifications for `resend domains` commands. + +--- + +## domains list + +List all domains. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +**Note:** List does NOT include DNS records. Use `domains get` for full details. + +--- + +## domains create + +Create a new domain and receive DNS records to configure. + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--name ` | string | Yes (non-interactive) | Domain name (e.g., `example.com`) | +| `--region ` | string | No | `us-east-1` \| `eu-west-1` \| `sa-east-1` \| `ap-northeast-1` | +| `--tls ` | string | No | `opportunistic` (default) \| `enforced` | +| `--sending` | boolean | No | Enable sending (default: enabled) | +| `--receiving` | boolean | No | Enable receiving (default: disabled) | + +**Output:** Domain object with `records[]` array of DNS records to configure. + +--- + +## domains get + +**Argument:** `` — Domain ID + +Returns full domain with `records[]`, `status` (`not_started`|`pending`|`verified`|`failed`|`temporary_failure`), `capabilities`, `region`. + +--- + +## domains verify + +Trigger async DNS verification. + +**Argument:** `` — Domain ID + +**Output:** `{"object":"domain","id":"..."}` + +--- + +## domains update + +**Argument:** `` — Domain ID + +| Flag | Type | Description | +|------|------|-------------| +| `--tls ` | string | `opportunistic` \| `enforced` | +| `--open-tracking` | boolean | Enable open tracking | +| `--no-open-tracking` | boolean | Disable open tracking | +| `--click-tracking` | boolean | Enable click tracking | +| `--no-click-tracking` | boolean | Disable click tracking | + +At least one option required. + +--- + +## domains delete + +**Argument:** `` — Domain ID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +**Alias:** `rm` diff --git a/skills/resend-cli/references/emails.md b/skills/resend-cli/references/emails.md new file mode 100644 index 0000000..a5b18f9 --- /dev/null +++ b/skills/resend-cli/references/emails.md @@ -0,0 +1,177 @@ +# emails + +Detailed flag specifications for `resend emails` commands. + +--- + +## emails send + +Send an email via the Resend API. + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--from
` | string | Yes | Sender address (must be on a verified domain) | +| `--to ` | string[] | Yes | Recipient(s), space-separated | +| `--subject ` | string | Yes | Email subject line | +| `--text ` | string | One of text/html/html-file | Plain-text body | +| `--html ` | string | One of text/html/html-file | HTML body | +| `--html-file ` | string | One of text/html/html-file | Path to HTML file | +| `--cc ` | string[] | No | CC recipients | +| `--bcc ` | string[] | No | BCC recipients | +| `--reply-to
` | string | No | Reply-to address | +| `--scheduled-at ` | string | No | Schedule for later (ISO 8601) | +| `--attachment ` | string[] | No | File paths to attach | +| `--headers ` | string[] | No | Custom headers | +| `--tags ` | string[] | No | Email tags | +| `--idempotency-key ` | string | No | Deduplicate request | + +**Output:** `{"id":""}` + +--- + +## emails get + +Retrieve a sent email by ID. + +**Argument:** `` — Email UUID + +**Output:** +```json +{ + "object": "email", + "id": "", + "from": "you@domain.com", + "to": ["user@example.com"], + "subject": "Hello", + "last_event": "delivered", + "created_at": "", + "scheduled_at": null +} +``` + +--- + +## emails list + +List sent emails. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination cursor | +| `--before ` | string | — | Backward pagination cursor | + +**Output:** `{"object":"list","data":[...],"has_more":bool}` + +--- + +## emails batch + +Send up to 100 emails in a single request. + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--file ` | string | Yes (non-interactive) | Path to JSON file with email array | +| `--idempotency-key ` | string | No | Deduplicate batch | +| `--batch-validation ` | string | No | `strict` (fail all) or `permissive` (partial success) | + +**JSON file format:** +```json +[ + {"from":"a@domain.com","to":["b@example.com"],"subject":"Hi","text":"Body"}, + {"from":"a@domain.com","to":["c@example.com"],"subject":"Hi","html":"Body"} +] +``` + +**Output (success):** `[{"id":"..."},{"id":"..."}]` +**Output (permissive with errors):** `{"data":[{"id":"..."}],"errors":[{"index":1,"message":"..."}]}` + +**Constraints:** Max 100 emails. Attachments and `scheduled_at` not supported per-email. + +--- + +## emails cancel + +Cancel a scheduled email. + +**Argument:** `` — Email UUID + +**Output:** `{"object":"email","id":"..."}` + +--- + +## emails update + +Update a scheduled email. + +**Argument:** `` — Email UUID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--scheduled-at ` | string | Yes | New schedule (ISO 8601) | + +**Output:** `{"object":"email","id":"..."}` + +--- + +## emails receiving list + +List received (inbound) emails. Requires domain receiving enabled. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## emails receiving get + +**Argument:** `` — Received email UUID + +Returns full email with html, text, headers, `raw.download_url`, and `attachments[]`. + +--- + +## emails receiving attachments + +**Argument:** `` — Received email UUID + +Lists attachments with `id`, `filename`, `size`, `content_type`, `download_url`, `expires_at`. + +--- + +## emails receiving attachment + +**Arguments:** `` `` + +Returns single attachment object with `download_url`. + +--- + +## emails receiving forward + +**Argument:** `` — Received email UUID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--to ` | string[] | Yes | Forward recipients | +| `--from
` | string | Yes | Sender address | + +**Output:** `{"id":"..."}` + +--- + +## emails receiving listen + +Poll for new inbound emails and display them as they arrive. Long-running command; Ctrl+C exits cleanly. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--interval ` | number | 5 | Polling interval in seconds (minimum 2) | + +**Behavior:** +- Interactive: one-line-per-email display (timestamp, from, to, subject, id) +- Piped / `--json`: NDJSON (one JSON object per line) +- Exits after 5 consecutive API failures diff --git a/skills/resend-cli/references/error-codes.md b/skills/resend-cli/references/error-codes.md new file mode 100644 index 0000000..1208829 --- /dev/null +++ b/skills/resend-cli/references/error-codes.md @@ -0,0 +1,54 @@ +# Error Codes + +All errors exit with code `1` and output JSON: + +```json +{"error":{"message":"Human-readable description","code":"error_code"}} +``` + +## Authentication Errors + +| Code | Cause | Resolution | +|------|-------|------------| +| `auth_error` | No API key found from any source | Set `RESEND_API_KEY` env, pass `--api-key`, or run `resend login --key re_xxx` | +| `missing_key` | `login` called non-interactively without `--key` | Pass `--key re_xxx` | +| `invalid_key_format` | API key does not start with `re_` | Use a valid Resend API key starting with `re_` | +| `validation_failed` | Resend API rejected the key during login | Verify the key exists and is active at resend.com/api-keys | + +## Email Errors + +| Code | Cause | Resolution | +|------|-------|------------| +| `missing_body` | None of `--text`, `--html`, or `--html-file` provided | Provide at least one body flag | +| `file_read_error` | Could not read file from `--html-file` path | Check file path exists and is readable | +| `send_error` | Resend API rejected the send request | Check from address is on a verified domain; check recipient is valid | + +## Domain Errors + +| Code | Cause | Resolution | +|------|-------|------------| +| `domain_error` | Domain creation, verification, or update failed | Check domain name is valid; check DNS records are configured | + +## General Errors + +| Code | Cause | Resolution | +|------|-------|------------| +| `unexpected_error` | Unhandled exception | Check CLI version with `resend update`; report at github.com/resend/resend-cli/issues | +| `unknown` | Error without a specific code | Inspect the `message` field for details | + +## Troubleshooting + +### "No API key found" in CI +Ensure `RESEND_API_KEY` is set in the environment. The CLI does not prompt in non-TTY mode. + +### "Missing required flags" errors +In non-interactive mode (CI, piped, agent), ALL required flags must be provided. The CLI will not prompt. + +### Deletion commands fail without `--yes` +All `delete`/`rm` subcommands require `--yes` in non-interactive mode to prevent accidental deletion. + +### API rate limits +The Resend API has rate limits. If you hit them, the error message will indicate rate limiting. Add delays between batch operations. + +### Scheduled email errors +`--scheduled-at` must be a valid ISO 8601 datetime. The scheduled time must be in the future. diff --git a/skills/resend-cli/references/segments.md b/skills/resend-cli/references/segments.md new file mode 100644 index 0000000..a7bbc32 --- /dev/null +++ b/skills/resend-cli/references/segments.md @@ -0,0 +1,39 @@ +# segments + +Detailed flag specifications for `resend segments` commands. + +--- + +## segments list + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## segments create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--name ` | string | Yes (non-interactive) | Segment name | + +--- + +## segments get + +**Argument:** `` — Segment UUID + +--- + +## segments delete + +**Argument:** `` — Segment UUID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +Deleting a segment does NOT delete its contacts. diff --git a/skills/resend-cli/references/templates.md b/skills/resend-cli/references/templates.md new file mode 100644 index 0000000..ed8f584 --- /dev/null +++ b/skills/resend-cli/references/templates.md @@ -0,0 +1,67 @@ +# templates + +Detailed flag specifications for `resend templates` commands. + +--- + +## templates list + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## templates create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--name ` | string | Yes | Template name | +| `--html ` | string | One of html/html-file | HTML body with `{{{VAR_NAME}}}` placeholders | +| `--html-file ` | string | One of html/html-file | Path to HTML file | +| `--subject ` | string | No | Email subject | +| `--text ` | string | No | Plain-text body | +| `--from
` | string | No | Sender address | +| `--reply-to
` | string | No | Reply-to address | +| `--alias ` | string | No | Lookup alias | +| `--var ` | string[] | No | Variables: `KEY:type` or `KEY:type:fallback` | + +Variable types: `string`, `number` + +--- + +## templates get + +**Argument:** `` — Template ID or alias + +--- + +## templates update + +**Argument:** `` — Template ID or alias + +Same optional flags as `create`. At least one required. + +--- + +## templates publish + +**Argument:** `` — Promotes draft to published. + +--- + +## templates duplicate + +**Argument:** `` — Creates a copy as draft. + +--- + +## templates delete + +**Argument:** `` + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | diff --git a/skills/resend-cli/references/topics.md b/skills/resend-cli/references/topics.md new file mode 100644 index 0000000..37e91bb --- /dev/null +++ b/skills/resend-cli/references/topics.md @@ -0,0 +1,48 @@ +# topics + +Detailed flag specifications for `resend topics` commands. + +--- + +## topics list + +Lists all topics. No pagination flags. + +--- + +## topics create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--name ` | string | Yes (non-interactive) | Topic name | +| `--description ` | string | No | Description | +| `--default-subscription ` | string | No | `opt_in` (default) \| `opt_out` | + +--- + +## topics get + +**Argument:** `` — Topic UUID + +--- + +## topics update + +**Argument:** `` — Topic UUID + +| Flag | Type | Description | +|------|------|-------------| +| `--name ` | string | New name | +| `--description ` | string | New description | + +`default_subscription` cannot be changed after creation. + +--- + +## topics delete + +**Argument:** `` — Topic UUID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | diff --git a/skills/resend-cli/references/webhooks.md b/skills/resend-cli/references/webhooks.md new file mode 100644 index 0000000..0cb6321 --- /dev/null +++ b/skills/resend-cli/references/webhooks.md @@ -0,0 +1,79 @@ +# webhooks + +Detailed flag specifications for `resend webhooks` commands. + +--- + +## webhooks list + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--limit ` | number | 10 | Max results (1-100) | +| `--after ` | string | — | Forward pagination | +| `--before ` | string | — | Backward pagination | + +--- + +## webhooks create + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--endpoint ` | string | Yes (non-interactive) | HTTPS webhook URL | +| `--events ` | string[] | Yes (non-interactive) | Event types or `all` | + +**All 17 events:** +- Email: `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` + +**Output includes `signing_secret`** — shown once only. Save immediately. + +--- + +## webhooks get + +**Argument:** `` — Webhook UUID + +**Note:** `signing_secret` is NOT returned by get (only at creation). + +--- + +## webhooks update + +**Argument:** `` — Webhook UUID + +| Flag | Type | Description | +|------|------|-------------| +| `--endpoint ` | string | New HTTPS URL | +| `--events ` | string[] | Replace event list (not additive) | +| `--status ` | string | `enabled` \| `disabled` | + +--- + +## webhooks delete + +**Argument:** `` — Webhook UUID + +| Flag | Type | Required | Description | +|------|------|----------|-------------| +| `--yes` | boolean | Yes (non-interactive) | Skip confirmation | + +--- + +## webhooks listen + +Start a local server that receives Resend webhook events in real time via a public tunnel URL. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--url ` | string | — | Public tunnel URL for receiving webhooks (required in non-interactive) | +| `--forward-to ` | string | — | Forward payloads to this local URL (preserves Svix headers) | +| `--events ` | string[] | all | Event types to listen for | +| `--port ` | number | 4318 | Local server port | + +**Behavior:** +1. Starts a local HTTP server on `--port` +2. Registers a temporary Resend webhook pointing at `--url` +3. Displays incoming events in the terminal +4. Optionally forwards payloads to `--forward-to` with original Svix headers +5. Deletes the temporary webhook on exit (Ctrl+C) diff --git a/skills/resend-cli/references/workflows.md b/skills/resend-cli/references/workflows.md new file mode 100644 index 0000000..a414163 --- /dev/null +++ b/skills/resend-cli/references/workflows.md @@ -0,0 +1,329 @@ +# Workflow Recipes + +Multi-step recipes for common Resend CLI tasks. + +--- + +## 1. Initial Setup + +```bash +# Install (pick one) +curl -fsSL https://resend.com/install.sh | bash # Linux / macOS +npm install -g resend-cli # npm +brew install resend/cli/resend # Homebrew (macOS / Linux) +irm https://resend.com/install.ps1 | iex # Windows PowerShell + +# Authenticate +resend login --key re_xxx + +# Verify setup +resend doctor -q +``` + +--- + +## 2. Send a Single Email + +```bash +# Basic text email +resend emails send \ + --from "you@yourdomain.com" \ + --to recipient@example.com \ + --subject "Hello" \ + --text "Body text" + +# HTML email with attachments +resend emails send \ + --from "Name " \ + --to alice@example.com bob@example.com \ + --subject "Report" \ + --html-file ./email.html \ + --attachment ./report.pdf \ + --cc manager@example.com \ + --reply-to support@yourdomain.com + +# Scheduled email +resend emails send \ + --from "you@yourdomain.com" \ + --to recipient@example.com \ + --subject "Reminder" \ + --text "Don't forget!" \ + --scheduled-at "2026-01-15T09:00:00Z" + +# Check status +resend emails get + +# Cancel if scheduled +resend emails cancel +``` + +--- + +## 3. Batch Sending + +```bash +# Create a JSON file with up to 100 emails +cat > batch.json << 'EOF' +[ + {"from":"you@domain.com","to":["a@example.com"],"subject":"Hi A","text":"Hello A"}, + {"from":"you@domain.com","to":["b@example.com"],"subject":"Hi B","text":"Hello B"} +] +EOF + +# Send batch (strict mode: all fail if any invalid) +resend emails batch --file batch.json --batch-validation strict + +# Send batch (permissive: partial success allowed) +resend emails batch --file batch.json --batch-validation permissive +``` + +--- + +## 4. Domain Setup + +```bash +# Create domain with receiving enabled +resend domains create --name example.com --region us-east-1 --receiving + +# Output includes DNS records to configure: +# - MX records, TXT/DKIM records, SPF, DMARC +# Configure these in your DNS provider, then: + +# Trigger verification +resend domains verify + +# Check status (repeat until "verified") +resend domains get + +# Enable tracking +resend domains update --open-tracking --click-tracking +``` + +--- + +## 5. Broadcasts (Bulk Email) + +```bash +# 1. Create a segment +resend segments create --name "Newsletter Subscribers" + +# 2. Add contacts to segment +resend contacts create --email user@example.com --first-name Jane --segment-id + +# 3. Create and send broadcast +resend broadcasts create \ + --from "news@yourdomain.com" \ + --subject "Monthly Update" \ + --segment-id \ + --html "

Hello {{{FIRST_NAME|there}}}

News content...

" \ + --send + +# Or create as draft first, then send later +resend broadcasts create \ + --from "news@yourdomain.com" \ + --subject "Monthly Update" \ + --segment-id \ + --html-file ./newsletter.html \ + --name "March Newsletter" + +resend broadcasts send + +# Schedule for later +resend broadcasts send --scheduled-at "2026-03-15T10:00:00Z" +``` + +--- + +## 6. Webhook Setup + +```bash +# Create webhook for email delivery events +resend webhooks create \ + --endpoint https://yourapp.com/webhooks/resend \ + --events email.delivered email.bounced email.complained + +# IMPORTANT: Save the signing_secret from output — shown once only + +# Or subscribe to all events +resend webhooks create \ + --endpoint https://yourapp.com/webhooks/resend \ + --events all + +# Disable temporarily +resend webhooks update --status disabled + +# Re-enable +resend webhooks update --status enabled + +# Change subscribed events (replaces entire list) +resend webhooks update --events email.delivered email.bounced + +# Local development listener (requires a tunnel like ngrok) +resend webhooks listen --url https://example.ngrok-free.app + +# Forward events to your local app +resend webhooks listen \ + --url https://example.ngrok-free.app \ + --forward-to localhost:3000/webhook + +# Listen for specific events only +resend webhooks listen \ + --url https://example.ngrok-free.app \ + --events email.delivered email.bounced +``` + +--- + +## 7. Profile Management + +```bash +# Add production profile +resend login --key re_prod_xxx +# When prompted, name it "production" + +# Add staging profile +resend auth switch # or create via login +resend login --key re_staging_xxx + +# List profiles +resend auth list + +# Switch active profile +resend auth switch production + +# Use a profile for a single command +resend emails list --profile staging + +# Rename profile +resend auth rename old-name new-name + +# Remove profile +resend auth remove staging +``` + +--- + +## 8. Templates + +```bash +# Create a template with variables +resend templates create \ + --name "Welcome Email" \ + --subject "Welcome, {{{NAME}}}!" \ + --html "

Welcome {{{NAME}}}

Your plan: {{{PLAN}}}

" \ + --from "welcome@yourdomain.com" \ + --alias welcome-email \ + --var NAME:string --var PLAN:string:free + +# Publish the template +resend templates publish welcome-email + +# Duplicate for A/B testing +resend templates duplicate welcome-email + +# Update the copy +resend templates update --name "Welcome Email v2" --subject "Hey {{{NAME}}}!" +``` + +--- + +## 9. Contact & Topic Management + +```bash +# Define custom properties +resend contact-properties create --key company --type string +resend contact-properties create --key plan --type string --fallback-value free + +# Create contacts with properties +resend contacts create \ + --email user@example.com \ + --first-name Jane \ + --last-name Smith \ + --properties '{"company":"Acme","plan":"pro"}' + +# Create topics for subscription preferences +resend topics create --name "Product Updates" --default-subscription opt_in +resend topics create --name "Marketing" --default-subscription opt_out + +# Update contact topic subscriptions +resend contacts update-topics user@example.com \ + --topics '[{"id":"","subscription":"opt_in"}]' + +# Check subscriptions +resend contacts topics user@example.com +``` + +--- + +## 10. CI/CD Integration + +```yaml +# GitHub Actions example +name: Deploy Notification +on: + push: + branches: [main] + +env: + RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Install Resend CLI + run: npm install -g resend-cli + + - name: Send deploy notification + run: | + resend emails send \ + --from "deploy@yourdomain.com" \ + --to "team@yourdomain.com" \ + --subject "Deploy: ${{ github.repository }}@${{ github.sha }}" \ + --text "Deployed by ${{ github.actor }} at $(date -u)" +``` + +```bash +# Generic CI script +export RESEND_API_KEY=re_xxx +resend emails send -q \ + --from "ci@yourdomain.com" \ + --to "team@yourdomain.com" \ + --subject "Build complete" \ + --text "Build ${BUILD_ID} passed all tests." +``` + +--- + +## 11. Inbound Email Processing + +```bash +# Enable receiving on domain (at creation or check existing) +resend domains create --name example.com --receiving + +# List received emails +resend emails receiving list --limit 20 + +# Get full email content +resend emails receiving get + +# List attachments +resend emails receiving attachments + +# Get specific attachment download URL +resend emails receiving attachment + +# Forward received email +resend emails receiving forward \ + --from "forwarded@yourdomain.com" \ + --to colleague@example.com + +# Watch for new inbound emails in real time +resend emails receiving listen + +# Poll every 10 seconds +resend emails receiving listen --interval 10 + +# Stream as NDJSON (for scripting) +resend emails receiving listen --json | head -3 +```