Go SDK

The official Go SDK for Posthawk. Works with Gin, Fiber, Echo, and standard net/http. Zero external dependencies.

SDKInstallation

Add posthawk-go to your Go module:

Example

bash
go get github.com/endibuka/posthawk-go
SDKQuick Start

Create a client and send your first email:

Example

go
package main

import (
    "context"
    "fmt"
    "log"

    posthawk "github.com/endibuka/posthawk-go"
)

func main() {
    client := posthawk.New("ck_live_...")

    result, err := client.Emails.Send(context.Background(), &posthawk.SendEmailRequest{
        From:    "hi@yourdomain.com",
        To:      []string{"user@example.com"},
        Subject: "Hello from Posthawk",
        HTML:    "<h1>Welcome!</h1><p>Your account is ready.</p>",
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Sent! Job ID:", result.JobID)
}
SDKSend Email

The SendEmailRequest struct supports all email fields: | Field | Type | Required | Description | |---|---|---|---| | From | string | Yes | Sender email address | | To | []string | Yes | Recipient email addresses | | Subject | string | Yes | Email subject line | | HTML | string | No | HTML body | | Text | string | No | Plain text body | | Cc | []string | No | CC recipients | | Bcc | []string | No | BCC recipients | | ReplyTo | string | No | Reply-to address | | TemplateID | string | No | Template identifier | | Variables | map[string]string | No | Template variables | | Headers | map[string]string | No | Custom email headers | | ScheduledFor | string | No | RFC 3339 send time | | Timezone | string | No | IANA timezone | | Metadata | map[string]any | No | Custom metadata | | Tags | map[string]any | No | Email tags |

Example

go
result, err := client.Emails.Send(ctx, &posthawk.SendEmailRequest{
    From:    "hi@yourdomain.com",
    To:      []string{"alice@example.com", "bob@example.com"},
    Cc:      []string{"manager@example.com"},
    Subject: "Weekly Report",
    HTML:    "<h1>Report</h1>",
    Text:    "Plain text fallback",
    Headers: map[string]string{"X-Custom": "value"},
    Metadata: map[string]any{"campaign": "onboarding"},
    Tags:     map[string]any{"type": "transactional"},
})
SDKCheck Delivery Status

Poll the status of a sent email by job ID. Status values: pending, processing, completed, failed.

Example

go
status, err := client.Emails.Get(ctx, "job-id-here")
if err != nil {
    log.Fatal(err)
}

fmt.Println("Status:", status.Status)     // pending | processing | completed | failed
fmt.Println("Created:", status.CreatedAt)
SDKList Scheduled Emails

Retrieve scheduled emails with optional filters:

Example

go
limit := 10
list, err := client.Scheduled.List(ctx, &posthawk.ListScheduledParams{
    Status: "scheduled",
    Limit:  &limit,
})
if err != nil {
    log.Fatal(err)
}

for _, email := range list.Data {
    fmt.Printf("%s → %s\n", email.Subject, email.ScheduledFor)
}
fmt.Println("Total:", list.Total)
SDKGet Scheduled Email

Retrieve a single scheduled email by ID:

Example

go
result, err := client.Scheduled.Get(ctx, "scheduled-email-id")
if err != nil {
    log.Fatal(err)
}

fmt.Println("Subject:", result.Data.Subject)
fmt.Println("Scheduled for:", result.Data.ScheduledFor)
fmt.Println("Status:", result.Data.Status)
SDKCancel Scheduled Email

Cancel a scheduled email before it sends:

Example

go
result, err := client.Scheduled.Cancel(ctx, "scheduled-email-id")
if err != nil {
    log.Fatal(err)
}

fmt.Println(result.Message) // "Scheduled email cancelled"
SDKReschedule Email

Change the send time of a scheduled email:

Example

go
result, err := client.Scheduled.Reschedule(ctx, "scheduled-email-id", &posthawk.RescheduleRequest{
    ScheduledFor: "2026-04-01T10:00:00Z",
})
if err != nil {
    log.Fatal(err)
}

fmt.Println("New time:", result.Data.ScheduledFor)
SDKGin / Fiber / net/http

The SDK works with any Go HTTP framework. Here are examples for the most popular ones.

Example

go
// ━━━ Gin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    posthawk "github.com/endibuka/posthawk-go"
)

var client = posthawk.New(os.Getenv("POSTHAWK_API_KEY"))

func main() {
    r := gin.Default()
    r.POST("/send", func(c *gin.Context) {
        var req struct {
            To      string `json:"to"`
            Subject string `json:"subject"`
            HTML    string `json:"html"`
        }
        c.BindJSON(&req)

        result, err := client.Emails.Send(c, &posthawk.SendEmailRequest{
            From: "hello@yourdomain.com", To: []string{req.To},
            Subject: req.Subject, HTML: req.HTML,
        })
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
        c.JSON(http.StatusOK, gin.H{"job_id": result.JobID})
    })
    r.Run()
}

// ━━━ Fiber ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
package main

import (
    "github.com/gofiber/fiber/v2"
    posthawk "github.com/endibuka/posthawk-go"
)

var client = posthawk.New(os.Getenv("POSTHAWK_API_KEY"))

func main() {
    app := fiber.New()
    app.Post("/send", func(c *fiber.Ctx) error {
        var req struct {
            To      string `json:"to"`
            Subject string `json:"subject"`
            HTML    string `json:"html"`
        }
        c.BodyParser(&req)

        result, err := client.Emails.Send(c.Context(), &posthawk.SendEmailRequest{
            From: "hello@yourdomain.com", To: []string{req.To},
            Subject: req.Subject, HTML: req.HTML,
        })
        if err != nil {
            return c.Status(500).JSON(fiber.Map{"error": err.Error()})
        }
        return c.JSON(fiber.Map{"job_id": result.JobID})
    })
    app.Listen(":3000")
}

// ━━━ net/http (standard library) ━━━━━━━━━━━━━━━━━━━━
package main

import (
    "encoding/json"
    "net/http"
    posthawk "github.com/endibuka/posthawk-go"
)

var client = posthawk.New(os.Getenv("POSTHAWK_API_KEY"))

func sendHandler(w http.ResponseWriter, r *http.Request) {
    var req struct {
        To      string `json:"to"`
        Subject string `json:"subject"`
        HTML    string `json:"html"`
    }
    json.NewDecoder(r.Body).Decode(&req)

    result, err := client.Emails.Send(r.Context(), &posthawk.SendEmailRequest{
        From: "hello@yourdomain.com", To: []string{req.To},
        Subject: req.Subject, HTML: req.HTML,
    })
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    json.NewEncoder(w).Encode(map[string]string{"job_id": result.JobID})
}

func main() {
    http.HandleFunc("/send", sendHandler)
    http.ListenAndServe(":3000", nil)
}
SDKError Handling

All methods return Go's standard (T, error) tuples. Errors are of type *posthawk.Error with StatusCode and Message fields. Use errors.As for type assertion:

Example

go
result, err := client.Emails.Send(ctx, &posthawk.SendEmailRequest{
    From:    "hi@yourdomain.com",
    To:      []string{"user@example.com"},
    Subject: "Test",
    HTML:    "<p>Hello</p>",
})
if err != nil {
    var posthawkErr *posthawk.Error
    if errors.As(err, &posthawkErr) {
        fmt.Printf("API error %d: %s\n", posthawkErr.StatusCode, posthawkErr.Message)
    } else {
        fmt.Printf("Unexpected error: %v\n", err)
    }
    return
}

fmt.Println("Success:", result.JobID)
SDKDomain Management

Manage sending domains programmatically — add, verify, enable inbound receiving, configure webhooks, and set sending limits:

Example

go
// Add a domain
domain, err := client.Domains.Create(ctx, &posthawk.CreateDomainRequest{
    Domain: "mail.customer.com",
    Region: "us-east-1",
})
// domain.Data.DnsRecords → DNS records to configure

// List all domains
domains, err := client.Domains.List(ctx)

// Trigger verification check
status, err := client.Domains.Verify(ctx, "domain-uuid")
// status.Data.SendingEnabled → true when verified

// Enable inbound receiving
client.Domains.EnableReceiving(ctx, "domain-uuid")

// Set a webhook for inbound emails
client.Domains.UpdateWebhook(ctx, "domain-uuid", &posthawk.UpdateDomainWebhookRequest{
    WebhookURL: strPtr("https://yourapp.com/api/incoming-email"),
})

// Test the webhook
client.Domains.TestWebhook(ctx, "domain-uuid")

// Set per-domain sending limits
client.Domains.UpdateLimits(ctx, "domain-uuid", &posthawk.UpdateDomainLimitsRequest{
    MaxDailyEmails:   intPtr(1000),
    MaxMonthlyEmails: intPtr(25000),
})

// Remove limits (nil = unlimited)
client.Domains.UpdateLimits(ctx, "domain-uuid", &posthawk.UpdateDomainLimitsRequest{
    MaxDailyEmails:   nil,
    MaxMonthlyEmails: nil,
})

// Delete a domain
client.Domains.Delete(ctx, "domain-uuid")
SDKSelf-Hosted Configuration

Point the SDK at your own Posthawk instance using WithBaseURL. You can also provide a custom http.Client:

Example

go
// Custom base URL for self-hosted
client := posthawk.New("ck_live_...",
    posthawk.WithBaseURL("https://api.yourdomain.com"),
)

// Custom HTTP client with timeout
client := posthawk.New("ck_live_...",
    posthawk.WithHTTPClient(&http.Client{
        Timeout: 10 * time.Second,
    }),
)
SDKclient.Contacts.*

Manage contacts: create/upsert, list with filters, update, delete, bulk import.

Example

go
// Create or upsert a contact
result, err := client.Contacts.Create(ctx, &posthawk.CreateContactRequest{
    Email: "user@example.com",
    Name:  "Jane Doe",
    Tags:  []string{"newsletter", "beta"},
})

// List with tag filter
contacts, err := client.Contacts.List(ctx, &posthawk.ListContactsParams{
    Tag:  "newsletter",
    Page: 1,
})

// Update
_, err = client.Contacts.Update(ctx, "contact-uuid", &posthawk.UpdateContactRequest{
    Tags: []string{"newsletter", "premium"},
})

// Delete
err = client.Contacts.Delete(ctx, "contact-uuid")

// Bulk import (up to 1000)
result, err := client.Contacts.Import(ctx, []posthawk.ContactImport{
    {Email: "a@x.com", Name: "A", Tags: []string{"import"}},
    {Email: "b@x.com", Name: "B", Tags: []string{"import"}},
})
SDKclient.Webhooks.*

Manage webhook endpoints for receiving real-time email + newsletter + broadcast events.

Example

go
// Create a webhook endpoint
result, err := client.Webhooks.Create(ctx, &posthawk.CreateWebhookRequest{
    URL:    "https://yourapp.com/api/webhooks/posthawk",
    Events: []string{"delivery", "bounce", "complaint", "open", "click"},
    Description: "Production webhook",
})
secret := result.Data.Secret // Store this — used for signature verification

// List
endpoints, err := client.Webhooks.List(ctx)

// Update
_, err = client.Webhooks.Update(ctx, "webhook-uuid", &posthawk.UpdateWebhookRequest{
    Events: []string{"delivery", "bounce", "complaint", "open", "click",
                     "newsletter.subscriber.created", "newsletter.issue.sent"},
})

// Test
_, err = client.Webhooks.Test(ctx, "webhook-uuid")

// Delete
err = client.Webhooks.Delete(ctx, "webhook-uuid")
SDKclient.Templates.Render(...)

Render a template server-side with variable substitution.

Example

go
result, err := client.Templates.Render(ctx, &posthawk.RenderTemplateRequest{
    TemplateID: "welcome-template",
    Variables: map[string]string{
        "firstName": "John",
        "company":   "Acme",
    },
})

fmt.Println(result.Data.HTML)
fmt.Println(result.Data.Text)
fmt.Println(result.Data.Subject)