Skip to main content
The Resend Go SDK allows you to send multiple emails in a single batch request with support for strict or permissive validation modes.

Quick Start

Here’s how to send a batch of emails:
package main

import (
	"context"
	"fmt"
	"os"

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

func main() {
	ctx := context.Background()
	apiKey := os.Getenv("RESEND_API_KEY")

	client := resend.NewClient(apiKey)

	// Create batch of emails
	batchEmails := []*resend.SendEmailRequest{
		{
			To:      []string{"[email protected]"},
			From:    "[email protected]",
			Subject: "Welcome!",
			Text:    "Welcome to our platform",
		},
		{
			To:      []string{"[email protected]"},
			From:    "[email protected]",
			Subject: "Hello!",
			Text:    "Thanks for joining",
		},
	}

	// Send batch
	sent, err := client.Batch.SendWithContext(ctx, batchEmails)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Sent %d emails\n", len(sent.Data))
	for _, email := range sent.Data {
		fmt.Printf("Email ID: %s\n", email.Id)
	}
}

Batch Send Methods

The SDK provides three methods for sending batch emails:
// Simple send without context
sent, err := client.Batch.Send(batchEmails)
if err != nil {
	panic(err)
}
Source: batch.go:73

Batch Validation Modes

The SDK supports two validation modes for batch email sending:

Strict Mode (Default)

In strict mode, the entire batch fails if any email has validation errors:
ctx := context.Background()

batchEmails := []*resend.SendEmailRequest{
	{
		To:      []string{"[email protected]"},
		From:    "[email protected]",
		Subject: "Valid email",
		Text:    "This is valid",
	},
	{
		To:      []string{}, // Invalid - missing recipient
		From:    "[email protected]",
		Subject: "Invalid email",
		Text:    "This will cause the entire batch to fail",
	},
}

// Strict mode is the default
options := &resend.BatchSendEmailOptions{
	BatchValidation: resend.BatchValidationStrict,
}

sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)
if err != nil {
	// Entire batch failed - no emails sent
	fmt.Printf("Batch failed: %v\n", err)
}
Source: batch.go:13
Use strict mode when all emails in the batch must be sent together (all-or-nothing behavior).

Permissive Mode

In permissive mode, valid emails are sent even if some emails in the batch have errors:
ctx := context.Background()

batchEmails := []*resend.SendEmailRequest{
	{
		To:      []string{"[email protected]"},
		From:    "[email protected]",
		Subject: "Valid email",
		Text:    "This email is valid",
	},
	{
		To:      []string{}, // Missing 'to' field - will fail
		From:    "[email protected]",
		Subject: "Invalid email",
		Text:    "This email has no recipient",
	},
	{
		To:      []string{"[email protected]"},
		From:    "[email protected]",
		Subject: "Another valid email",
		Text:    "This email is also valid",
	},
}

options := &resend.BatchSendEmailOptions{
	BatchValidation: resend.BatchValidationPermissive,
}

sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)
if err != nil {
	panic(err)
}

fmt.Printf("Successfully sent %d emails\n", len(sent.Data))
for _, email := range sent.Data {
	fmt.Printf("  - Email ID: %s\n", email.Id)
}

if len(sent.Errors) > 0 {
	fmt.Printf("Failed to send %d emails:\n", len(sent.Errors))
	for _, batchErr := range sent.Errors {
		fmt.Printf("  - Index %d: %s\n", batchErr.Index, batchErr.Message)
	}
}
Source: examples/send_batch_email.go:53
Use permissive mode when you want partial success - valid emails will be sent regardless of invalid ones in the batch.
Source: batch.go:16

BatchSendEmailOptions

The BatchSendEmailOptions struct supports the following fields:
idempotencyKey
string
Unique key to prevent duplicate batch sends. Valid for 24 hours.
batchValidation
BatchValidationMode
Validation mode: BatchValidationStrict (default) or BatchValidationPermissive.
  • Strict: All emails must be valid or entire batch fails
  • Permissive: Valid emails are sent, invalid ones are reported in errors
Source: batch.go:29

BatchEmailResponse

The response from a batch send includes:
data
[]SendEmailResponse
Array of successfully sent emails with their IDs.
errors
[]BatchError
Array of errors for emails that failed validation (only in permissive mode).
Source: batch.go:58

