Go SDK

High-performance Go SDK with context support, functional options, and idiomatic Go patterns.

✅ Ready v1.0.0 Go Modules

Quick Start

Installation

# Install using Go modules
go get github.com/sparkmailr/go-sdk

# In your go.mod file
require github.com/sparkmailr/go-sdk v1.0.0

Basic Usage

package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/sparkmailr/go-sdk"
)

func main() {
    // Initialize client
    client := sparkmailr.NewClient("your-api-key")
    
    // Send email
    ctx := context.Background()
    response, err := client.Emails.Send(ctx, &sparkmailr.SendEmailRequest{
        To:      []string{"user@example.com"},
        From:    "noreply@yourapp.com",
        Subject: "Welcome to SparkMailr!",
        HTML:    "<h1>Hello World</h1>",
        Text:    "Hello World",
    })
    
    if err != nil {
        log.Fatalf("Error sending email: %v", err)
    }
    
    fmt.Printf("Email sent: %s\n", response.ID)
}

Configuration with Functional Options

package main

import (
    "time"
    "github.com/sparkmailr/go-sdk"
)

func main() {
    // Full configuration using functional options
    client := sparkmailr.NewClient("your-api-key",
        sparkmailr.WithBaseURL("https://api.sparkmailr.com"),  // Optional
        sparkmailr.WithTimeout(30),                            // Timeout in seconds
        sparkmailr.WithMaxRetries(3),                          // Max retry attempts
        sparkmailr.WithRetryDelay(time.Second),                // Delay between retries
        sparkmailr.WithDebug(false),                           // Enable debug logging
        sparkmailr.WithCustomHTTPClient(httpClient),           // Custom HTTP client
    )
    
    // Environment-based configuration
    client := sparkmailr.NewClient(
        os.Getenv("SPARKMAILR_API_KEY"),
        sparkmailr.WithTimeout(30),
        sparkmailr.WithDebug(os.Getenv("DEBUG") == "true"),
    )
}

Context Support & Concurrency

Context with Timeout

package main

import (
    "context"
    "fmt"
    "time"
    
    "github.com/sparkmailr/go-sdk"
)

func sendEmailWithTimeout() error {
    client := sparkmailr.NewClient("your-api-key")
    
    // Create context with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    response, err := client.Emails.Send(ctx, &sparkmailr.SendEmailRequest{
        To:      []string{"user@example.com"},
        From:    "noreply@yourapp.com",
        Subject: "Email with Timeout",
        HTML:    "<p>This email has a 10-second timeout</p>",
    })
    
    if err != nil {
        return fmt.Errorf("failed to send email: %w", err)
    }
    
    fmt.Printf("Email sent: %s\n", response.ID)
    return nil
}

Concurrent Email Sending

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
    
    "github.com/sparkmailr/go-sdk"
)

