Skip to content
Open
14 changes: 10 additions & 4 deletions skills/resend-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
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
Operate the Resend platform from the terminal — send emails (including React Email
.tsx templates via --react-email), 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, or send/preview React Email
templates. Always load this skill before running `resend` commands — it contains
the non-interactive flag contract and gotchas that prevent silent failures.
license: MIT
metadata:
Expand Down Expand Up @@ -101,6 +102,11 @@ Read the matching reference file for detailed flags and output shapes.
resend emails send --from "you@domain.com" --to user@example.com --subject "Hello" --text "Body"
```

**Send a React Email template (.tsx):**
```bash
resend emails send --from "you@domain.com" --to user@example.com --subject "Welcome" --react-email ./emails/welcome.tsx
```

**Domain setup flow:**
```bash
resend domains create --name example.com --region us-east-1
Expand Down
2 changes: 2 additions & 0 deletions skills/resend-cli/references/broadcasts.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Detailed flag specifications for `resend broadcasts` commands.
| `--html <html>` | string | At least one body flag | HTML body (supports `{{{PROPERTY\|fallback}}}`) |
| `--html-file <path>` | string | At least one body flag | Path to HTML file |
| `--text <text>` | string | At least one body flag | Plain-text body |
| `--react-email <path>` | string | At least one body flag | Path to React Email template (.tsx) — bundles and renders to HTML. Compatible with `--text` for plain-text fallback |
| `--name <name>` | string | No | Internal label |
| `--reply-to <address>` | string | No | Reply-to address |
| `--preview-text <text>` | string | No | Preview text |
Expand Down Expand Up @@ -66,6 +67,7 @@ Send a draft broadcast.
| `--html <html>` | string | Update HTML body |
| `--html-file <path>` | string | Path to HTML file |
| `--text <text>` | string | Update plain-text body |
| `--react-email <path>` | string | Path to React Email template (.tsx) — bundles and renders to HTML |
| `--name <name>` | string | Update internal label |

---
Expand Down
8 changes: 5 additions & 3 deletions skills/resend-cli/references/emails.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ Send an email via the Resend API.
| `--from <address>` | string | Yes | Sender address (must be on a verified domain) |
| `--to <addresses...>` | string[] | Yes | Recipient(s), space-separated |
| `--subject <subject>` | string | Yes | Email subject line |
| `--text <text>` | string | One of text/html/html-file | Plain-text body |
| `--html <html>` | string | One of text/html/html-file | HTML body |
| `--html-file <path>` | string | One of text/html/html-file | Path to HTML file |
| `--text <text>` | string | One of text/html/html-file/react-email | Plain-text body |
| `--html <html>` | string | One of text/html/html-file/react-email | HTML body |
| `--html-file <path>` | string | One of text/html/html-file/react-email | Path to HTML file |
| `--react-email <path>` | string | One of text/html/html-file/react-email | Path to React Email template (.tsx) — bundles, renders to HTML, and sends |
| `--cc <addresses...>` | string[] | No | CC recipients |
| `--bcc <addresses...>` | string[] | No | BCC recipients |
| `--reply-to <address>` | string | No | Reply-to address |
Expand Down Expand Up @@ -72,6 +73,7 @@ Send up to 100 emails in a single request.
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--file <path>` | string | Yes (non-interactive) | Path to JSON file with email array |
| `--react-email <path>` | string | No | Path to React Email template (.tsx) — rendered HTML is set on every email in the batch |
| `--idempotency-key <key>` | string | No | Deduplicate batch |
| `--batch-validation <mode>` | string | No | `strict` (fail all) or `permissive` (partial success) |

Expand Down
4 changes: 3 additions & 1 deletion skills/resend-cli/references/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ All errors exit with code `1` and output JSON to **stderr**:

| Code | Cause | Resolution |
|------|-------|------------|
| `missing_body` | None of `--text`, `--html`, or `--html-file` provided | Provide at least one body flag |
| `missing_body` | None of `--text`, `--html`, `--html-file`, or `--react-email` provided | Provide at least one body flag |
| `react_email_build_error` | Failed to bundle a React Email `.tsx` template with esbuild | Check the template compiles; ensure `react` and `@react-email/render` (or `@react-email/components`) are installed in the project |
| `react_email_render_error` | Bundled template failed during `render()` | Check the component exports a default function and renders valid React Email markup |
| `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 |

