High-performance Go SDK with context support, functional options, and idiomatic Go patterns.
# 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
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)
}
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"),
)
}
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
}
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
}
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")
}
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))
}
// 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)
// 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)
// 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)
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)
}
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)
}
}
}
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
}