Resend Send Native Node
Send email via the Resend.com HTTPS API.
Native Node.js. Zero dependencies. One POST call for real sends. Small enough to audit directly.
When to use
Trigger phrases: "email me", "send an email", "mail this to", "send a notification", "email the report".
Use this when:
- The user wants to send an email fast, without Gmail OAuth or App Password pain
- Simple "fire and forget" sends (no inbox reading needed)
- The user has a Resend.com account (check Resend's current pricing/limits before relying on a specific quota)
- Weekly/report-style outbound messages where the body is generated from explicitly reviewed text
Do NOT use this when:
- The user wants to READ email (this is send-only)
- The user needs to send from a specific personal Gmail address (use a Gmail-specific skill)
- Sensitive business emails where provenance matters (Resend's default
onboarding@resend.devsender looks transactional) - The recipient, sender, or final body has not been explicitly reviewed/approved for a real send
Safety policy for agents
This skill is send-only, but it is still externally mutating. For agent use:
- Draft first. Generate or inspect the exact body text before sending.
- Dry-run first. The script dry-runs by default; review the printed payload.
- Explicit approval. Use
--sendonly after the user explicitly approves the exactto,cc,bcc,from,reply-to,subject, and body. Treatreply-toas response-routing control and review display-name text infromfor spoof-like wording before any real send. - Use an allowlist. Real sends fail closed unless
RESEND_ALLOWED_TO=addr@example.com,other@example.comis set in the process environment for approved recipients. TreatRESEND_ALLOWED_TOas an operator-controlled, approval-scoped guard; agents must not broaden or set it merely to make a send succeed unless the user explicitly approves that exact allowlist change. - No raw memory dumps. Email only curated report text, not unfiltered memory, transcripts, logs, or private workspace context.
How to run
The script is in scripts/send.mjs. Requires Node.js 18+ because real sends use native fetch and AbortController.
For operator workflows, prefer --json so dry-runs and real sends produce a stable machine-readable receipt with mode, sent, recipients, subject, body byte count, full body SHA-256, SHA-256 prefix, allowlist status, and resendId on successful real sends.
Agent-safe invocation boundary
For agent-generated, user-supplied, multiline, or otherwise arbitrary subject, body, from, reply-to, to, cc, or bcc values, do not interpolate those values into a shell command string. Shell quoting is outside this script's control and can be broken before the script's dry-run, allowlist, or approval gates execute.
Canonical agent path: invoke Node directly with an argv array and pass reviewed message fields as one JSON object on stdin with --input-json-stdin. Keep mode flags such as --dry-run, --send, and --json in argv; the stdin JSON payload accepts only message fields and cannot set send mode.
Example argv shape for agents/orchestrators:
argv: ["<skill-dir>/scripts/send.mjs", "--json", "--dry-run", "--input-json-stdin"]
stdin JSON: {"to":["you@example.com"],"subject":"Reviewed subject","body":"Approved body text"}
The shell examples below are for simple literal operator commands only. They are not the safe transfer path for arbitrary generated content.
Basic:
node "<skill-dir>/scripts/send.mjs" --to "you@example.com" --subject "Hello" --body "Hi there"
Without --send, this prints a dry-run payload and does not send. Dry-run output includes the full reviewed body JSON plus body byte length and SHA-256 prefix; redact dry-run logs before sharing externally.
With from address override:
node "<skill-dir>/scripts/send.mjs" --from "Example Sender <onboarding@resend.dev>" --to "you@example.com" --subject "Hello" --body "Hi"
HTML body:
node "<skill-dir>/scripts/send.mjs" --html --to "you@example.com" --subject "Styled" --body "<h1>Hi</h1><p>Hello</p>"
Dry run (no send, just print the payload):
node "<skill-dir>/scripts/send.mjs" --dry-run --to "you@example.com" --subject "Test" --body "..."
If --dry-run and --send are both present, dry-run wins and no email is sent.
Real send (only after explicit approval):
node "<skill-dir>/scripts/send.mjs" --send --to "you@example.com" --subject "Weekly report" --body "Approved report text"
All flags
| Flag | Required? | Purpose |
|---|---|---|
--to |
yes unless supplied by --input-json-stdin |
Comma-separated recipient addresses |
--subject |
yes unless supplied by --input-json-stdin |
Message subject |
--body |
yes unless supplied by --input-json-stdin |
Inline message body; use stdin JSON instead for agent-generated or arbitrary text |
--cc |
no | Comma-separated cc |
--bcc |
no | Comma-separated bcc |
--from |
no | Override sender, e.g. "Example Sender <onboarding@resend.dev>" |
--reply-to |
no | Reply-to address |
--html |
no | Body is HTML instead of plain text |
--dry-run |
no | Don't send; print the JSON payload |
--send |
no | Actually send. Without this, the script dry-runs by default |
--json |
no | Print a stable JSON receipt for dry-run or real send |
--input-json-stdin |
no | Read message fields from one JSON object on stdin; preferred for agent-generated/arbitrary text because it avoids shell interpolation |
-h, --help |
no | Show help |
Recipients in --to, --cc, --bcc, and --reply-to must be bare email addresses. Only --from accepts display-name format such as "Reports <reports@example.com>".
--body-file is intentionally not supported in the public package. Review file contents yourself and pass approved text with --body, or for agent workflows pass the exact reviewed message fields through --input-json-stdin without shell interpolation.
Credentials
Requires process environment values:
RESEND_API_KEY- starts withre_...RESEND_ALLOWED_TO- comma-separated recipient allowlist for real sends- Real sends require
RESEND_ALLOWED_TO; without it the script refuses--send - Treat
RESEND_ALLOWED_TOas operator-controlled. Setting or widening it is a separate approval-scoped action, not something an agent should do automatically to satisfy--send.
How to get one:
- Sign up at https://resend.com and check the current pricing/limits for the account
- Go to API Keys in the dashboard
- Click Create API Key, name it, and choose the least-privilege sending permission available for your account
- Copy the key
Export it in the runtime process environment:
$env:RESEND_API_KEY="<your-resend-key>"
$env:RESEND_ALLOWED_TO="you@example.com,reports@example.com"
Sender identity
By default, emails are sent from onboarding@resend.dev - Resend's default sender. This may support quick testing subject to current Resend account restrictions; use a verified domain/sender for production-style mail.
For a custom domain (later, optional):
- Add your domain to Resend at https://resend.com/domains
- Configure DNS records they provide
- Use a verified sender with
--from "Reports <reports@your-verified-domain.example>"
What this skill does
- Reads
RESEND_API_KEYfrom the process environment only - POSTs a JSON request to the fixed endpoint
https://api.resend.com/emailswith redirects disabled - Prints a one-line confirmation with the Resend message ID
- Defaults to dry-run unless
--sendis present - Validates basic recipient address shape before sending
- Enforces
RESEND_ALLOWED_TOfor real sends; fail-closed if it is missing - Prints body byte length and SHA-256 prefix in dry-run so reviewed content can be matched to the send
- Supports
--jsonreceipt output so automation can compare the reviewed body hash to the send receipt and captureresendIdwithout scraping human text
What this skill does NOT do
- Does not read or manage email (this is send-only)
- Does not read local files or support
--body-file - Does not write any files
- Does not intentionally make network calls other than the fixed
https://api.resend.com/emailsrequest; redirects are disabled withredirect: "error" - Does not auto-update
- Does not support attachments in this version
Output
On success:
sent to you@example.com (subject: Hello) - resend-id: c8f43f2a-...
With --json, dry-runs and sends emit parseable JSON. Successful real sends include sent: true, bodySha256, bodySha256Prefix, and resendId.
On failure, clear error on stderr with a non-zero exit code.
Troubleshooting
- "RESEND_API_KEY not set" - create one at https://resend.com, check current pricing/limits, and export
RESEND_API_KEYin the process environment - HTTP 401 - API key is invalid or was revoked
- HTTP 403 - API key doesn't have send permission, or the from address can only send to the account owner until a domain is verified (check dashboard). On Windows, a Node.js cleanup assertion may appear after a 403 exit; this is cosmetic and does not indicate a successful send.
- HTTP 422 - the from address isn't verified on your Resend account (use
onboarding@resend.devor verify your own domain) - HTTP 429 - rate limited; check https://resend.com/pricing or the Resend dashboard for current limits
- HTTP 5xx - Resend/server-side failure or ambiguity; if the request may have reached Resend, check the dashboard before retrying to avoid duplicate sends.
- Network error or timeout - transient, but a network/timeout/read error after the request was sent does not prove the email was not delivered. Check the Resend dashboard before retrying to avoid duplicate sends.
Sample output
Sanitized representative output for eval/review checks:
$ node scripts/send.mjs --to "you@example.com" --subject "Hello" --body "Hi there"
--- DRY RUN: request would be sent ---
note: add --send to perform a real send after explicit approval.
body: 8 bytes, sha256:8328c36d18b7
WARNING: RESEND_ALLOWED_TO is not configured. Real sends will fail closed until an allowlist is set.
POST https://api.resend.com/emails
Authorization: Bearer [redacted]
{
"from": "onboarding@resend.dev",
"to": [
"you@example.com"
],
"subject": "Hello",
"text": "Hi there"
}
$ node scripts/send.mjs --send --to "you@example.com" --subject "Hello" --body "Hi there"
error: RESEND_ALLOWED_TO must be set for real sends. Refusing --send without a recipient allowlist.
# PowerShell:
# Only set or widen RESEND_ALLOWED_TO after explicit approval for that exact allowlist change.
$env:RESEND_ALLOWED_TO="you@example.com"; node scripts/send.mjs --send --to "you@example.com" --subject "Hello" --body "Hi there"
# bash/zsh:
# RESEND_ALLOWED_TO="you@example.com" node scripts/send.mjs --send --to "you@example.com" --subject "Hello" --body "Hi there"
error: RESEND_API_KEY not set in process environment. Create one at https://resend.com, check current pricing/limits, and export RESEND_API_KEY.
Required checks before publishing/updating
Minimum no-send checks:
node --check skills\resend-send-native-node\scripts\send.mjs
node skills\resend-send-native-node\scripts\send.mjs --help
node skills\resend-send-native-node\scripts\send.mjs --to "test@example.com" --subject "Smoke" --body "Hello" --json
node skills\resend-send-native-node\tests\run-resend-send-tests.mjs
The smoke command above must remain a dry-run: do not include --send. Real sends require separate explicit approval for the exact recipient headers, subject, and body plus an allowlist.
Account limits
Resend pricing and free-tier limits can change. Check the current Resend dashboard/pricing page before relying on a specific daily/monthly quota or paid-tier price. New accounts commonly support quick testing from onboarding@resend.dev; use a verified domain/sender for production-style mail.
Changelog
1.0.20: Mirror the operator-controlledRESEND_ALLOWED_TOwarning in CLI help and document 5xx retry ambiguity/support guidance. No send behavior change.1.0.19: MakeRESEND_ALLOWED_TOownership explicit: it is an operator-controlled, approval-scoped guard, and agents must not set or widen it merely to make a send succeed. No send behavior change.1.0.18: Clarify the flags table/help that required message fields may be supplied through--input-json-stdininstead of inline flags, so support handoffs do not steer arbitrary generated/user content through shell interpolation. No send behavior change.1.0.17: Add canonical--input-json-stdinagent invocation path for arbitrary reviewed message fields, document that agents must not shell-interpolate generated email content, add hostile metacharacter stdin regression coverage, and enforce fixed-endpoint no-redirect behavior withredirect: "error". No real-send approval semantics changed.1.0.16: Add committed no-send regression coverage for--dry-run --sendprecedence andcc/bccallowlist blocking before any API-key/fetch path. No send behavior change.1.0.15: Document the actual no-send publish/update checks, including the existingtests\run-resend-send-tests.mjsrunner, so public update gates do not rely on a nonexistent self-test path. No send behavior change.1.0.14: Public package eval hygiene: generate the Resend API-key-shaped test sentinel at runtime so package scanners do not see a static key-shaped string in source; no send behavior change.1.0.13: Source-polish retest prep: genericized a review fixture subject, documented that--dry-run --sendresolves to dry-run, and normalized SKILL.md line endings; no send behavior change.1.0.12: ClawHub publication/version refresh after JSON receipt fix and public-readiness review; no additional runtime behavior change.1.0.11: Add--jsonstructured receipts for dry-run and real send output so operators can capture stable subjects, body hashes, allowlist status, andresendIdwithout scraping human text.1.0.10: Clarify explicit approval must cover all delivery/reply headers (to,cc,bcc,from,reply-to), subject, and body before real sends.1.0.9: Add explicit Node.js 18+ usage prerequisite and offline gate-regression tests for dry-run, allowlist fail-closed, missing-key fail-closed, HTML/reply-to payloads, unsupported body-file, invalid recipients, and help output.1.0.8: Soften public Resend account/default-sender wording to avoid stale pricing/free-tier/domain-setup assumptions.1.0.7: Fix no-allowlist sample output, document bare-recipient requirement and dry-run body visibility, add 30s send timeout, and warn to verify Resend dashboard before retrying ambiguous network/timeout failures.1.0.6: Add frontmatter version metadata, hedge rate-limit wording, document Windows 403 cleanup assertion behavior, and include sanitized dry-run/fail-closed sample outputs for eval review.1.0.5: Public package wording and metadata cleanup; send behavior remains dry-run-first with--sendplus recipient allowlist required for real sends.