func sendConcurrentEmails() error {
    client := sparkmailr.NewClient("your-api-key")
    
    emails := []string{
        "user1@example.com",
        "user2@example.com", 
        "user3@example.com",
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    var wg sync.WaitGroup
    results := make(chan string, len(emails))
    errors := make(chan error, len(emails))
    
    for i, email := range emails {
        wg.Add(1)
        go func(email string, index int) {
            defer wg.Done()
            
            response, err := client.Emails.Send(ctx, &sparkmailr.SendEmailRequest{
                To:      []string{email},
                From:    "noreply@yourapp.com",
                Subject: fmt.Sprintf("Concurrent Email %d", index+1),
                HTML:    fmt.Sprintf("<p>Hello %s</p>", email),
            })
            
            if err != nil {
                errors <- fmt.Errorf("failed to send to %s: %w", email, err)
                return
            }
            
            results <- response.ID
        }(email, i)
    }
    
    // Wait for all goroutines to complete
    wg.Wait()
    close(results)
    close(errors)
    
    // Collect results
    var sentCount int
    for emailID := range results {
        fmt.Printf("Email sent: %s\n", emailID)
        sentCount++
    }
    
    // Handle errors
    for err := range errors {
        fmt.Printf("Error: %v\n", err)
    }
    
    fmt.Printf("Successfully sent %d out of %d emails\n", sentCount, len(emails))
    return nil
}

HTTP Server Integration

Gin Framework Example

package main

import (
    "net/http"
    
    "github.com/gin-gonic/gin"
    "github.com/sparkmailr/go-sdk"
)

type EmailService struct {
    sparkMailr *sparkmailr.Client
}

func NewEmailService(apiKey string) *EmailService {
    return &EmailService{
        sparkMailr: sparkmailr.NewClient(apiKey,
            sparkmailr.WithTimeout(30),
        ),
    }
}

type ContactRequest struct {
    Name    string `json:"name" binding:"required"`
    Email   string `json:"email" binding:"required,email"`
    Message string `json:"message" binding:"required"`
}

func (es *EmailService) HandleContact(c *gin.Context) {
    var req ContactRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    // Send email
    response, err := es.sparkMailr.Emails.Send(c.Request.Context(), &sparkmailr.SendEmailRequest{
        To:      []string{"admin@yourapp.com"},
        From:    "noreply@yourapp.com",
        Subject: "Contact Form from " + req.Name,
        HTML: fmt.Sprintf(`
            <h2>Contact Form Submission</h2>
            <p><strong>Name:</strong> %s</p>
            <p><strong>Email:</strong> %s</p>
            <p><strong>Message:</strong></p>
            <p>%s</p>
        `, req.Name, req.Email, req.Message),
    })
    
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "error":   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success":  true,
        "email_id": response.ID,
    })
}

func main() {
    emailService := NewEmailService(os.Getenv("SPARKMAILR_API_KEY"))
    
    r := gin.Default()
    
    // API routes
    api := r.Group("/api")
    {
        api.POST("/contact", emailService.HandleContact)
    }
    
    r.Run(":8080")
}

Standard HTTP Server Example

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    
    "github.com/sparkmailr/go-sdk"
)

type Server struct {
    sparkMailr *sparkmailr.Client
}

func NewServer(apiKey string) *Server {
    return &Server{
        sparkMailr: sparkmailr.NewClient(apiKey),
    }
}

func (s *Server) handleSendEmail(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    var req struct {
        To      []string `json:"to"`
        Subject string   `json:"subject"`
        HTML    string   `json:"html"`
    }
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    response, err := s.sparkMailr.Emails.Send(r.Context(), &sparkmailr.SendEmailRequest{
        To:      req.To,
        From:    "noreply@yourapp.com",
        Subject: req.Subject,
        HTML:    req.HTML,
    })
    
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "success":  true,
        "email_id": response.ID,
    })
}

func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) {
    emailID := r.URL.Query().Get("id")
    if emailID == "" {
        http.Error(w, "Missing email ID", http.StatusBadRequest)
        return
    }
    
    status, err := s.sparkMailr.Emails.GetStatus(r.Context(), emailID)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(status)
}

