Send transactional email and manage Resend domains, audiences, and templates with dry-run mutations.
- Integrations
- resend
- transactional
- domains
- contacts
- audiences
- templates
- broadcasts
- License
- MIT
- Published
- August 22, 2026
- Pinned commit
9cfdfb6- Rating
- No ratings yet
- Forks
- 0
- Stars
- 0
- Adaptation effort
- —
README
@kody/resend
Intent
Reusable Resend helpers so Kody agents can send transactional email and manage
sending domains, audiences/contacts, templates, and draft-first broadcasts on
the caller's Resend account — not a shared platform inbox. Auth is a saved
API key by default, or an optional OAuth integration. Reads are free-form;
mutations support dryRun: true, and sends or deletes also require
confirm: true.
This listing is meant to be forked. After you fork, save your own resend
credential and call the helpers in your account.
Agent setup
Default auth is a Resend API key (secret-backed). There is no built-in platform OAuth app. Do not paste the key into chat.
- Create an API key at resend.com/api-keys.
Prefer a key limited to the permissions you need. Sending also needs a
verified domain (or Resend's
onboarding@resend.devtest sender, which only delivers to your account email). - Save it in Kody:
- In the account secrets UI, approve host
api.resend.com. Saving the secret does not approve hosts by itself.
Optional OAuth
Resend supports OAuth 2.1 + PKCE with Dynamic Client Registration — no
dashboard app is required. Redirect URI must be exactly
https://kody.codes/connect/oauth.
- Register a public client (
registerOauthClientin this package) or follow Building a Resend OAuth client. - Open the hosted connect flow while signed in, enter the
client_id, and approve:
- Pass
integration: "resend"on every call.emails:sendonly covers sending routes; domains, contacts, templates, and logs needfull_access.
Multi-account
There are no hard-coded account aliases:
- Token lane:
account: "work"reads secretresend-work, or passsecretName: "resend-work". - OAuth lane: pass
integration: "resend-work".
Hosts, 401, and 403
| Status | Meaning | Next step |
|---|---|---|
| 401 | Missing or invalid API key / expired OAuth token | Save resend at the secrets URL, or reconnect OAuth |
| 403 + domain | from is not on a verified sending domain | listDomains(), add DNS, verifyDomain |
| 403 + scope | OAuth connected with emails:send but a management route was called | Reconnect with full_access |
| 429 | Rate limit (default 2 req/s) | Back off, or use sendBatch |
Approve api.resend.com on the secret or integration. Host approval lives
in the account security UI.
Mutation safety
- Pass
dryRun: trueon any mutation to return{ dryRun: true, method, path, body }without contacting Resend. sendEmail,sendBatch,sendBroadcast,cancelScheduledEmail,publishTemplate, and everydelete*helper also throw unlessconfirm: true.- Broadcasts are draft-first:
createBroadcastDraftnever sends. - POST helpers accept optional
idempotencyKeyso a retried send cannot double-deliver.
import { sendEmail } from 'kody:@kody/resend/emails'
const preview = await sendEmail({
from: 'Name <hello@yourdomain.com>',
to: 'person@example.com',
subject: 'Hello from Kody',
html: '<p>It works!</p>',
dryRun: true,
})
const sent = await sendEmail({
from: 'Name <hello@yourdomain.com>',
to: 'person@example.com',
subject: 'Hello from Kody',
html: '<p>It works!</p>',
confirm: true,
idempotencyKey: 'welcome/user-123',
})Exports
kody:@kody/resend— action dispatcher (defaults tosmoke-test); also re-exports every helper belowkody:@kody/resend/emails— send, batch, schedule, cancel, sent/receivedkody:@kody/resend/domains— list, create, verify, tracking, deletekody:@kody/resend/contacts— contacts, segments, topics (audiences)kody:@kody/resend/templates— draft, update, publish, deletekody:@kody/resend/broadcasts— draft-first marketing sendskody:@kody/resend/account— API key metadata, webhooks, logskody:@kody/resend/smoke-test— dry-run self-check, then a live domain and recent-email read when credentials exist
Low-level transport (resendRequest, resendCall, ResendApiError) stays
pinned to https://api.resend.com. resendCall writes default to dry-run
until you pass dryRun: false. Deprecated /audiences routes are not
wrapped — use contacts + segments.
This package declares no package-owned jobs.
Smoke test
import { smokeTest } from 'kody:@kody/resend'
export default async function main() {
return await smokeTest()
}Without a resend secret or OAuth integration this still returns
{ ok: true, live: false } plus the setup URLs. With credentials it lists
domains and one page of recent sent email — no writes.
Import the official package as kody:@kody/resend. Do not invoke a personal
resend kody id; that can resolve a different account package.
Example
import resend from 'kody:@kody/resend'
export default async function main() {
return await resend({ action: 'list-domains' })
}Branding
community-icon.svg uses Resend's official lettermark path from
cdn.resend.com/brand/resend-icon-white.svg
on black. The path is unmodified. Resend® is a trademark of Resend. This
package is not affiliated with or endorsed by Resend.
Docs
Report this listing
Log in to report this listing.