READ-ONLY PACKAGE PREVIEW

resend/references/sending/single-email-examples.md

Version edbfece3f402.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.

← Return to resource and package checksum

Resend Examples

Table of Contents

Idempotency Keys

Use idempotency keys to prevent duplicate emails when retrying failed requests. Keys expire after 24 hours and have a max length of 256 characters.

Key format: Use <event-type>/<entity-id> pattern (e.g., welcome-email/user-123, order-confirmation/order-456).

Behavior: If same key is sent with a different payload, Resend returns a 409 error. If same key with same payload, returns the original response without resending.

Node.js

The Node.js SDK has a dedicated idempotencyKey option:

import { Resend } from 'resend';

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

const { data, error } = await resend.emails.send(
  {
    from: 'Acme <[email protected]>',
    to: ['[email protected]'],
    subject: 'Order Confirmation',
    html: '<p>Your order has been confirmed.</p>',
  },
  {
    idempotencyKey: `order-confirmation/${orderId}`,
  }
);

Python

import resend
import os

resend.api_key = os.environ["RESEND_API_KEY"]

email = resend.Emails.send({
    "from": "Acme <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Order Confirmation",
    "html": "<p>Your order has been confirmed.</p>",
}, idempotency_key=f"order-confirmation/{order_id}")

Go

Other SDKs use the Idempotency-Key header:

import "github.com/resend/resend-go/v3"

client := resend.NewClient(os.Getenv("RESEND_API_KEY"))

params := &resend.SendEmailRequest{
    From:    "Acme <[email protected]>",
    To:      []string{"[email protected]"},
    Subject: "Order Confirmation",
    Html:    "<p>Your order has been confirmed.</p>",
    Headers: map[string]string{
        "Idempotency-Key": fmt.Sprintf("order-confirmation/%s", orderID),
    },
}

sent, err := client.Emails.Send(params)

cURL

curl -X POST 'https://api.resend.com/emails' \
  -H 'Authorization: Bearer re_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-confirmation/12345' \
  -d '{
    "from": "Acme <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Order Confirmation",
    "html": "<p>Your order has been confirmed.</p>"
  }'

Error Handling

Common Error Codes

Code Description Action
400 Bad request (invalid params) Fix request parameters
400 invalid_idempotency_key Key must be 1-256 characters
401 Invalid API key Check RESEND_API_KEY
403 Domain not verified Verify domain at resend.com/domains
409 invalid_idempotent_request Key already used with different payload
409 concurrent_idempotent_requests Same key request in progress, retry later
422 Unprocessable entity Check email format/content
429 Rate limited Implement backoff and retry
500 Server error Retry with backoff

Node.js

import { Resend } from 'resend';

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

const { data, error } = await resend.emails.send({
  from: 'Acme <[email protected]>',
  to: ['[email protected]'],
  subject: 'Hello',
  html: '<p>Hello world</p>',
});

if (error) {
  console.error('Failed to send email:', error.message);

  // Handle specific error types
  if (error.name === 'validation_error') {
    // Invalid parameters - don't retry
    throw new Error(`Invalid email params: ${error.message}`);
  }

  if (error.name === 'rate_limit_exceeded') {
    // Rate limited - retry after delay
    console.log('Rate limited, retrying...');
  }

  return;
}

console.log('Email sent:', data.id);

Python

import resend
import os

resend.api_key = os.environ["RESEND_API_KEY"]

try:
    email = resend.Emails.send({
        "from": "Acme <[email protected]>",
        "to": ["[email protected]"],
        "subject": "Hello",
        "html": "<p>Hello world</p>",
    })
    print(f"Email sent: {email['id']}")
except resend.exceptions.ValidationError as e:
    # Invalid parameters - don't retry
    print(f"Validation error: {e}")
except resend.exceptions.RateLimitError as e:
    # Rate limited - retry after delay
    print(f"Rate limited: {e}")
except resend.exceptions.ResendError as e:
    # Other API error
    print(f"API error: {e}")

Go

import (
    "fmt"
    "github.com/resend/resend-go/v3"
)

client := resend.NewClient(os.Getenv("RESEND_API_KEY"))

params := &resend.SendEmailRequest{
    From:    "Acme <[email protected]>",
    To:      []string{"[email protected]"},
    Subject: "Hello",
    Html:    "<p>Hello world</p>",
}

sent, err := client.Emails.Send(params)
if err != nil {
    // Check error type and handle accordingly
    fmt.Printf("Failed to send email: %v\n", err)
    return
}

fmt.Printf("Email sent: %s\n", sent.Id)

Retry Logic

Implement exponential backoff for transient failures (rate limits, server errors). Don't retry 409 invalid_idempotent_request errors (different payload).

Node.js

import { Resend } from 'resend';

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

async function sendEmailWithRetry(
  params: Parameters<typeof resend.emails.send>[0],
  options: { maxRetries?: number; idempotencyKey?: string } = {}
) {
  const { maxRetries = 3, idempotencyKey } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const { data, error } = await resend.emails.send(
      params,
      idempotencyKey ? { idempotencyKey } : undefined
    );

    if (!error) {
      return data;
    }

    // Don't retry validation errors or idempotency conflicts
    if (error.name === 'validation_error' || error.name === 'invalid_idempotent_request') {
      throw new Error(`${error.name}: ${error.message}`);
    }

    // Last attempt failed
    if (attempt === maxRetries) {
      throw new Error(`Failed after ${maxRetries + 1} attempts: ${error.message}`);
    }

    // Exponential backoff: 1s, 2s, 4s...
    const delay = Math.pow(2, attempt) * 1000;
    console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }
}