Handling BatchErrors

In permissive mode, failed emails are reported in the Errors field:
sent, err := client.Batch.SendWithOptions(ctx, batchEmails, &resend.BatchSendEmailOptions{
	BatchValidation: resend.BatchValidationPermissive,
})
if err != nil {
	panic(err)
}

// Check for partial failures
if len(sent.Errors) > 0 {
	fmt.Printf("Warning: %d emails failed to send:\n", len(sent.Errors))
	
	for _, batchErr := range sent.Errors {
		// Index corresponds to position in original batch array
		failedEmail := batchEmails[batchErr.Index]
		
		fmt.Printf("Failed email to %v:\n", failedEmail.To)
		fmt.Printf("  Error: %s\n", batchErr.Message)
		fmt.Printf("  Index: %d\n", batchErr.Index)
	}
}

// Process successful emails
fmt.Printf("Successfully sent %d emails\n", len(sent.Data))

BatchError Struct

index
int
Zero-based index of the failed email in the original batch array.
message
string
Error message describing why the email failed validation.
Source: batch.go:51

Using Idempotency Keys

Prevent duplicate batch sends with idempotency keys:
ctx := context.Background()

batchEmails := []*resend.SendEmailRequest{
	{
		To:      []string{"[email protected]"},
		From:    "[email protected]",
		Subject: "Order Confirmation",
		Text:    "Your order has been confirmed.",
	},
}

options := &resend.BatchSendEmailOptions{
	IdempotencyKey: "order-batch-12345",
}

// First request sends the emails
sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)
if err != nil {
	panic(err)
}

// Second request with same key returns the same response without sending duplicates
sent2, err := client.Batch.SendWithOptions(ctx, batchEmails, options)
// sent2 will have the same email IDs as sent
Source: examples/send_batch_email.go:42
Idempotency keys are valid for 24 hours. After that, the same key can be reused.

Common Use Cases

// Send welcome emails to new users
var batchEmails []*resend.SendEmailRequest

for _, user := range newUsers {
    batchEmails = append(batchEmails, &resend.SendEmailRequest{
        To:      []string{user.Email},
        From:    "[email protected]",
        Subject: fmt.Sprintf("Welcome %s!", user.Name),
        Html:    fmt.Sprintf("<h1>Welcome %s!</h1><p>Thanks for joining.</p>", user.Name),
        Tags: []resend.Tag{
            {Name: "campaign", Value: "welcome"},
            {Name: "user_id", Value: user.ID},
        },
    })
}

// Use permissive mode to ensure valid emails are sent
options := &resend.BatchSendEmailOptions{
    BatchValidation: resend.BatchValidationPermissive,
    IdempotencyKey:  fmt.Sprintf("welcome-batch-%s", time.Now().Format("2006-01-02")),
}

sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)
// Send notification to all subscribers
var batchEmails []*resend.SendEmailRequest

for _, subscriber := range subscribers {
    batchEmails = append(batchEmails, &resend.SendEmailRequest{
        To:      []string{subscriber.Email},
        From:    "[email protected]",
        Subject: "New Feature Released!",
        Html:    "<p>We just released an exciting new feature...</p>",
        Tags: []resend.Tag{
            {Name: "type", Value: "notification"},
            {Name: "subscriber_id", Value: subscriber.ID},
        },
    })
}

// Use strict mode to ensure all or nothing
sent, err := client.Batch.SendWithContext(ctx, batchEmails)
// Send order confirmations using template
var batchEmails []*resend.SendEmailRequest

for _, order := range completedOrders {
    batchEmails = append(batchEmails, &resend.SendEmailRequest{
        To: []string{order.CustomerEmail},
        Template: &resend.EmailTemplate{
            Id: "order-confirmation",
            Variables: map[string]any{
                "orderNumber": order.Number,
                "orderTotal":  order.Total,
                "customerName": order.CustomerName,
            },
        },
        Tags: []resend.Tag{
            {Name: "order_id", Value: order.ID},
        },
    })
}

options := &resend.BatchSendEmailOptions{
    BatchValidation: resend.BatchValidationPermissive,
    IdempotencyKey:  fmt.Sprintf("orders-%s", batchID),
}

sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)

Processing Large Batches

