Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/resend

README.md

161 lines · 6.1 KB · Markdown

@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.

  1. 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.dev test sender, which only delivers to your account email).
  2. Save it in Kody:

https://kody.codes/account/secrets/new?name=resend&description=Resend%20API%20key%20(re_)%20for%20transactional%20email%2C%20domains%2C%20audiences%2C%20and%20templates&allowedHosts=api.resend.com&scope=user

  1. 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.

  1. Register a public client (registerOauthClient in this package) or follow Building a Resend OAuth client.
  2. Open the hosted connect flow while signed in, enter the client_id, and approve:

https://kody.codes/connect/oauth?provider=resend&authorizeUrl=https%3A%2F%2Fapi.resend.com%2Foauth%2Fauthorize&tokenUrl=https%3A%2F%2Fapi.resend.com%2Foauth%2Ftoken&scopes=full_access&allowedHosts=api.resend.com&apiBaseUrl=https%3A%2F%2Fapi.resend.com&dashboardUrl=https%3A%2F%2Fresend.com

  1. Pass integration: "resend" on every call. emails:send only covers sending routes; domains, contacts, templates, and logs need full_access.
Multi-account

There are no hard-coded account aliases:

  • Token lane: account: "work" reads secret resend-work, or pass secretName: "resend-work".
  • OAuth lane: pass integration: "resend-work".

https://kody.codes/account/secrets/new?name=resend-work&description=Resend%20API%20key%20for%20the%20work%20account&allowedHosts=api.resend.com&scope=user

Hosts, 401, and 403
StatusMeaningNext step
401Missing or invalid API key / expired OAuth tokenSave resend at the secrets URL, or reconnect OAuth
403 + domainfrom is not on a verified sending domainlistDomains(), add DNS, verifyDomain
403 + scopeOAuth connected with emails:send but a management route was calledReconnect with full_access
429Rate 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: true on any mutation to return { dryRun: true, method, path, body } without contacting Resend.
  • sendEmail, sendBatch, sendBroadcast, cancelScheduledEmail, publishTemplate, and every delete* helper also throw unless confirm: true.
  • Broadcasts are draft-first: createBroadcastDraft never sends.
  • POST helpers accept optional idempotencyKey so 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 to smoke-test); also re-exports every helper below
  • kody:@kody/resend/emails — send, batch, schedule, cancel, sent/received
  • kody:@kody/resend/domains — list, create, verify, tracking, delete
  • kody:@kody/resend/contacts — contacts, segments, topics (audiences)
  • kody:@kody/resend/templates — draft, update, publish, delete
  • kody:@kody/resend/broadcasts — draft-first marketing sends
  • kody:@kody/resend/account — API key metadata, webhooks, logs
  • kody:@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