Requirements: Go 1.22 or higher
Installation
Copy
go get github.com/ArkHQ-io/ark-go
Copy
go get github.com/ArkHQ-io/[email protected]
Quick Start
Copy
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
Copy
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
Copy
export ARK_API_KEY="ark_live_..."
Parameter Types
The SDK uses helper functions for optional parameters:Copy
// 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
Send
Send a single email.
Copy
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)
SendBatch
SendBatch
Send multiple emails in a single request.
Copy
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)
}
SendRaw
SendRaw
Send a raw MIME message.
Copy
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
List
List emails with filtering and pagination.
Copy
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
Get
Get a single email by ID.
Copy
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)
GetDeliveries
GetDeliveries
Get delivery attempts for an email.
Copy
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
Retry
Retry a failed email.
Copy
result, err := client.Emails.Retry(ctx, "msg_abc123xyz")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Retry scheduled: %s\n", result.Data.ID)
Domains
Create
Create
Register a new sending domain.
Copy
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
List
List all domains.
Copy
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
Get
Get domain details.
Copy
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)
Verify
Verify
Trigger DNS verification.
Copy
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.")
}
Delete
Delete
Remove a domain.
Copy
err := client.Domains.Delete(ctx, "dom_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Println("Domain deleted")
Suppressions
Create
Create
Add an email to the suppression list.
Copy
suppression, err := client.Suppressions.Create(ctx, ark.SuppressionCreateParams{
Email: ark.String("[email protected]"),
Reason: ark.String("hard_bounce"),
})
BulkCreate
BulkCreate
Add multiple emails to the suppression list.
Copy
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
List
List suppressed emails.
Copy
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)
}
Get
Get
Check if an email is suppressed.
Copy
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)
Delete
Delete
Remove from suppression list.
Copy
err := client.Suppressions.Delete(ctx, "[email protected]")
if err != nil {
log.Fatal(err)
}
fmt.Println("Removed from suppression list")
Webhooks
Create
Create
Create a webhook endpoint.
Copy
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
List
List all webhooks.
Copy
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
Update
Update webhook configuration.
Copy
webhook, err := client.Webhooks.Update(ctx, "wh_abc123", ark.WebhookUpdateParams{
Events: ark.StringSlice([]string{"MessageSent", "MessageBounced"}),
})
Test
Test
Send a test event to your webhook.
Copy
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
Delete
Delete a webhook.
Copy
err := client.Webhooks.Delete(ctx, "wh_abc123")
Tracking
Create
Create
Configure a custom tracking domain.
Copy
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
List
List tracking domains.
Copy
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
Verify
Verify tracking domain DNS.
Copy
result, err := client.Tracking.Verify(ctx, "trk_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Verified: %t\n", result.Data.Verified)
Delete
Delete
Remove a tracking domain.
Copy
err := client.Tracking.Delete(ctx, "trk_abc123")
Error Handling
The SDK returns*ark.Error for API errors:
Copy
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
UseDumpRequest and DumpResponse for debugging:
Copy
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:Copy
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:Copy
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
Copy
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:Copy
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:Copy
email, err := client.Emails.Send(ctx, params,
option.WithHeader("X-Custom-Header", "value"),
)
Extra Fields
Access undocumented response fields:Copy
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:Copy
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
Copy
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
Copy
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
Copy
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")
}