// Usage
const result = await sendEmailWithRetry(
  {
    from: 'Acme <[email protected]>',
    to: ['[email protected]'],
    subject: 'Order Confirmation',
    html: '<p>Your order is confirmed.</p>',
  },
  { idempotencyKey: `order-confirmation/${orderId}` }
);

Python

import resend
import os
import time

resend.api_key = os.environ["RESEND_API_KEY"]

def send_email_with_retry(params, max_retries=3, idempotency_key=None):
    for attempt in range(max_retries + 1):
        try:
            return resend.Emails.send(params, idempotency_key=idempotency_key)
        except resend.exceptions.ValidationError:
            # Don't retry validation errors
            raise
        except resend.exceptions.ResendError as e:
            if attempt == max_retries:
                raise Exception(f"Failed after {max_retries + 1} attempts: {e}")

            # Exponential backoff: 1s, 2s, 4s...
            delay = (2 ** attempt)
            print(f"Attempt {attempt + 1} failed, retrying in {delay}s...")
            time.sleep(delay)

# Usage
result = send_email_with_retry(
    {
        "from": "Acme <[email protected]>",
        "to": ["[email protected]"],
        "subject": "Order Confirmation",
        "html": "<p>Your order is confirmed.</p>",
    },
    idempotency_key=f"order-confirmation/{order_id}"
)

Go

import (
    "fmt"
    "time"
    "github.com/resend/resend-go/v3"
)

func sendEmailWithRetry(client *resend.Client, params *resend.SendEmailRequest, maxRetries int) (*resend.SendEmailResponse, error) {
    var lastErr error

    for attempt := 0; attempt <= maxRetries; attempt++ {
        sent, err := client.Emails.Send(params)
        if err == nil {
            return sent, nil
        }

        lastErr = err

        if attempt == maxRetries {
            break
        }

        // Exponential backoff: 1s, 2s, 4s...
        delay := time.Duration(1<<attempt) * time.Second
        fmt.Printf("Attempt %d failed, retrying in %v...\n", attempt+1, delay)
        time.Sleep(delay)
    }

    return nil, fmt.Errorf("failed after %d attempts: %w", maxRetries+1, lastErr)
}

// Usage
client := resend.NewClient(os.Getenv("RESEND_API_KEY"))

params := &resend.SendEmailRequest{
    From:    "Acme <[email protected]>",
    To:      []string{"[email protected]"},
    Subject: "Order Confirmation",
    Html:    "<p>Your order is confirmed.</p>",
    Headers: map[string]string{
        "Idempotency-Key": fmt.Sprintf("order-confirmation/%s", orderID),
    },
}

sent, err := sendEmailWithRetry(client, params, 3)

Complete Examples

Node.js - Production-Ready Email Service

import { Resend } from 'resend';
import { randomUUID } from 'crypto';

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

interface SendEmailOptions {
  to: string | string[];
  subject: string;
  html: string;
  from?: string;
  replyTo?: string;
  idempotencyKey?: string;
  maxRetries?: number;
}

async function sendEmail(options: SendEmailOptions) {
  const {
    to,
    subject,
    html,
    from = 'Acme <[email protected]>',
    replyTo,
    idempotencyKey = randomUUID(),
    maxRetries = 3,
  } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const { data, error } = await resend.emails.send(
      {
        from,
        to: Array.isArray(to) ? to : [to],
        subject,
        html,
        ...(replyTo && { replyTo }),
      },
      { idempotencyKey }
    );

    if (!error) {
      return { success: true, id: data.id };
    }

    // Don't retry client errors or idempotency conflicts
    if (error.name === 'validation_error' || error.name === 'not_found' || error.name === 'invalid_idempotent_request') {
      return { success: false, error: error.message, retryable: false };
    }

    if (attempt < maxRetries) {
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(r => setTimeout(r, delay));
    }
  }

  return { success: false, error: 'Max retries exceeded', retryable: true };
}

// Usage
const result = await sendEmail({
  to: '[email protected]',
  subject: 'Welcome!',
  html: '<h1>Welcome to Acme</h1>',
  idempotencyKey: `welcome-email/${userId}`,
});

Python - Production-Ready Email Service

import resend
import os
import time
import uuid
from dataclasses import dataclass
from typing import Optional, Union, List

resend.api_key = os.environ["RESEND_API_KEY"]

@dataclass
class EmailResult:
    success: bool
    id: Optional[str] = None
    error: Optional[str] = None
    retryable: bool = False

def send_email(
    to: Union[str, List[str]],
    subject: str,
    html: str,
    from_addr: str = "Acme <[email protected]>",
    reply_to: Optional[str] = None,
    idempotency_key: Optional[str] = None,
    max_retries: int = 3,
) -> EmailResult:
    idempotency_key = idempotency_key or str(uuid.uuid4())
    recipients = [to] if isinstance(to, str) else to

    params = {
        "from": from_addr,
        "to": recipients,
        "subject": subject,
        "html": html,
    }
    if reply_to:
        params["reply_to"] = reply_to

    for attempt in range(max_retries + 1):
        try:
            result = resend.Emails.send(params, idempotency_key=idempotency_key)
            return EmailResult(success=True, id=result["id"])
        except resend.exceptions.ValidationError as e:
            return EmailResult(success=False, error=str(e), retryable=False)
        except resend.exceptions.ResendError as e:
            if attempt < max_retries:
                time.sleep(2 ** attempt)
            else:
                return EmailResult(success=False, error=str(e), retryable=True)

    return EmailResult(success=False, error="Max retries exceeded", retryable=True)

# Usage
result = send_email(
    to="[email protected]",
    subject="Welcome!",
    html="<h1>Welcome to Acme</h1>",
    idempotency_key=f"welcome-email/{user_id}",
)