# Resend

> Resend email API expertise for transactional emails, notifications, and batch sends. Triggers when sending emails, implementing email notifications, password resets, order confirmations, or any file importing from resend. Use for email sending, webhooks, templates, deliverability, and error handling.

- **Type:** Skill
- **Install:** `agentstack add skill-stonegiantstudio-skills-resend`
- **Verified:** Pending review
- **Seller:** [stonegiantstudio](https://agentstack.voostack.com/s/stonegiantstudio)
- **Installs:** 0
- **Category:** [Communication](https://agentstack.voostack.com/c/communication)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [stonegiantstudio](https://github.com/stonegiantstudio)
- **Source:** https://github.com/stonegiantstudio/skills/tree/main/plugins/stone-giant/skills/resend

## Install

```sh
agentstack add skill-stonegiantstudio-skills-resend
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Resend Email API

Resend is the email API for developers. TypeScript-first, excellent DX.

**Always consult [resend.com/docs](https://resend.com/docs) for latest API.**

> **Upstream:** Based on [resend/resend-skills](https://github.com/resend/resend-skills). Check weekly for updates.

## Quick Start

```bash
# Install
 add resend

# Environment
RESEND_API_KEY=re_xxxxxxxxx  # Get from resend.com/api-keys
```

## Decision: Single vs Batch

| Approach | Endpoint | Use Case |
|----------|----------|----------|
| **Single** | `POST /emails` | Individual emails, attachments, scheduled sends |
| **Batch** | `POST /emails/batch` | 2-100 distinct emails, reduce API calls |

**Choose batch when:** 2+ emails, no attachments, no scheduling, reducing API calls matters (rate limit: 2 req/sec default)

**Choose single when:** One email, needs attachments, needs scheduling, different timing per recipient

## Single Email

```typescript
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

// With idempotency key (always use in production)
const { data, error } = await resend.emails.send(
  {
    from: "Acme ",
    to: ["user@example.com"],
    subject: "Welcome to Acme",
    html: "Thanks for signing up!",
  },
  { idempotencyKey: `welcome-email/${userId}` }
);

if (error) {
  console.error("Failed:", error.message);
  return;
}
console.log("Sent:", data.id);
```

### Required Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `from` | string | Sender: `"Name "` |
| `to` | string[] | Recipients (max 50) |
| `subject` | string | Subject line |
| `html` or `text` | string | Body content |

### Optional Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `cc` | string[] | CC recipients |
| `bcc` | string[] | BCC recipients |
| `replyTo` | string[] | Reply-to addresses |
| `scheduledAt` | string | ISO 8601 datetime |
| `attachments` | array | Files (max 40MB total) |
| `tags` | array | Key/value pairs for tracking |
| `headers` | object | Custom headers |

## Batch Email

```typescript
const { data, error } = await resend.batch.send(
  [
    {
      from: "Acme ",
      to: ["user1@example.com"],
      subject: "Order Shipped",
      html: "Your order has shipped!",
    },
    {
      from: "Acme ",
      to: ["user2@example.com"],
      subject: "Order Confirmed",
      html: "Your order is confirmed!",
    },
  ],
  { idempotencyKey: `batch-orders/${batchId}` }
);
```

**Limitations:**
- No attachments (use single sends)
- No scheduling (use single sends)
- Max 100 emails per request
- Atomic: one invalid email fails entire batch

### Large Batches (100+ Emails)

```typescript
function chunkArray(array: T[], size: number): T[][] {
  const chunks: T[][] = [];
  for (let i = 0; i 
    resend.batch.send(chunk, {
      idempotencyKey: `batch-${batchId}/chunk-${index}`,
    })
  )
);
```

## React Router Integration

```typescript
// app/lib/email.server.ts
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWelcomeEmail(user: { email: string; name: string }) {
  const { error } = await resend.emails.send(
    {
      from: "Acme ",
      to: [user.email],
      subject: "Welcome to Acme!",
      html: `Hi ${user.name}, welcome aboard!`,
    },
    { idempotencyKey: `welcome/${user.email}` }
  );

  if (error) {
    console.error("Email failed:", error);
    throw new Error("Failed to send welcome email");
  }
}
```

```typescript
// app/routes/signup.tsx
import { sendWelcomeEmail } from "~/lib/email.server";

export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const user = await createUser(formData);

  // Fire and forget (or await if critical)
  sendWelcomeEmail(user).catch(console.error);

  return redirect("/dashboard");
}
```

## Idempotency Keys

**Always use in production** - prevents duplicate sends on retry.

| Format | Example |
|--------|---------|
| Single | `/` → `welcome-email/user-123` |
| Batch | `batch-/` → `batch-orders/batch-456` |

- Expires after 24 hours
- Max 256 characters
- Same key + same payload = returns original response (no resend)
- Same key + different payload = 409 error

## Error Handling

| Code | Action |
|------|--------|
| 400, 422 | Fix request, don't retry |
| 401, 403 | Check API key / domain, don't retry |
| 409 | Idempotency conflict - new key or fix payload |
| 429 | Rate limited - exponential backoff |
| 500 | Server error - exponential backoff |

```typescript
async function sendWithRetry(
  emailFn: () => Promise,
  maxRetries = 3
) {
  for (let attempt = 0; attempt  setTimeout(r, delay));
  }

  return { data: null, error: new Error("Max retries exceeded") };
}
```

## Webhooks

Track delivery status in real-time.

| Event | Use Case |
|-------|----------|
| `email.delivered` | Confirm delivery |
| `email.bounced` | Remove from list, alert user |
| `email.complained` | Auto-unsubscribe (spam complaint) |
| `email.opened` | Track engagement (marketing only) |
| `email.clicked` | Track engagement (marketing only) |

**Always verify signatures:**

```typescript
import { Webhook } from "resend";