func main() {
    server := NewServer(os.Getenv("SPARKMAILR_API_KEY"))
    
    http.HandleFunc("/send", server.handleSendEmail)
    http.HandleFunc("/status", server.handleGetStatus)
    
    fmt.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Advanced Features

Template Emails

// Send email with template and complex data
templateData := map[string]interface{}{
    "user": map[string]interface{}{
        "name":      "John Doe",
        "plan":      "premium",
        "joinDate":  time.Now().Format("2006-01-02"),
    },
    "features":        []string{"Feature 1", "Feature 2", "Feature 3"},
    "activationLink":  "https://app.example.com/activate/123",
    "expirationDate":  time.Now().AddDate(0, 0, 7).Format("2006-01-02"),
}

response, err := client.Emails.Send(ctx, &sparkmailr.SendEmailRequest{
    To:           []string{"user@example.com"},
    From:         "noreply@yourapp.com",
    TemplateID:   "welcome-template",
    TemplateData: templateData,
})

if err != nil {
    return fmt.Errorf("failed to send template email: %w", err)
}

fmt.Printf("Template email sent: %s\n", response.ID)

Bulk Email Sending

// Prepare bulk email recipients
recipients := []sparkmailr.BulkEmailRecipient{
    {
        To: "user1@example.com",
        TemplateData: map[string]interface{}{
            "name":      "User 1",
            "promoCode": "ABC123",
        },
    },
    {
        To: "user2@example.com",
        TemplateData: map[string]interface{}{
            "name":      "User 2",
            "promoCode": "DEF456",
        },
    },
    {
        To: "user3@example.com",
        TemplateData: map[string]interface{}{
            "name":      "User 3",
            "promoCode": "GHI789",
        },
    },
}

bulkRequest := &sparkmailr.BulkEmailRequest{
    From:       "newsletter@yourapp.com",
    TemplateID: "newsletter-template",
    Recipients: recipients,
}

response, err := client.Emails.SendBulk(ctx, bulkRequest)
if err != nil {
    return fmt.Errorf("failed to send bulk emails: %w", err)
}

fmt.Printf("Bulk email batch: %s\n", response.BatchID)

Email Analytics

// Get email status
emailID := "em_1234567890"
status, err := client.Emails.GetStatus(ctx, emailID)
if err != nil {
    return fmt.Errorf("failed to get email status: %w", err)
}

fmt.Printf("Email %s status: %s\n", emailID, status.Status)
fmt.Printf("Delivered at: %v\n", status.DeliveredAt)
fmt.Printf("Opened at: %v\n", status.OpenedAt)

// Get analytics for date range
analytics, err := client.Analytics.GetEmailStats(ctx, &sparkmailr.AnalyticsRequest{
    StartDate: time.Now().AddDate(0, -1, 0), // Last month
    EndDate:   time.Now(),
})
if err != nil {
    return fmt.Errorf("failed to get analytics: %w", err)
}

fmt.Printf("Total sent: %d\n", analytics.TotalSent)
fmt.Printf("Delivery rate: %.2f%%\n", analytics.DeliveryRate)
fmt.Printf("Open rate: %.2f%%\n", analytics.OpenRate)
fmt.Printf("Click rate: %.2f%%\n", analytics.ClickRate)

Error Handling

package main

import (
    "context"
    "errors"
    "fmt"
    "time"
    
    "github.com/sparkmailr/go-sdk"
)

func sendEmailWithErrorHandling(client *sparkmailr.Client) error {
    ctx := context.Background()
    
    response, err := client.Emails.Send(ctx, &sparkmailr.SendEmailRequest{
        To:      []string{"invalid-email"},
        From:    "noreply@yourapp.com",
        Subject: "Test Email",
    })
    
    if err != nil {
        // Type assertion for specific error types
        var authErr *sparkmailr.AuthenticationError
        var rateErr *sparkmailr.RateLimitError
        var validationErr *sparkmailr.ValidationError
        var networkErr *sparkmailr.NetworkError
        
        switch {
        case errors.As(err, &authErr):
            return fmt.Errorf("authentication failed: %w", err)
            
        case errors.As(err, &rateErr):
            fmt.Printf("Rate limited - retry after: %d seconds\n", rateErr.RetryAfter)
            
            // Implement exponential backoff
            time.Sleep(time.Duration(rateErr.RetryAfter) * time.Second)
            // Retry logic here
            return fmt.Errorf("rate limited: %w", err)
            
        case errors.As(err, &validationErr):
            fmt.Println("Validation failed:")
            for field, fieldErrors := range validationErr.ValidationErrors {
                fmt.Printf("  %s: %v\n", field, fieldErrors)
            }
            return fmt.Errorf("validation failed: %w", err)
            
        case errors.As(err, &networkErr):
            fmt.Printf("Network error: %s\n", err.Error())
            // Retry logic for network issues
            return fmt.Errorf("network error: %w", err)
            
        default:
            return fmt.Errorf("unexpected error: %w", err)
        }
    }
    
    fmt.Printf("Email sent successfully: %s\n", response.ID)
    return nil
}

// Retry with exponential backoff
func sendEmailWithRetry(client *sparkmailr.Client, maxRetries int) error {
    var lastErr error
    
    for i := 0; i < maxRetries; i++ {
        err := sendEmailWithErrorHandling(client)
        if err == nil {
            return nil // Success
        }
        
        lastErr = err
        
        // Check if it's a retryable error
        var rateErr *sparkmailr.RateLimitError
        var networkErr *sparkmailr.NetworkError
        
        if errors.As(err, &rateErr) || errors.As(err, &networkErr) {
            // Wait before retry (exponential backoff)
            waitTime := time.Duration(1<<uint(i)) * time.Second
            fmt.Printf("Retry %d/%d in %v...\n", i+1, maxRetries, waitTime)
            time.Sleep(waitTime)
            continue
        }
        
        // Non-retryable error
        break
    }
    
    return fmt.Errorf("failed after %d retries: %w", maxRetries, lastErr)
}

Testing with Testify

package main

import (
    "context"
    "testing"
    
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "github.com/sparkmailr/go-sdk"
)

// Mock client for testing
type MockEmailsClient struct {
    mock.Mock
}

func (m *MockEmailsClient) Send(ctx context.Context, req *sparkmailr.SendEmailRequest) (*sparkmailr.EmailResponse, error) {
    args := m.Called(ctx, req)
    return args.Get(0).(*sparkmailr.EmailResponse), args.Error(1)
}

func (m *MockEmailsClient) GetStatus(ctx context.Context, emailID string) (*sparkmailr.EmailStatus, error) {
    args := m.Called(ctx, emailID)
    return args.Get(0).(*sparkmailr.EmailStatus), args.Error(1)
}

func TestSendEmail(t *testing.T) {
    // Arrange
    mockClient := new(MockEmailsClient)
    
    expectedResponse := &sparkmailr.EmailResponse{
        ID:     "em_123456",
        Status: "queued",
    }
    
    mockClient.On("Send", mock.Anything, mock.AnythingOfType("*sparkmailr.SendEmailRequest")).
        Return(expectedResponse, nil)
    
    // Act
    response, err := mockClient.Send(context.Background(), &sparkmailr.SendEmailRequest{
        To:      []string{"test@example.com"},
        From:    "noreply@test.com",
        Subject: "Test Email",
    })
    
    // Assert
    assert.NoError(t, err)
    assert.Equal(t, "em_123456", response.ID)
    assert.Equal(t, "queued", response.Status)
    
    mockClient.AssertExpectations(t)
}

func TestSendEmailError(t *testing.T) {
    // Arrange
    mockClient := new(MockEmailsClient)
    
    expectedError := &sparkmailr.RateLimitError{
        Message:    "Rate limit exceeded",
        RetryAfter: 60,
    }
    
    mockClient.On("Send", mock.Anything, mock.AnythingOfType("*sparkmailr.SendEmailRequest")).
        Return((*sparkmailr.EmailResponse)(nil), expectedError)
    
    // Act
    response, err := mockClient.Send(context.Background(), &sparkmailr.SendEmailRequest{
        To:      []string{"test@example.com"},
        From:    "noreply@test.com",
        Subject: "Test Email",
    })
    
    // Assert
    assert.Error(t, err)
    assert.Nil(t, response)
    
    var rateErr *sparkmailr.RateLimitError
    assert.ErrorAs(t, err, &rateErr)
    assert.Equal(t, 60, rateErr.RetryAfter)
    
    mockClient.AssertExpectations(t)
}

// Integration test with HTTP mock
func TestSendEmailIntegration(t *testing.T) {
    // This would use httpmock or similar for integration testing
    // Example with actual HTTP mocking
    
    client := sparkmailr.NewClient("test-api-key",
        sparkmailr.WithBaseURL("http://mock-api.test"),
    )
    
    // Setup HTTP mock here...
    
    response, err := client.Emails.Send(context.Background(), &sparkmailr.SendEmailRequest{
        To:      []string{"test@example.com"},
        From:    "noreply@test.com",
        Subject: "Integration Test",
        HTML:    "<p>Test content</p>",
    })
    
    assert.NoError(t, err)
    assert.NotEmpty(t, response.ID)
}

// Benchmark test
func BenchmarkSendEmail(b *testing.B) {
    mockClient := new(MockEmailsClient)
    
    mockClient.On("Send", mock.Anything, mock.AnythingOfType("*sparkmailr.SendEmailRequest")).
        Return(&sparkmailr.EmailResponse{ID: "em_123456", Status: "queued"}, nil)
    
    ctx := context.Background()
    req := &sparkmailr.SendEmailRequest{
        To:      []string{"test@example.com"},
        From:    "noreply@test.com",
        Subject: "Benchmark Test",
    }
    
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        _, err := mockClient.Send(ctx, req)
        if err != nil {
            b.Fatal(err)
        }
    }
}

