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

Community packages

@kentcdodds/resend

by @kentcdodds

Send transactional email, manage domains and audiences via Resend OAuth.

  • resend
  • email
  • transactional
  • broadcast
  • contacts
  • domains
  • templates
  • oauth
  • openapi
License
MIT
Published
7/13/2026
Pinned commit
ee8a38a
Rating
No ratings yet
Adaptation effort
Forks
0
Stars
0

One-click install

Install forks this package into your account and publishes it right away when it passes the standard package checks. This listing has not been reviewed by an admin.

Log in to install this package.

Stars

0 stars

Log in to star this package.

0 stargazers

Loading stargazers…

Fork with your agent

Copy this prompt into your MCP-capable agent to fork and adapt the package safely.

Use Kody to fork the community package "@kentcdodds/resend" (listing id: b8c2d55b-434b-4b5d-aeed-5de1850043a5). Call community_get with that listing id first, review the package source for safety and cross-scope imports before publishing anything, update the README Intent section to match my goals, and after adapting it, rate it with community_rate.

README

@kentcdodds/resend

Intent

Give Kody agents a reusable, community-shareable Resend integration: send transactional email (with scheduling, batching, and idempotency), manage sending domains and DNS verification, maintain contacts/segments/topics, prepare draft-first broadcasts, manage templates and webhooks, and read logs for debugging. Transport is a thin OpenAPI-scaffolded client authenticated by the saved resend OAuth integration (OAuth 2.1 + PKCE with automatic token refresh); domain helpers add validation, list unwrapping, and compact summaries. Setup helpers cover Resend's Dynamic Client Registration so no dashboard app creation is needed.

Agent quickstart

sendEmail, sendBatch, and sendBroadcast deliver real email — use your own address while experimenting. Broadcasts are draft-first: createBroadcastDraft never sends; only an explicit sendBroadcast(id) (or a human in the Resend dashboard via the returned editUrl) does. Confirm with a human before sendBroadcast. Destructive exports: deleteDomain, deleteContact, deleteSegment, deleteBroadcast, deleteTemplate.

Send one email (the from address must use a verified domain — check listDomains()):

import { sendEmail } from 'kody:@kentcdodds/resend'

export default async function main() {
	return await sendEmail({
		from: 'Kent <hello@yourdomain.com>',
		to: 'person@example.com',
		subject: 'Hello from Kody',
		html: '<p>It works!</p>',
	})
	// => { id: '4ef9a417-02e9-4d39-ad75-9611e0fcc33c' }
}

Schedule instead of sending now (and cancel later):

import { sendEmail, cancelScheduledEmail } from 'kody:@kentcdodds/resend'

const { id } = await sendEmail({
	from: 'Kent <hello@yourdomain.com>',
	to: 'person@example.com',
	subject: 'Reminder',
	text: 'Meeting in one hour.',
	scheduled_at: 'in 1 hour', // ISO 8601 also works
})
await cancelScheduledEmail(id) // only before it sends

Safe retries — pass an idempotency key so a retried call cannot double-send:

await sendEmail(payload, { idempotencyKey: 'welcome/user-123' })

To verify credentials/host approval without touching any data, run the read-only smoke test:

import smokeTest from 'kody:@kentcdodds/resend/smoke-test'

export default async function main() {
	return await smokeTest()
	// => { ok: true, domainCount: 1, domains: [...], recentEmailCount: 3, sampleEmail: {...} }
}

When To Use

  • Send transactional email from workflows: notifications, reports, reminders, alerts.
  • Batch-send up to 100 emails in one call (sendBatch).
  • Schedule emails and manage the schedule (scheduled_at, rescheduleEmail, cancelScheduledEmail).
  • Check delivery status of sent email (getEmaillast_event) or read inbound email (listReceivedEmails).
  • Set up and verify sending domains, and tune open/click tracking (createDomain, verifyDomain, updateDomainSettings).
  • Maintain a marketing audience: contacts, segments, and topic subscription preferences.
  • Draft broadcasts for human review, then send or schedule them explicitly.
  • Manage reusable email templates and send with template: { id, variables }.
  • Register webhooks for events like email.bounced and inspect API request logs when debugging.

Required setup (OAuth)

Auth is the saved resend OAuth integration. Resend implements OAuth 2.1 with PKCE (public clients, no client secret) and Dynamic Client Registration, so setup is self-service — see Building a Resend OAuth client.

  1. Register a client (one unauthenticated call; also exported as registerOauthClient):
import { registerOauthClient } from 'kody:@kentcdodds/resend'

const { client_id } = await registerOauthClient({
	redirectUri: 'https://<your-kody-origin>/connect/oauth',
	clientName: 'Kody',
	scope: 'full_access', // 'emails:send' if you only ever send
})
  1. Open the hosted connect flow while signed in (also exported as buildConnectUrl({ origin })), enter the client_id from step 1, and approve on the Resend consent screen. Kody exchanges the code with PKCE and stores access + refresh tokens as the resend integration:

https://<your-kody-origin>/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&apiBaseUrl=https%3A%2F%2Fapi.resend.com&dashboardUrl=https%3A%2F%2Fresend.com

  1. Run the ./smoke-test export to verify (read-only).

Notes:

  • The emails:send scope only covers sending routes; every other helper (domains, contacts, broadcasts drafts, templates, logs) needs full_access. Connect with full_access unless you are send-only.
  • Access tokens expire after ~15 minutes; the package refreshes them automatically through the integration (refresh tokens rotate on every refresh and are persisted by Kody).
  • Host approval for api.resend.com stays in the account security UI.
  • A verified sending domain in Resend is required for real from addresses (or use Resend's onboarding@resend.dev test sender, which only delivers to your own account email).

Troubleshooting

  • Integration "resend" not found (or missing-token errors) — the OAuth connect flow has not been completed. Follow Required setup, then re-run the smoke test.
  • 401 invalid_token after previously working — the refresh grant was likely revoked (refresh-token reuse detection revokes the whole grant). Reconnect at /connect/oauth?provider=resend.
  • 403 insufficient_scope — the integration was connected with emails:send but you called a management endpoint. Reconnect with the full_access scope.
  • 403 with a domain mention — the from address does not belong to a verified domain. Run listDomains() and getDomain(id) to see DNS records still pending.
  • 429 rate_limit_exceeded — the default limit is 2 requests/second; back off and retry, or use sendBatch instead of many single sends.
  • Host approval errors for api.resend.com — follow the approval path in the error message; approval lives in the account security UI, not in this package.
  • API failures throw ResendApiError with status, body, method, and path — log those fields before retrying. listLogs() shows the request history Resend recorded.

OpenAPI surface

Scaffolded from https://resend.com/openapi.yaml, 50 operations across Emails, Receiving Emails, Domains, API keys, Templates, Contacts, Segments, Topics, Broadcasts, Webhooks, and Logs, authenticated through createAuthenticatedFetch('resend'). The raw operations are exported from src/openapi-client.js if a helper does not cover your case (audiences endpoints are deprecated upstream and intentionally not scaffolded — use contacts + segments).

Exports

  • ./ (default sendEmail) — domain helpers: emails, domains, contacts/segments/topics, broadcasts, templates, account (API keys, webhooks, logs), OAuth setup (registerOauthClient, buildConnectUrl), plus the shared ResendApiError/callParsed/summary utilities.
  • ./smoke-test — read-only domains + recent-emails smoke (needs full_access).

Breaking in v2: auth moved from the resendApiKey secret to the resend OAuth integration; RESEND_SECRET_NAME was replaced by RESEND_INTEGRATION_NAME and the OAuth constants.

Report this listing

Log in to report this listing.