For large email lists, split them into smaller batches:
func sendInBatches(ctx context.Context, client *resend.Client, emails []*resend.SendEmailRequest, batchSize int) error {
	for i := 0; i < len(emails); i += batchSize {
		end := i + batchSize
		if end > len(emails) {
			end = len(emails)
		}
		
		batch := emails[i:end]
		
		options := &resend.BatchSendEmailOptions{
			BatchValidation: resend.BatchValidationPermissive,
			IdempotencyKey:  fmt.Sprintf("batch-%d", i),
		}
		
		sent, err := client.Batch.SendWithOptions(ctx, batch, options)
		if err != nil {
			return fmt.Errorf("batch %d failed: %w", i/batchSize, err)
		}
		
		fmt.Printf("Batch %d: sent %d, failed %d\n", 
			i/batchSize, len(sent.Data), len(sent.Errors))
		
		// Optional: Add delay between batches to respect rate limits
		time.Sleep(100 * time.Millisecond)
	}
	
	return nil
}

// Usage
emails := []*resend.SendEmailRequest{ /* ... */ }
err := sendInBatches(ctx, client, emails, 100) // Send 100 emails per batch
Recommended batch size: 100-500 emails per request for optimal performance.

Error Handling

Handle both request errors and validation errors:
sent, err := client.Batch.SendWithOptions(ctx, batchEmails, options)
if err != nil {
	// Handle rate limit errors
	if errors.Is(err, resend.ErrRateLimit) {
		if rateLimitErr, ok := err.(*resend.RateLimitError); ok {
			fmt.Printf("Rate limit hit. Retry after: %s seconds\n", rateLimitErr.RetryAfter)
			// Implement retry logic with backoff
		}
		return
	}
	
	// Handle other errors
	fmt.Printf("Batch send failed: %v\n", err)
	return
}

// Check for validation errors (permissive mode)
if len(sent.Errors) > 0 {
	fmt.Printf("Partial success: %d succeeded, %d failed\n", 
		len(sent.Data), len(sent.Errors))
	
	// Log failed emails for retry
	for _, batchErr := range sent.Errors {
		failedEmail := batchEmails[batchErr.Index]
		log.Printf("Failed to send to %v: %s", failedEmail.To, batchErr.Message)
	}
}

fmt.Printf("Successfully sent %d emails\n", len(sent.Data))

Best Practices

1

Choose the Right Validation Mode

Use strict mode for critical transactional emails where consistency matters:
// Strict for order confirmations (all must succeed)
BatchValidation: resend.BatchValidationStrict

// Permissive for marketing campaigns (partial success OK)
BatchValidation: resend.BatchValidationPermissive
2

Use Idempotency Keys

Always use idempotency keys for batch sends to prevent duplicates on retry:
IdempotencyKey: fmt.Sprintf("batch-%s-%d", campaignID, batchNumber)
3

Add Tags for Tracking

Tag batch emails for better analytics and debugging:
Tags: []resend.Tag{
    {Name: "batch_id", Value: batchID},
    {Name: "campaign", Value: campaignName},
    {Name: "send_date", Value: time.Now().Format("2006-01-02")},
}
4

Handle Partial Failures

In permissive mode, always check and log validation errors:
if len(sent.Errors) > 0 {
    // Log for retry or investigation
    for _, err := range sent.Errors {
        log.Printf("Email at index %d failed: %s", err.Index, err.Message)
    }
}
5

Batch Size Optimization

Keep batches to 100-500 emails for optimal performance:
const batchSize = 250
// Split large lists into batches of 250
6

Rate Limit Awareness

Add delays between batches if sending many batches in succession:
time.Sleep(200 * time.Millisecond) // Brief pause between batches

Validation Mode Comparison

FeatureStrict ModePermissive Mode
BehaviorAll-or-nothingPartial success allowed
Default✅ YesNo
Use CaseCritical transactional emailsMarketing campaigns
On Invalid EmailEntire batch failsValid emails sent, errors reported
ResponseError or all sentAlways includes Data and Errors arrays
Best ForOrder confirmations, invoicesNewsletters, welcome campaigns
Source: batch.go:9

Next Steps

Sending Emails

Learn single email sending basics

Email Templates

Use templates in batch emails

Scheduled Emails

Schedule batch emails for later

Attachments

Add attachments to batch emails

Build docs developers (and LLMs) love