Expand Down
7 changes: 4 additions & 3 deletions skills/resend-cli/references/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Detailed flag specifications for `resend templates` commands.
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--name <name>` | string | Yes | Template name |
| `--html <html>` | string | One of html/html-file | HTML body with `{{{VAR_NAME}}}` placeholders |
| `--html-file <path>` | string | One of html/html-file | Path to HTML file |
| `--html <html>` | string | One of html/html-file/react-email | HTML body with `{{{VAR_NAME}}}` placeholders |
| `--html-file <path>` | string | One of html/html-file/react-email | Path to HTML file |
| `--react-email <path>` | string | One of html/html-file/react-email | Path to React Email template (.tsx) — bundles and renders to HTML |
| `--subject <subject>` | string | No | Email subject |
| `--text <text>` | string | No | Plain-text body |
| `--from <address>` | string | No | Sender address |
Expand All @@ -42,7 +43,7 @@ Variable types: `string`, `number`

**Argument:** `<id|alias>` — Template ID or alias

Same optional flags as `create`. At least one required.
Same optional flags as `create` (including `--react-email`). At least one required.

---

Expand Down
31 changes: 31 additions & 0 deletions skills/resend-cli/references/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ resend emails send \
--cc manager@example.com \
--reply-to support@yourdomain.com

# React Email template (.tsx) — bundles, renders to HTML, and sends
resend emails send \
--from "you@yourdomain.com" \
--to recipient@example.com \
--subject "Welcome" \
--react-email ./emails/welcome.tsx

# React Email with plain-text fallback
resend emails send \
--from "you@yourdomain.com" \
--to recipient@example.com \
--subject "Welcome" \
--react-email ./emails/welcome.tsx \
--text "Welcome to our platform!"

# Scheduled email
resend emails send \
--from "you@yourdomain.com" \
Expand Down Expand Up @@ -118,6 +133,14 @@ resend broadcasts create \
--html "<h1>Hello {{{FIRST_NAME|there}}}</h1><p>News content...</p>" \
--send

# Create broadcast from a React Email template
resend broadcasts create \
--from "news@yourdomain.com" \
--subject "Monthly Update" \
--segment-id <segment-id> \
--react-email ./emails/newsletter.tsx \
--text "Plain-text fallback for email clients that don't support HTML"

# Or create as draft first, then send later
resend broadcasts create \
--from "news@yourdomain.com" \
Expand Down Expand Up @@ -223,6 +246,14 @@ resend templates duplicate welcome-email

# Update the copy
resend templates update <new-id> --name "Welcome Email v2" --subject "Hey {{{NAME}}}!"

# Create a template from a React Email component
resend templates create \
--name "Onboarding" \
--react-email ./emails/onboarding.tsx

# Update a template with a new React Email version
resend templates update <id> --react-email ./emails/onboarding-v2.tsx
```

---
Expand Down
25 changes: 23 additions & 2 deletions src/commands/broadcasts/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { readFile } from '../../lib/files';
import { buildHelpText } from '../../lib/help-text';
import { outputError } from '../../lib/output';
import { cancelAndExit } from '../../lib/prompts';
import { buildReactEmailHtml } from '../../lib/react-email';
import { isInteractive } from '../../lib/tty';

