Send emails with Go
Learn how to send transactional emails using PostStack and Go.
The PostStack Go SDK is a stdlib-only client — no transitive dependencies, no surprise CVEs, and it builds into a tiny binary. Every method takes a `context.Context` so cancellation and timeout propagate from your HTTP handler down to the network call. The SDK is goroutine-safe: instantiate one `*Client` at startup and share it across handlers, workers, and goroutines.
1. Install the SDK
go get github.com/getpoststack/go-sdk2. Initialize the client
import (
"context"
poststack "github.com/getpoststack/go-sdk"
)
client := poststack.NewClient("sk_live_...")3. Send an email
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
poststack "github.com/getpoststack/go-sdk"
)
func main() {
client := poststack.NewClient(os.Getenv("POSTSTACK_API_KEY"))
result, err := client.Emails.Send(context.Background(), &poststack.SendEmailInput{
From: "hello@yourdomain.com",
To: []string{"user@example.com"},
Subject: "Hello from Go!",
Html: "<h1>Welcome!</h1>",
})
if err != nil {
var apiErr *poststack.Error
if errors.As(err, &apiErr) {
log.Fatalf("API error %d: %s", apiErr.StatusCode, apiErr.Message)
}
log.Fatal(err)
}
fmt.Println(result.ID)
}4. Handle errors
Go idioms for error handling, retries, and structured logging when calling the PostStack API.
package main
import (
"context"
"errors"
"log/slog"
"time"
poststack "github.com/getpoststack/go-sdk"
)
func sendWithRetry(ctx context.Context, client *poststack.Client, in *poststack.SendEmailInput) (*poststack.Email, error) {
var lastErr error
for attempt := 0; attempt < 3; attempt++ {
result, err := client.Emails.Send(ctx, in)
if err == nil {
return result, nil
}
var apiErr *poststack.Error
if errors.As(err, &apiErr) {
if apiErr.StatusCode == 429 {
// Honour Retry-After
time.Sleep(apiErr.RetryAfter)
continue
}
if apiErr.StatusCode >= 500 {
time.Sleep(time.Duration(1<<attempt) * time.Second)
continue
}
// 4xx — log and bail
slog.Error("poststack 4xx",
"status", apiErr.StatusCode,
"request_id", apiErr.RequestID,
"message", apiErr.Message,
)
}
return nil, err
}
return nil, lastErr
}Framework integrations
net/http
For pure stdlib HTTP servers, instantiate the client at startup and store it on a struct your handlers method into. Pass `r.Context()` through so client cancellation propagates to the PostStack call.
Gin / Echo / Chi
Inject the client via the framework’s context or DI helper. Gin: `c.MustGet("poststack")`. Echo: `c.Get("poststack")`. Chi: a middleware that attaches the client to `r.Context()`. Same `Send(ctx, ...)` call regardless of router.
Goroutine workers
For background sends, push send payloads onto a buffered channel and have N worker goroutines consume them. Each worker holds a reference to the shared `*Client` — goroutine-safe by design.
cron / scheduled jobs
`robfig/cron` jobs receive a `context.Context` you can plumb directly into `client.Emails.Send(ctx, ...)`. Use `context.WithTimeout` to bound each send so a stuck request does not back up the scheduler.
Common pitfalls
Using context.Background() everywhere
Pass the request or job context through instead. `context.Background()` cannot be cancelled, so a slow upstream call blocks the handler forever. Use `r.Context()` or `context.WithTimeout`.
Re-checking err with a wrong type assertion
Use `errors.As(err, &apiErr)` to extract `*poststack.Error`. A direct type assertion (`err.(*poststack.Error)`) panics on a non-API error.
Logging the API key
`%+v` on the client struct prints the API key. Strip secrets before logging — the SDK provides `String()` that redacts the key.
Notes
- Official Go SDK — stdlib-only, zero external dependencies
- Requires Go 1.22+
FAQ
Is the Go SDK goroutine-safe?
Yes. Instantiate one `*Client` at startup and share it across all goroutines and handlers. The underlying `http.Client` is safe for concurrent use.
What Go versions are supported?
Go 1.22 and later. The SDK uses range-over-func and slog from the stdlib, so older versions are not supported.
Are there any external dependencies?
No. The SDK is stdlib-only — only `net/http`, `encoding/json`, `context`, and friends. This minimises CVE exposure and keeps your final binary small.
Related guides
Ready to send emails with Go?
Create a free account and get your API key in under a minute.