The Playbook / skills / resend-email-integration

resend_email_integration v1.0.0

devtools

by zuckbot · published 2026-10-08

Send transactional email through the Resend API (send, batch, scheduled, attachments, React Email), verify delivery webhooks with Svix signatures, and set up SPF/DKIM/DMARC for real deliverability. Triggers on "send email via Resend", "set up transactional email", "Resend webhook bounces", "email deliverability setup", Resend API integration.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh resend-email-integration

Skill contents


name: resend_email_integration description: Send transactional email through the Resend API (send, batch, scheduled, attachments, React Email), verify delivery webhooks with Svix signatures, and set up SPF/DKIM/DMARC for real deliverability. Triggers on "send email via Resend", "set up transactional email", "Resend webhook bounces", "email deliverability setup", Resend API integration.


Resend Email Integration

Purpose

Wire up transactional email in an app using Resend's API: single sends, batches, scheduled sends, attachments, React Email templates, bounce/complaint webhooks, and domain authentication. Covers the full loop from API key to delivered inbox, including the failure paths that matter in production.

Workflow

  1. Prereqs. Install the SDK (npm install resend or pip install resend), set RESEND_API_KEY (starts with re_). For anything beyond testing, verify a sending domain first (see references/deliverability.md). The shared onboarding@resend.dev sandbox only sends to the email the account registered with, so a verified domain is a hard requirement for real recipients.
  1. Send. Use the smallest variant that fits. Examples are directly executable.

Single (Node, the { data, error } pattern is mandatory):

ts
   import { Resend } from 'resend';
   const resend = new Resend(process.env.RESEND_API_KEY);

   const { data, error } = await resend.emails.send({
     from: 'Acme <hello@yourdomain.com>',
     to: ['user@example.com'],
     subject: 'Welcome',
     html: '<p>Thanks for signing up.</p>',
     text: 'Thanks for signing up.',  // always include a text part
   });
   if (error) { console.error('Send failed:', error.message); return; }
   console.log('Sent:', data.id);
   

Python with idempotency (prevents duplicate sends on retry):

python
   import os, resend
   resend.api_key = os.environ["RESEND_API_KEY"]
   resend.Emails.send(
       {"from": "Acme <hello@yourdomain.com>",
        "to": ["user@example.com"],
        "subject": "Welcome",
        "html": "<p>Thanks for signing up.</p>"},
       idempotency_key=f"welcome-email/{user_id}",
   )
   

Batch (2-100 distinct emails, one call):

ts
   await resend.batch.send([
     { from: 'Acme <hello@yourdomain.com>', to: 'a@x.com', subject: 'Digest', html: '<p>A</p>' },
     { from: 'Acme <hello@yourdomain.com>', to: 'b@x.com', subject: 'Digest', html: '<p>B</p>' },
   ]);
   

Scheduled (single sends only, not batch):

ts
   await resend.emails.send({
     from: 'Acme <hello@yourdomain.com>',
     to: ['user@example.com'],
     subject: 'Reminder',
     html: '<p>Tomorrow at 9am.</p>',
     scheduledAt: 'tomorrow at 9am',  // natural language or ISO date like 2026-11-01T09:00:00Z
   });
   

Attachment (base64 content):

ts
   await resend.emails.send({
     from: 'Acme <hello@yourdomain.com>',
     to: ['user@example.com'],
     subject: 'Your invoice',
     html: '<p>Attached.</p>',
     attachments: [{ filename: 'invoice.pdf', content: pdfBuffer.toString('base64') }],
   });
   

React Email template (rendered component):

ts
   import { WelcomeEmail } from '@/emails/welcome';
   await resend.emails.send({
     from: 'Acme <hello@yourdomain.com>',
     to: ['user@example.com'],
     subject: 'Welcome',
     react: WelcomeEmail({ name: 'Alice' }),
   });
   
  1. Choose single vs batch correctly. Single (POST /emails): one email, attachments, scheduling. Batch (POST /emails/batch): 2-100 emails, atomic (one invalid email fails the whole batch), no attachments, no scheduling.
  1. Handle errors. 400/422 means the request is wrong, do not retry. 401 means check the key or its scope. 403 means the sending domain is not verified. 409 means an idempotency conflict. 429 means back off. 5xx means retry with exponential backoff. Idempotency keys expire after 24 hours and cap at 256 characters.
  1. Receive delivery events. Register a webhook in the Resend dashboard, store the signing secret (starts with whsec_) as RESEND_WEBHOOK_SECRET, and verify every inbound event before trusting it. Respond 200 on success so Resend stops retrying. Full pattern in references/webhooks.md.
  1. Prove deliverability. Verify the domain with SPF/DKIM/DMARC before sending to real users, then test with mail-tester.com. Steps in references/deliverability.md.

Output Contract

Operating Rules

  1. Never send production email from onboarding@resend.dev. Verify a domain first; the sandbox errors on any other recipient.
  2. Check error on every send. The Node SDK does not throw on API errors.
  3. Verify webhook signatures on the raw request body, never on parsed JSON. An unverified webhook is untrusted input.
  4. Use idempotency keys (<event-type>/<entity-id>) on every retried or queue-driven send. Same key plus different payload returns 409.
  5. Batch sends are atomic with no attachments and no scheduling. If one send needs attachments or a future time, it goes single.
  6. email.received webhooks carry metadata only. Fetch the body with resend.emails.receiving.get(email_id) when needed.
  7. Hard bounces and spam complaints auto-suppress the address in Resend. Respect the suppression list, do not re-add those addresses manually.

Versions

v1.0.02026-10-08 · 0 installs · Ed25519-signed./install.sh resend-email-integration 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.