export async function action({ request }: Route.ActionArgs) {
  const payload = await request.text();
  const headers = Object.fromEntries(request.headers);

  const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET!);

  try {
    const event = webhook.verify(payload, headers);

    switch (event.type) {
      case "email.bounced":
        await handleBounce(event.data);
        break;
      case "email.complained":
        await handleComplaint(event.data);
        break;
    }

    return new Response("OK", { status: 200 });
  } catch {
    return new Response("Invalid signature", { status: 401 });
  }
}
```

## Templates

```typescript
const { data, error } = await resend.emails.send({
  from: "Acme ",
  to: ["user@example.com"],
  subject: "Welcome!",
  template: {
    id: "tmpl_abc123",
    variables: {
      USER_NAME: "John",      // Case-sensitive!
      ORDER_TOTAL: "$99.00",
    },
  },
});
```

**Important:**
- Variable names are **case-sensitive** (`USER_NAME` ≠ `user_name`)
- Max 20 variables per template
- Must be **published** in dashboard (drafts don't work)
- Can't combine with `html`, `text`, or `react` params

## Tags

```typescript
tags: [
  { name: "user_id", value: "usr_123" },
  { name: "email_type", value: "welcome" },
  { name: "plan", value: "enterprise" },
]
```

Use for: filtering in dashboard, correlating webhooks, analytics.

## Testing

**Never use fake addresses at real providers** (test@gmail.com destroys reputation).

| Address | Result |
|---------|--------|
| `delivered@resend.dev` | Simulates success |
| `bounced@resend.dev` | Simulates hard bounce |
| `complained@resend.dev` | Simulates spam complaint |

## Deliverability Checklist

**Required:**
- [ ] Valid SPF, DKIM, DMARC records
- [ ] Links match sending domain (send from @acme.com → link to acme.com)
- [ ] Include plain text version (or let Resend auto-generate)
- [ ] Avoid "no-reply" addresses (use support@, hello@)
- [ ] Body under 102KB (Gmail clips larger)

**Recommended:**
- [ ] Use subdomains (notifications.acme.com for transactional)
- [ ] Disable tracking for transactional emails (password resets, receipts)

## Domain Warm-up (New Domains)

| Day | Max Emails | Max/Hour |
|-----|------------|----------|
| 1 | 150 | - |
| 2 | 250 | - |
| 3 | 400 | - |
| 4 | 700 | 50 |
| 5 | 1,000 | 75 |
| 6 | 1,500 | 100 |
| 7 | 2,000 | 150 |

**Monitor:** Bounce rate < 4%, Spam complaints < 0.08%

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Retrying without idempotency key | Always include - prevents duplicates |
| Batch with attachments | Use single sends for attachments |
| Retrying 400/422 errors | Fix request, don't retry |
| Tracking on transactional | Disable for password resets, receipts |
| Using "no-reply" sender | Use real address (support@) |
| Not verifying webhook signatures | Always verify - security critical |
| Testing with fake emails | Use `@resend.dev` test addresses |
| High volume from new domain | Warm up gradually |

## Resources

- [Documentation](https://resend.com/docs)
- [API Reference](https://resend.com/docs/api-reference)
- [React Email](https://react.email) - Component library for emails
- [Official Skills](https://github.com/resend/resend-skills) - Upstream source

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [stonegiantstudio](https://github.com/stonegiantstudio)
- **Source:** [stonegiantstudio/skills](https://github.com/stonegiantstudio/skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-stonegiantstudio-skills-resend
- Seller: https://agentstack.voostack.com/s/stonegiantstudio
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
