Go SDK
The official Go SDK for Posthawk. Works with Gin, Fiber, Echo, and standard net/http. Zero external dependencies.
InstallationAdd posthawk-go to your Go module:
Example
go get github.com/endibuka/posthawk-goQuick StartCreate a client and send your first email:
Example
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)
}Send EmailThe 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
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"},
})Check Delivery StatusPoll the status of a sent email by job ID. Status values: pending, processing, completed, failed.
Example
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)List Scheduled EmailsRetrieve scheduled emails with optional filters:
Example
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)Get Scheduled EmailRetrieve a single scheduled email by ID:
Example
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)Cancel Scheduled EmailCancel a scheduled email before it sends:
Example
result, err := client.Scheduled.Cancel(ctx, "scheduled-email-id")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Message) // "Scheduled email cancelled"Reschedule EmailChange the send time of a scheduled email:
Example
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)Gin / Fiber / net/httpThe SDK works with any Go HTTP framework. Here are examples for the most popular ones.
Example
// ━━━ 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)
}Error HandlingAll 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
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)Domain ManagementManage sending domains programmatically — add, verify, enable inbound receiving, configure webhooks, and set sending limits:
Example
// 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")Self-Hosted ConfigurationPoint the SDK at your own Posthawk instance using WithBaseURL. You can also provide a custom http.Client:
Example
// 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,
}),
)client.Contacts.*Manage contacts: create/upsert, list with filters, update, delete, bulk import.
Example
// 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"}},
})client.Webhooks.*Manage webhook endpoints for receiving real-time email + newsletter + broadcast events.
Example
// 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")client.Templates.Render(...)Render a template server-side with variable substitution.
Example
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)