Worker Pools for High Throughput

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
    
    "github.com/sparkmailr/go-sdk"
)

type EmailJob struct {
    To      string
    Subject string
    HTML    string
}

type EmailWorker struct {
    id       int
    client   *sparkmailr.Client
    jobChan  <-chan EmailJob
    resultChan chan<- string
    errorChan  chan<- error
    wg       *sync.WaitGroup
}

func NewEmailWorker(id int, client *sparkmailr.Client, jobChan <-chan EmailJob, 
                   resultChan chan<- string, errorChan chan<- error, wg *sync.WaitGroup) *EmailWorker {
    return &EmailWorker{
        id:         id,
        client:     client,
        jobChan:    jobChan,
        resultChan: resultChan,
        errorChan:  errorChan,
        wg:         wg,
    }
}

func (w *EmailWorker) Start(ctx context.Context) {
    defer w.wg.Done()
    
    for {
        select {
        case job, ok := <-w.jobChan:
            if !ok {
                fmt.Printf("Worker %d: Job channel closed, stopping\n", w.id)
                return
            }
            
            fmt.Printf("Worker %d: Processing job for %s\n", w.id, job.To)
            
            response, err := w.client.Emails.Send(ctx, &sparkmailr.SendEmailRequest{
                To:      []string{job.To},
                From:    "noreply@yourapp.com",
                Subject: job.Subject,
                HTML:    job.HTML,
            })
            
            if err != nil {
                select {
                case w.errorChan <- fmt.Errorf("worker %d failed to send to %s: %w", w.id, job.To, err):
                case <-ctx.Done():
                    return
                }
                continue
            }
            
            select {
            case w.resultChan <- response.ID:
            case <-ctx.Done():
                return
            }
            
        case <-ctx.Done():
            fmt.Printf("Worker %d: Context cancelled, stopping\n", w.id)
            return
        }
    }
}