export const createBroadcastCommand = new Command('create')
Expand All @@ -29,6 +30,10 @@ export const createBroadcastCommand = new Command('create')
'--text-file <path>',
'Path to a plain-text file for the body (use "-" for stdin)',
)
.option(
'--react-email <path>',
'Path to a React Email template (.tsx) to bundle, render, and use as HTML body',
)
.option('--name <name>', 'Internal label for the broadcast (optional)')
.option('--reply-to <address>', 'Reply-to address (optional)')
.option(
Expand All @@ -48,7 +53,7 @@ export const createBroadcastCommand = new Command('create')
'after',
buildHelpText({
context: `Non-interactive: --from, --subject, and --segment-id are required.
Body: provide at least one of --html, --html-file, --text, or --text-file.
Body: provide at least one of --html, --html-file, --text, --text-file, or --react-email.

Variable interpolation:
HTML bodies support triple-brace syntax for contact properties.
Expand All @@ -68,6 +73,8 @@ Scheduling:
'file_read_error',
'invalid_options',
'stdin_read_error',
'react_email_build_error',
'react_email_render_error',
'create_error',
],
examples: [
Expand All @@ -93,6 +100,16 @@ Scheduling:
);
}

if (opts.reactEmail && (opts.html || opts.htmlFile)) {
outputError(
{
message: 'Cannot use --react-email with --html or --html-file',
code: 'invalid_options',
},
{ json: globalOpts.json },
);
}

const resend = await requireClient(globalOpts);

let from = opts.from;
Expand Down Expand Up @@ -181,12 +198,16 @@ Scheduling:
text = readFile(opts.textFile, globalOpts);
}

if (opts.reactEmail) {
html = await buildReactEmailHtml(opts.reactEmail, globalOpts);
}

if (!html && !text) {
if (!isInteractive() || globalOpts.json) {
outputError(
{
message:
'Missing body. Provide --html, --html-file, --text, or --text-file.',
'Missing body. Provide --html, --html-file, --text, --text-file, or --react-email.',
code: 'missing_body',
},
{ json: globalOpts.json },
Expand Down
24 changes: 23 additions & 1 deletion src/commands/broadcasts/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { readFile } from '../../lib/files';
import { buildHelpText } from '../../lib/help-text';
import { outputError } from '../../lib/output';
import { pickId } from '../../lib/prompts';
import { buildReactEmailHtml } from '../../lib/react-email';
import { broadcastPickerConfig } from './utils';

export const updateBroadcastCommand = new Command('update')
Expand All @@ -27,6 +28,10 @@ export const updateBroadcastCommand = new Command('update')
'--text-file <path>',
'Path to a plain-text file to replace the body (use "-" for stdin)',
)
.option(
'--react-email <path>',
'Path to a React Email template (.tsx) to bundle, render, and use as HTML body',
)
.option('--name <name>', 'Update internal label')
.addHelpText(
'after',
Expand All @@ -44,6 +49,8 @@ Variable interpolation:
'file_read_error',
'invalid_options',
'stdin_read_error',
'react_email_build_error',
'react_email_render_error',
'update_error',
],
examples: [
Expand All @@ -64,12 +71,13 @@ Variable interpolation:
!opts.htmlFile &&
!opts.text &&
!opts.textFile &&
!opts.reactEmail &&
!opts.name
) {
outputError(
{
message:
'Provide at least one option to update: --from, --subject, --html, --html-file, --text, --text-file, or --name.',
'Provide at least one option to update: --from, --subject, --html, --html-file, --text, --text-file, --react-email, or --name.',
code: 'no_changes',
},
{ json: globalOpts.json },
Expand All @@ -87,6 +95,16 @@ Variable interpolation:
);
}

if (opts.reactEmail && (opts.html || opts.htmlFile)) {
outputError(
{
message: 'Cannot use --react-email with --html or --html-file',
code: 'invalid_options',
},
{ json: globalOpts.json },
);
}

const id = await pickId(idArg, broadcastPickerConfig, globalOpts);

let html = opts.html;
Expand All @@ -110,6 +128,10 @@ Variable interpolation:
text = readFile(opts.textFile, globalOpts);
}

if (opts.reactEmail) {
html = await buildReactEmailHtml(opts.reactEmail, globalOpts);
}

await runWrite(
{
spinner: {
Expand Down
14 changes: 14 additions & 0 deletions src/commands/emails/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { readFile } from '../../lib/files';
import { buildHelpText } from '../../lib/help-text';
import { outputError, outputResult } from '../../lib/output';
import { requireText } from '../../lib/prompts';
import { buildReactEmailHtml } from '../../lib/react-email';
import { withSpinner } from '../../lib/spinner';
import { isInteractive } from '../../lib/tty';

Expand All @@ -15,6 +16,10 @@ export const batchCommand = new Command('batch')
'--file <path>',
'Path to a JSON file containing an array of email objects (use "-" for stdin; required in non-interactive mode)',
)
.option(
'--react-email <path>',
'Path to a React Email template (.tsx) — rendered HTML is set on every email in the batch',
)
.option(
'--idempotency-key <key>',
'Deduplicate this batch request using this key',
Expand All @@ -38,6 +43,8 @@ export const batchCommand = new Command('batch')
'stdin_read_error',
'invalid_json',
'invalid_format',
'react_email_build_error',
'react_email_render_error',
'batch_error',
],
examples: [
Expand Down Expand Up @@ -98,6 +105,13 @@ export const batchCommand = new Command('batch')
);
}

if (opts.reactEmail) {
const reactHtml = await buildReactEmailHtml(opts.reactEmail, globalOpts);
for (const email of emails) {
(email as Record<string, unknown>).html = reactHtml;
}
}

for (let i = 0; i < emails.length; i++) {
const email = emails[i] as Record<string, unknown>;
if ('attachments' in email) {
Expand Down
Loading