Skip to main content
Requirements: Go 1.22 or higher

Installation

go get github.com/ArkHQ-io/ark-go
Or pin a specific version:
go get github.com/ArkHQ-io/[email protected]

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/ArkHQ-io/ark-go"
    "github.com/ArkHQ-io/ark-go/option"
)

func main() {
    // Initialize the client (uses ARK_API_KEY env var by default)
    client := ark.NewClient()

    // Send an email
    email, err := client.Emails.Send(context.Background(), ark.EmailSendParams{
        From:    "Security <[email protected]>",
        To:      []string{"[email protected]"},
        Subject: "Reset your password",
        HTML:    ark.String("<h1>Password Reset</h1><p>Click the link to reset.</p>"),
        Text:    ark.String("Password Reset\n\nClick the link to reset."),
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Email ID: %s\n", email.Data.ID)
    fmt.Printf("Status: %s\n", email.Data.Status)
}

Configuration

Client Options

import (
    "github.com/ArkHQ-io/ark-go"
    "github.com/ArkHQ-io/ark-go/option"
)

client := ark.NewClient(
    option.WithAPIKey("ark_..."),                      // Required (or set ARK_API_KEY env var)
    option.WithBaseURL("https://api.arkhq.io/v1"),    // API base URL (rarely needed)
    option.WithMaxRetries(2),                          // Number of retry attempts (default: 2)
)

Environment Variables

export ARK_API_KEY="ark_live_..."

Parameter Types

The SDK uses helper functions for optional parameters:
// Required string fields (passed directly)
From:    "[email protected]",
Subject: "Hello World",

// Required string slices (passed directly)
To: []string{"[email protected]"},

// Optional string fields - use ark.String()
HTML: ark.String("<h1>Welcome!</h1>"),
Text: ark.String("Plain text version"),

// Optional string slice fields - use ark.StringSlice()
CC: ark.StringSlice([]string{"[email protected]"}),

// Boolean fields
TrackOpens: ark.Bool(true),

// Integer fields
PerPage: ark.Int(25),

API Reference

Emails

Send a single email.
email, err := client.Emails.Send(ctx, ark.EmailSendParams{
    From:    "[email protected]",
    To:      []string{"[email protected]"},
    Subject: "Welcome!",
    HTML:    ark.String("<h1>Hello</h1>"),
    Text:    ark.String("Hello"),  // Optional but recommended
    CC:      ark.StringSlice([]string{"[email protected]"}),  // Optional
    BCC:     ark.StringSlice([]string{"[email protected]"}), // Optional
    ReplyTo: ark.String("[email protected]"),  // Optional
    Tags:    ark.StringSlice([]string{"welcome", "onboarding"}),  // Optional
    Metadata: map[string]string{"user_id": "123"},  // Optional
    TrackOpens:  ark.Bool(true),   // Optional
    TrackClicks: ark.Bool(true),   // Optional
    ScheduledAt: ark.String("2024-01-20T09:00:00Z"),  // Optional
    Attachments: []ark.Attachment{  // Optional
        {
            Filename:    ark.String("invoice.pdf"),
            Content:     ark.String(base64Content),
            ContentType: ark.String("application/pdf"),
        },
    },
    Headers: map[string]string{  // Optional
        "X-Custom-Header": "value",
    },
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Email ID: %s\n", email.Data.ID)
Send multiple emails in a single request.
result, err := client.Emails.SendBatch(ctx, ark.EmailSendBatchParams{
    Emails: []ark.BatchEmail{
        {
            From:    "[email protected]",
            To:      []string{"[email protected]"},
            Subject: "Hello User 1",
            HTML:    ark.String("<p>Hello!</p>"),
        },
        {
            From:    "[email protected]",
            To:      []string{"[email protected]"},
            Subject: "Hello User 2",
            HTML:    ark.String("<p>Hello!</p>"),
        },
    },
})
if err != nil {
    log.Fatal(err)
}

for _, email := range result.Data {
    fmt.Printf("%s: %s\n", email.ID, email.Status)
}
Send a raw MIME message.
rawMessage := `From: [email protected]
To: [email protected]
Subject: Raw email
Content-Type: text/plain

This is a raw MIME message.`

email, err := client.Emails.SendRaw(ctx, ark.EmailSendRawParams{
    From:       "[email protected]",
    To:         []string{"[email protected]"},
    RawMessage: rawMessage,
})
List emails with filtering and pagination.
emails, err := client.Emails.List(ctx, ark.EmailListParams{
    Page:    ark.Int(1),
    PerPage: ark.Int(25),
    Status:  ark.String("sent"),       // Optional filter
    Tag:     ark.String("welcome"),    // Optional filter
})
if err != nil {
    log.Fatal(err)
}

for _, email := range emails.Data {
    fmt.Printf("%s: %s - %s\n", email.ID, email.Subject, email.Status)
}

// Pagination info
fmt.Printf("Page %d of %d\n", emails.Page, emails.TotalPages)
Get a single email by ID.
email, err := client.Emails.Get(ctx, "msg_abc123xyz")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Subject: %s\n", email.Data.Subject)
fmt.Printf("Status: %s\n", email.Data.Status)
fmt.Printf("Created: %s\n", email.Data.CreatedAt)
Get delivery attempts for an email.
deliveries, err := client.Emails.GetDeliveries(ctx, "msg_abc123xyz")
if err != nil {
    log.Fatal(err)
}

for _, delivery := range deliveries.Data {
    fmt.Printf("Attempt at %s: %s\n", delivery.Timestamp, delivery.Status)
    if delivery.Error != "" {
        fmt.Printf("  Error: %s\n", delivery.Error)
    }
}
Retry a failed email.
result, err := client.Emails.Retry(ctx, "msg_abc123xyz")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Retry scheduled: %s\n", result.Data.ID)

Domains

Register a new sending domain.
domain, err := client.Domains.Create(ctx, ark.DomainCreateParams{
    Name: ark.String("mail.yourdomain.com"),
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Domain ID: %s\n", domain.Data.ID)
fmt.Println("DNS Records to configure:")
for _, record := range domain.Data.DNSRecords {
    fmt.Printf("  %s %s -> %s\n", record.Type, record.Name, record.Value)
}
List all domains.
domains, err := client.Domains.List(ctx)
if err != nil {
    log.Fatal(err)
}

for _, domain := range domains.Data {
    status := "Pending"
    if domain.Verified {
        status = "Verified"
    }
    fmt.Printf("%s: %s\n", domain.Name, status)
}
Get domain details.
domain, err := client.Domains.Get(ctx, "dom_abc123")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Domain: %s\n", domain.Data.Name)
fmt.Printf("Verified: %t\n", domain.Data.Verified)
Trigger DNS verification.
result, err := client.Domains.Verify(ctx, "dom_abc123")
if err != nil {
    log.Fatal(err)
}

if result.Data.Verified {
    fmt.Println("Domain verified successfully!")
} else {
    fmt.Println("DNS records not found. Please check your configuration.")
}
Remove a domain.
err := client.Domains.Delete(ctx, "dom_abc123")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Domain deleted")

Suppressions

Add an email to the suppression list.
suppression, err := client.Suppressions.Create(ctx, ark.SuppressionCreateParams{
    Email:  ark.String("[email protected]"),
    Reason: ark.String("hard_bounce"),
})
Add multiple emails to the suppression list.
result, err := client.Suppressions.BulkCreate(ctx, ark.SuppressionBulkCreateParams{
    Suppressions: []ark.SuppressionInput{
        {Email: ark.String("[email protected]"), Reason: ark.String("hard_bounce")},
        {Email: ark.String("[email protected]"), Reason: ark.String("complaint")},
    },
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Added %d suppressions\n", result.Data.CreatedCount)
List suppressed emails.
suppressions, err := client.Suppressions.List(ctx, ark.SuppressionListParams{
    Page:    ark.Int(1),
    PerPage: ark.Int(50),
    Reason:  ark.String("hard_bounce"),  // Optional filter
})
if err != nil {
    log.Fatal(err)
}

for _, s := range suppressions.Data {
    fmt.Printf("%s: %s (added %s)\n", s.Email, s.Reason, s.CreatedAt)
}
Check if an email is suppressed.
suppression, err := client.Suppressions.Get(ctx, "[email protected]")
if err != nil {
    var arkErr *ark.Error
    if errors.As(err, &arkErr) && arkErr.StatusCode == 404 {
        fmt.Println("Email is not suppressed")
        return
    }
    log.Fatal(err)
}
fmt.Printf("Suppressed: %s\n", suppression.Data.Reason)
Remove from suppression list.
err := client.Suppressions.Delete(ctx, "[email protected]")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Removed from suppression list")

Webhooks

Create a webhook endpoint.
webhook, err := client.Webhooks.Create(ctx, ark.WebhookCreateParams{
    URL:    ark.String("https://yourapp.com/webhooks/ark"),
    Events: ark.StringSlice([]string{"MessageSent", "MessageBounced", "MessageLoaded"}),
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Webhook ID: %s\n", webhook.Data.ID)
fmt.Printf("Signing Secret: %s\n", webhook.Data.SigningSecret)  // Store this!
List all webhooks.
webhooks, err := client.Webhooks.List(ctx)
if err != nil {
    log.Fatal(err)
}

for _, wh := range webhooks.Data {
    fmt.Printf("%s: %s\n", wh.ID, wh.URL)
    fmt.Printf("  Events: %v\n", wh.Events)
}
Update webhook configuration.
webhook, err := client.Webhooks.Update(ctx, "wh_abc123", ark.WebhookUpdateParams{
    Events: ark.StringSlice([]string{"MessageSent", "MessageBounced"}),
})
Send a test event to your webhook.
result, err := client.Webhooks.Test(ctx, "wh_abc123", ark.WebhookTestParams{
    Event: ark.String("MessageSent"),
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Test result: %s\n", result.Data.Status)
Delete a webhook.
err := client.Webhooks.Delete(ctx, "wh_abc123")

Tracking

Configure a custom tracking domain.
tracking, err := client.Tracking.Create(ctx, ark.TrackingCreateParams{
    Domain: ark.String("track.yourdomain.com"),
})
if err != nil {
    log.Fatal(err)
}

fmt.Println("Add this CNAME record:")
fmt.Printf("  %s\n", tracking.Data.CNAMETarget)
List tracking domains.
trackingDomains, err := client.Tracking.List(ctx)
if err != nil {
    log.Fatal(err)
}

for _, t := range trackingDomains.Data {
    status := "Pending"
    if t.Verified {
        status = "Verified"
    }
    fmt.Printf("%s: %s\n", t.Domain, status)
}
Verify tracking domain DNS.
result, err := client.Tracking.Verify(ctx, "trk_abc123")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Verified: %t\n", result.Data.Verified)
Remove a tracking domain.
err := client.Tracking.Delete(ctx, "trk_abc123")

Error Handling

The SDK returns *ark.Error for API errors:
import (
    "errors"
    "github.com/ArkHQ-io/ark-go"
)

email, err := client.Emails.Send(ctx, params)
if err != nil {
    var arkErr *ark.Error
    if errors.As(err, &arkErr) {
        fmt.Printf("Status: %d\n", arkErr.StatusCode)
        fmt.Printf("Message: %s\n", arkErr.Message)
        fmt.Printf("Code: %s\n", arkErr.Code)

        switch arkErr.StatusCode {
        case 400:
            fmt.Println("Bad request - check your parameters")
        case 401:
            fmt.Println("Authentication failed - check your API key")
        case 403:
            fmt.Println("Permission denied")
        case 404:
            fmt.Println("Resource not found")
        case 429:
            fmt.Println("Rate limited - slow down requests")
        default:
            fmt.Println("Server error")
        }
    } else {
        // Network or other error
        fmt.Printf("Error: %v\n", err)
    }
    return
}

Debug Helpers

Use DumpRequest and DumpResponse for debugging:
email, err := client.Emails.Send(ctx, params)
if err != nil {
    var arkErr *ark.Error
    if errors.As(err, &arkErr) {
        fmt.Println(arkErr.DumpRequest(true))   // Include body
        fmt.Println(arkErr.DumpResponse(true))  // Include body
    }
}

Context and Timeouts

Use context for request timeouts and cancellation:
import (
    "context"
    "time"
)

// Timeout for a single request
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

email, err := client.Emails.Send(ctx, params)

// Per-request timeout option
email, err := client.Emails.Send(ctx, params,
    option.WithRequestTimeout(5*time.Second),
)

Concurrency

Use goroutines for concurrent requests:
import (
    "context"
    "sync"
)

func sendWelcomeEmails(users []User) {
    client := ark.NewClient()
    ctx := context.Background()

    var wg sync.WaitGroup
    results := make(chan string, len(users))
    errors := make(chan error, len(users))

    for _, user := range users {
        wg.Add(1)
        go func(u User) {
            defer wg.Done()

            email, err := client.Emails.Send(ctx, ark.EmailSendParams{
                From:    "[email protected]",
                To:      []string{u.Email},
                Subject: fmt.Sprintf("Welcome, %s!", u.Name),
                HTML:    ark.String("<h1>Hello!</h1>"),
            })
            if err != nil {
                errors <- err
                return
            }
            results <- email.Data.ID
        }(user)
    }

    wg.Wait()
    close(results)
    close(errors)

    for id := range results {
        fmt.Printf("Sent: %s\n", id)
    }
    for err := range errors {
        fmt.Printf("Error: %v\n", err)
    }
}

With errgroup

import "golang.org/x/sync/errgroup"

func sendEmails(ctx context.Context, users []User) error {
    client := ark.NewClient()
    g, ctx := errgroup.WithContext(ctx)

    // Limit concurrency
    g.SetLimit(10)

    for _, user := range users {
        u := user // Capture variable
        g.Go(func() error {
            _, err := client.Emails.Send(ctx, ark.EmailSendParams{
                From:    "[email protected]",
                To:      []string{u.Email},
                Subject: "Welcome!",
                HTML:    ark.String("<h1>Hello!</h1>"),
            })
            return err
        })
    }

    return g.Wait()
}

Advanced Usage

Raw Response Access

Access HTTP response details:
import "net/http"

var response *http.Response

email, err := client.Emails.Send(ctx, params,
    option.WithResponseInto(&response),
)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Status: %d\n", response.StatusCode)
fmt.Printf("Request ID: %s\n", response.Header.Get("X-Request-ID"))

Custom Headers

Add custom headers to requests:
email, err := client.Emails.Send(ctx, params,
    option.WithHeader("X-Custom-Header", "value"),
)

Extra Fields

Access undocumented response fields:
email, err := client.Emails.Get(ctx, "msg_abc123")
if err != nil {
    log.Fatal(err)
}

// Check for extra fields not in the type definition
if val, ok := email.Data.ExtraFields["newField"]; ok {
    fmt.Printf("New field: %v\n", val)
}

Idempotency

Use idempotency keys to safely retry requests:
import "github.com/google/uuid"

idempotencyKey := uuid.New().String()

email, err := client.Emails.Send(ctx, ark.EmailSendParams{
    From:           "[email protected]",
    To:             []string{"[email protected]"},
    Subject:        "Order Confirmation",
    HTML:           ark.String("<p>Your order is confirmed.</p>"),
    IdempotencyKey: ark.String(idempotencyKey),
})

Framework Integration

Gin

import (
    "github.com/gin-gonic/gin"
    "github.com/ArkHQ-io/ark-go"
)

var arkClient = ark.NewClient()

func main() {
    r := gin.Default()

    r.POST("/send-email", func(c *gin.Context) {
        var req struct {
            To      string `json:"to"`
            Subject string `json:"subject"`
            HTML    string `json:"html"`
        }
        if err := c.BindJSON(&req); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }

        email, err := arkClient.Emails.Send(c.Request.Context(), ark.EmailSendParams{
            From:    "[email protected]",
            To:      []string{req.To},
            Subject: req.Subject,
            HTML:    ark.String(req.HTML),
        })
        if err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }

        c.JSON(200, gin.H{"email_id": email.Data.ID})
    })

    r.Run()
}

Echo

import (
    "github.com/labstack/echo/v4"
    "github.com/ArkHQ-io/ark-go"
)

var arkClient = ark.NewClient()

func main() {
    e := echo.New()

    e.POST("/send-email", func(c echo.Context) error {
        var req struct {
            To      string `json:"to"`
            Subject string `json:"subject"`
            HTML    string `json:"html"`
        }
        if err := c.Bind(&req); err != nil {
            return err
        }

        email, err := arkClient.Emails.Send(c.Request().Context(), ark.EmailSendParams{
            From:    "[email protected]",
            To:      []string{req.To},
            Subject: req.Subject,
            HTML:    ark.String(req.HTML),
        })
        if err != nil {
            return err
        }

        return c.JSON(200, map[string]string{"email_id": email.Data.ID})
    })

    e.Start(":8080")
}

Fiber

import (
    "github.com/gofiber/fiber/v2"
    "github.com/ArkHQ-io/ark-go"
)

var arkClient = ark.NewClient()

func main() {
    app := fiber.New()

    app.Post("/send-email", func(c *fiber.Ctx) error {
        var req struct {
            To      string `json:"to"`
            Subject string `json:"subject"`
            HTML    string `json:"html"`
        }
        if err := c.BodyParser(&req); err != nil {
            return err
        }

        email, err := arkClient.Emails.Send(c.Context(), ark.EmailSendParams{
            From:    "[email protected]",
            To:      []string{req.To},
            Subject: req.Subject,
            HTML:    ark.String(req.HTML),
        })
        if err != nil {
            return err
        }

        return c.JSON(fiber.Map{"email_id": email.Data.ID})
    })

    app.Listen(":8080")
}

Resources