Proper error handling is crucial for building reliable email systems. This guide covers error types, rate limit handling, and retry strategies.
Error Types
The Resend Go SDK provides several error types for different scenarios:
Returned when you exceed your rate limit. Contains metadata about limits and retry timing. type RateLimitError struct {
Message string // Error message from the API
Limit string // Maximum requests allowed
Remaining string // Requests remaining in current window
Reset string // Time when limit resets (seconds)
RetryAfter string // Recommended wait time before retry (seconds)
}
Returned for client-side errors like invalid parameters or missing required fields. type InvalidRequestError struct {
StatusCode int // HTTP status code (400 or 422)
Name string // Error name
Message string // Error description
}
MissingRequiredFieldsError
Returned when required fields are missing before making an API request. type MissingRequiredFieldsError struct {
message string // Description of missing fields
}
Returned for individual email failures in batch requests. type BatchError struct {
Index int // Position of failed email in batch
Message string // Error description
}
Basic Error Handling
Always check for errors after API calls:
params := & resend . SendEmailRequest {
To : [] string { "[email protected] " },
From : "[email protected] " ,
Text : "hello world" ,
Subject : "Hello from Golang" ,
}
sent , err := client . Emails . SendWithContext ( ctx , params )
if err != nil {
// Log the error
log . Printf ( "Failed to send email: %v " , err )
// Handle based on error type
// ... see sections below
return err
}
log . Printf ( "Email sent successfully: %s " , sent . Id )
Handle Rate Limits
Rate limits protect the API from abuse. Handle them gracefully with automatic retry:
Check for rate limit errors
Use errors.Is to detect rate limit errors: import (
" errors "
" github.com/resend/resend-go/v3 "
)
sent , err := client . Emails . SendWithContext ( ctx , params )
if err != nil {
if errors . Is ( err , resend . ErrRateLimit ) {
fmt . Println ( "Rate limit exceeded!" )
// Handle rate limit...
}
}
Extract rate limit details
Get detailed information about the rate limit: var rateLimitErr * resend . RateLimitError
if errors . As ( err , & rateLimitErr ) {
fmt . Printf ( "Message: %s \n " , rateLimitErr . Message )
fmt . Printf ( "Limit: %s requests \n " , rateLimitErr . Limit )
fmt . Printf ( "Remaining: %s requests \n " , rateLimitErr . Remaining )
fmt . Printf ( "Reset in: %s seconds \n " , rateLimitErr . Reset )
fmt . Printf ( "Retry after: %s seconds \n " , rateLimitErr . RetryAfter )
}
Implement retry logic
Wait and retry based on the RetryAfter value: import (
" strconv "
" time "
)
if retryAfter , err := strconv . Atoi ( rateLimitErr . RetryAfter ); err == nil {
fmt . Printf ( "Waiting %d seconds before retry... \n " , retryAfter )
time . Sleep ( time . Duration ( retryAfter ) * time . Second )
// Retry the request
sent , err = client . Emails . SendWithContext ( ctx , params )
if err != nil {
return err
}
fmt . Printf ( "Successfully sent after retry: %s \n " , sent . Id )
}
Complete Rate Limit Example
Here’s a complete example from examples/handle_rate_limit.go :
examples/handle_rate_limit.go
package main
import (
" context "
" errors "
" fmt "
" os "
" strconv "
" time "
" github.com/resend/resend-go/v3 "
)
func main () {
ctx := context . TODO ()
apiKey := os . Getenv ( "RESEND_API_KEY" )
client := resend . NewClient ( apiKey )
params := & resend . SendEmailRequest {
To : [] string { "[email protected] " },
From : "[email protected] " ,
Text : "hello world" ,
Subject : "Hello from Golang" ,
}
sent , err := client . Emails . SendWithContext ( ctx , params )
if err != nil {
// Check if it's a rate limit error using errors.Is
if errors . Is ( err , resend . ErrRateLimit ) {
fmt . Println ( "Rate limit exceeded!" )
// Extract detailed rate limit information
var rateLimitErr * resend . RateLimitError
if errors . As ( err , & rateLimitErr ) {
fmt . Printf ( "Message: %s \n " , rateLimitErr . Message )
fmt . Printf ( "Limit: %s requests \n " , rateLimitErr . Limit )
fmt . Printf ( "Remaining: %s requests \n " , rateLimitErr . Remaining )
fmt . Printf ( "Reset in: %s seconds \n " , rateLimitErr . Reset )
fmt . Printf ( "Retry after: %s seconds \n " , rateLimitErr . RetryAfter )
// Implement retry logic
if retryAfter , err := strconv . Atoi ( rateLimitErr . RetryAfter ); err == nil {
fmt . Printf ( "Waiting %d seconds before retry... \n " , retryAfter )
time . Sleep ( time . Duration ( retryAfter ) * time . Second )
// Retry the request
sent , err = client . Emails . SendWithContext ( ctx , params )
if err != nil {
panic ( err )
}
fmt . Printf ( "Successfully sent after retry: %s \n " , sent . Id )
}
}
return
}
// Handle other errors
panic ( err )
}
fmt . Printf ( "Email sent successfully: %s \n " , sent . Id )
}
Advanced Retry Strategies
Exponential Backoff
Retry with increasing delays between attempts:
import (
" errors "
" math "
" time "
)
func sendEmailWithRetry ( ctx context . Context , client * resend . Client , params * resend . SendEmailRequest , maxRetries int ) ( * resend . SendEmailResponse , error ) {
var sent * resend . SendEmailResponse
var err error
for attempt := 0 ; attempt <= maxRetries ; attempt ++ {
sent , err = client . Emails . SendWithContext ( ctx , params )
// Success
if err == nil {
return sent , nil
}
// Check if it's a rate limit error
if errors . Is ( err , resend . ErrRateLimit ) {
var rateLimitErr * resend . RateLimitError
if errors . As ( err , & rateLimitErr ) {
// Use RetryAfter from API if available
if retryAfter , parseErr := strconv . Atoi ( rateLimitErr . RetryAfter ); parseErr == nil {
waitDuration := time . Duration ( retryAfter ) * time . Second
fmt . Printf ( "Rate limited. Waiting %v before retry %d / %d \n " ,
waitDuration , attempt + 1 , maxRetries )
time . Sleep ( waitDuration )
continue
}
}
}
// For other errors, use exponential backoff
if attempt < maxRetries {
backoff := time . Duration ( math . Pow ( 2 , float64 ( attempt ))) * time . Second
fmt . Printf ( "Request failed (attempt %d / %d ): %v . Retrying in %v ... \n " ,
attempt + 1 , maxRetries , err , backoff )
time . Sleep ( backoff )
continue
}
// Max retries exceeded
break
}
return nil , fmt . Errorf ( "failed after %d retries: %w " , maxRetries , err )
}
// Usage
sent , err := sendEmailWithRetry ( ctx , client , params , 3 )
if err != nil {
log . Printf ( "Failed to send email after retries: %v " , err )
}
Retry with Context Timeout
Limit total retry time using context:
func sendEmailWithTimeout ( ctx context . Context , client * resend . Client , params * resend . SendEmailRequest , timeout time . Duration ) ( * resend . SendEmailResponse , error ) {
ctx , cancel := context . WithTimeout ( ctx , timeout )
defer cancel ()
attempt := 0
for {
attempt ++
sent , err := client . Emails . SendWithContext ( ctx , params )
if err == nil {
return sent , nil
}
// Check if context is done
select {
case <- ctx . Done ():
return nil , fmt . Errorf ( "timeout after %d attempts: %w " , attempt , ctx . Err ())
default :
}
// Handle rate limit
if errors . Is ( err , resend . ErrRateLimit ) {
var rateLimitErr * resend . RateLimitError
if errors . As ( err , & rateLimitErr ) {
if retryAfter , parseErr := strconv . Atoi ( rateLimitErr . RetryAfter ); parseErr == nil {
waitDuration := time . Duration ( retryAfter ) * time . Second
// Check if we have time to wait
select {
case <- time . After ( waitDuration ):
continue
case <- ctx . Done ():
return nil , fmt . Errorf ( "timeout while waiting for rate limit: %w " , ctx . Err ())
}
}
}
}
// For other errors, wait briefly before retry
select {
case <- time . After ( 1 * time . Second ):
continue
case <- ctx . Done ():
return nil , fmt . Errorf ( "timeout: %w " , ctx . Err ())
}
}
}
// Usage: Retry for up to 30 seconds
sent , err := sendEmailWithTimeout ( ctx , client , params , 30 * time . Second )
if err != nil {
log . Printf ( "Failed to send email: %v " , err )
}
Handle Validation Errors
Catch and handle invalid request errors:
sent , err := client . Emails . SendWithContext ( ctx , params )
if err != nil {
// Check for validation errors
if strings . Contains ( err . Error (), "Invalid request" ) ||
strings . Contains ( err . Error (), "validation" ) {
log . Printf ( "Validation error: %v " , err )
// Don't retry validation errors - fix the input instead
return fmt . Errorf ( "invalid email parameters: %w " , err )
}
// Handle other errors
return err
}
Validation errors indicate a problem with your request parameters. Don’t retry these errors - fix the input instead.
Handle Batch Errors
When sending batch emails, some may succeed while others fail:
batchParams := [] * resend . SendEmailRequest {
{
To : [] string { "[email protected] " },
From : "[email protected] " ,
Subject : "Welcome" ,
Text : "Hello user 1" ,
},
{
To : [] string { "invalid-email" },
From : "[email protected] " ,
Subject : "Welcome" ,
Text : "Hello user 2" ,
},
}
resp , err := client . Batch . SendWithContext ( ctx , batchParams )
if err != nil {
log . Printf ( "Batch request failed: %v " , err )
return err
}
// Check for partial failures
if len ( resp . Errors ) > 0 {
fmt . Printf ( "Some emails failed: \n " )
for _ , batchErr := range resp . Errors {
fmt . Printf ( " - Email at index %d failed: %s \n " ,
batchErr . Index , batchErr . Message )
}
}
// Process successful emails
fmt . Printf ( "Successfully sent %d emails: \n " , len ( resp . Data ))
for i , sent := range resp . Data {
fmt . Printf ( " - Email %d : %s \n " , i , sent . Id )
}
Error Handling Best Practices
Always Check Errors Never ignore error returns from API calls. Always check and handle them appropriately.
Use Idempotency Keys When retrying, use idempotency keys to prevent duplicate emails: options := & resend . SendEmailOptions {
IdempotencyKey : generateUniqueKey (),
}
client . Emails . SendWithOptions ( ctx , params , options )
Don't Retry Validation Errors Validation errors indicate bad input. Fix your parameters instead of retrying.
Respect Rate Limits Always use the RetryAfter value from rate limit errors. Don’t retry immediately.
Set Timeouts Use context timeouts to prevent indefinite retries: ctx , cancel := context . WithTimeout ( context . Background (), 30 * time . Second )
defer cancel ()
Log Errors Log errors with context to help debugging: log . Printf ( "Failed to send email to %v : %v " , params . To , err )
Next Steps
Custom Client Configure HTTP client with custom retry logic
Basic Usage Learn the fundamentals of sending emails
Templates Use email templates with dynamic variables
API Reference View complete API documentation