func ProcessEmailsWithWorkerPool() error {
    client := sparkmailr.NewClient("your-api-key",
        sparkmailr.WithTimeout(30),
        sparkmailr.WithMaxRetries(3),
    )
    
    // Create channels
    jobChan := make(chan EmailJob, 100)
    resultChan := make(chan string, 100)
    errorChan := make(chan error, 100)
    
    // Create context with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    defer cancel()
    
    // Start workers
    numWorkers := 10
    var wg sync.WaitGroup
    
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        worker := NewEmailWorker(i, client, jobChan, resultChan, errorChan, &wg)
        go worker.Start(ctx)
    }
    
    // Send jobs
    jobs := []EmailJob{
        {To: "user1@example.com", Subject: "Welcome", HTML: "<h1>Welcome User 1</h1>"},
        {To: "user2@example.com", Subject: "Welcome", HTML: "<h1>Welcome User 2</h1>"},
        {To: "user3@example.com", Subject: "Welcome", HTML: "<h1>Welcome User 3</h1>"},
        // Add more jobs...
    }
    
    go func() {
        for _, job := range jobs {
            select {
            case jobChan <- job:
            case <-ctx.Done():
                return
            }
        }
        close(jobChan)
    }()
    
    // Collect results
    go func() {
        wg.Wait()
        close(resultChan)
        close(errorChan)
    }()
    
    var successCount, errorCount int
    
    // Process results
    for {
        select {
        case emailID, ok := <-resultChan:
            if !ok {
                resultChan = nil
            } else {
                fmt.Printf("Success: %s\n", emailID)
                successCount++
            }
            
        case err, ok := <-errorChan:
            if !ok {
                errorChan = nil
            } else {
                fmt.Printf("Error: %v\n", err)
                errorCount++
            }
        }
        
        if resultChan == nil && errorChan == nil {
            break
        }
    }
    
    fmt.Printf("Processing completed: %d success, %d errors\n", successCount, errorCount)
    return nil
}

Need More Help?

Complete Documentation pkg.go.dev Contact Support