--- title: "The Complete Guide to sending Transactional Emails in Go with AhaSend" description: "Go developers have two solid options for sending transactional email with AhaSend: the HTTP API via the official Go SDK (recommended for most applications) and SMTP relay (useful when you're working with existing Go email libraries). This guide covers both, from installation to a production-ready example with error handling and webhook processing." url: https://ahasend.com/blog/complete-guide-sending-transactional-emails-go-ahasend markdown_url: https://ahasend.com/blog/complete-guide-sending-transactional-emails-go-ahasend.md lang: en type: blog_post published: 2026-04-13 updated: 2026-08-28 author: "Mark Kraakman" category: "Guides" image: https://cms.ahasend.com/sites/default/files/2026-04/the-complete-guide-to-sending-transactional-emails-in-go-with-ahasend-1.png --- # The Complete Guide to sending Transactional Emails in Go with AhaSend _By Mark Kraakman, April 13, 2026, in Guides_  Go developers have two solid options for sending transactional email with AhaSend: the HTTP API via the official Go SDK (recommended for most applications) and SMTP relay (useful when you're working with existing Go email libraries). This guide covers both, from installation to a production-ready example with error handling and webhook processing. ## Prerequisites - Go 1.18 or later - An AhaSend account ([free tier available](https://dash.ahasend.com/user/register), no credit card required) - A verified sending domain If you haven't set up your domain yet, follow the [domain configuration guide](https://ahasend.com/docs/domains) first. You cannot send from an unverified domain. ## Option 1: HTTP API with the AhaSend Go SDK The Go SDK is the recommended approach. It gives you full API coverage, built-in rate limiting, automatic retries with exponential backoff, idempotency protection, and webhook verification out of the box. ### Install the SDK ```bash go get github.com/AhaSend/ahasend-go ``` The SDK has minimal dependencies - only `github.com/google/uuid` and `github.com/stretchr/testify` (for tests). ### Set your credentials Store credentials as environment variables, never in source code: ```bash export AHASEND_API_KEY="aha-sk-your-64-character-key" export AHASEND_ACCOUNT_ID="your-account-id-here" ``` You'll find both values in your [AhaSend dashboard](https://dash.ahasend.com/). ### Send your first email ```go package main import ( "context" "log" "os" "github.com/AhaSend/ahasend-go" "github.com/google/uuid" ) func main() { apiKey := os.Getenv("AHASEND_API_KEY") accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID")) client := ahasend.NewAPIClient(ahasend.NewConfiguration()) ctx := context.WithValue(context.Background(), ahasend.ContextAccessToken, apiKey) message := ahasend.CreateMessageRequest{ From: ahasend.SenderAddress{Email: "hello@yourdomain.com"}, Recipients: []ahasend.Recipient{ {Email: "recipient@example.com"}, }, Subject: "Welcome to Your App", HtmlContent: ahasend.PtrString("
Thanks for signing up.
"), TextContent: ahasend.PtrString("Welcome! Thanks for signing up."), } response, _, err := client.MessagesAPI.CreateMessage(ctx, accountID, message) if err != nil { log.Fatalf("Failed to send email: %v", err) } log.Printf("Email sent. Message ID: %s", *response.Data[0].Id) } ``` ### Set the API key at client level For applications sending many emails, set the API key once on the client rather than passing it through context on every request: ```go client := ahasend.NewAPIClient( ahasend.WithAPIKey(os.Getenv("AHASEND_API_KEY")), ) // All subsequent calls use this key automatically - no ctx override needed response, _, err := client.MessagesAPI.CreateMessage(ctx, accountID, message) ``` ### A real-world example: user signup confirmation In practice you'll be sending email as part of an application flow. Here's a signup confirmation inside a Go HTTP handler, with proper error handling: ```go package handlers import ( "context" "fmt" "log" "net/http" "os" "github.com/AhaSend/ahasend-go" "github.com/google/uuid" ) var ( emailClient *ahasend.APIClient accountID uuid.UUID ) func init() { emailClient = ahasend.NewAPIClient( ahasend.WithAPIKey(os.Getenv("AHASEND_API_KEY")), ) accountID = uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID")) } func sendWelcomeEmail(ctx context.Context, userEmail, userName string) error { htmlBody := fmt.Sprintf(`Your account is ready. Log in at any time.
Questions? Just reply to this email.
`, userName) textBody := fmt.Sprintf( "Welcome, %s! Your account is ready. Questions? Just reply to this email.", userName, ) message := ahasend.CreateMessageRequest{ From: ahasend.SenderAddress{ Email: "hello@yourdomain.com", Name: ahasend.PtrString("Your App"), }, Recipients: []ahasend.Recipient{ { Email: userEmail, Name: ahasend.PtrString(userName), }, }, Subject: "Welcome to Your App", HtmlContent: ahasend.PtrString(htmlBody), TextContent: ahasend.PtrString(textBody), } _, _, err := emailClient.MessagesAPI.CreateMessage(ctx, accountID, message) return err } func RegisterHandler(w http.ResponseWriter, r *http.Request) { // ... your registration logic ... if err := sendWelcomeEmail(r.Context(), newUser.Email, newUser.Name); err != nil { // Log but don't fail the registration. A transient email issue // shouldn't block account creation. Track delivery via webhooks. log.Printf("Warning: failed to send welcome email to %s: %v", newUser.Email, err) } w.WriteHeader(http.StatusCreated) } ``` ### Handling delivery events with webhooks The SDK includes Standard Webhooks compliant processing with HMAC-SHA256 signature verification. Configure a webhook endpoint in your [AhaSend dashboard](https://dash.ahasend.com/), set the secret as an environment variable, then handle events: ```go package main import ( "log" "net/http" "os" "github.com/AhaSend/ahasend-go/webhooks" ) func main() { verifier, err := webhooks.NewWebhookVerifier(os.Getenv("AHASEND_WEBHOOK_SECRET")) if err != nil { log.Fatalf("Failed to create verifier: %v", err) } http.HandleFunc("/webhooks/ahasend", func(w http.ResponseWriter, r *http.Request) { event, err := verifier.ParseRequest(r) if err != nil { http.Error(w, "Invalid webhook", http.StatusBadRequest) return } switch e := event.(type) { case *webhooks.MessageDeliveredEvent: log.Printf("Delivered to %s", e.Data.Recipient) case *webhooks.MessageBouncedEvent: // Mark address as undeliverable in your database log.Printf("Bounced: %s - reason: %s", e.Data.Recipient, e.Data.Reason) case *webhooks.MessageOpenedEvent: log.Printf("Opened by %s", e.Data.Recipient) case *webhooks.MessageClickedEvent: log.Printf("Link clicked by %s", e.Data.Recipient) } w.WriteHeader(http.StatusOK) }) log.Println("Webhook server listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } ``` Supported event types: `message.delivered`, `message.bounced`, `message.opened`, `message.clicked`, plus `suppression.*`, `domain.*`, and `route.*` events. For the complete webhook server example, see [`webhook_processing.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/webhook_processing.go) in the SDK repository. ### Rate limiting and retry configuration The SDK manages rate limiting automatically. For high-volume workloads you can tune it: ```go // Configure for high-volume sending client.SetSendMessageRateLimit(500, 1000) // 500 req/s, burst of 1000 // Configure retry behaviour config := &ahasend.RetryConfig{ Enabled: true, MaxRetries: 3, BackoffStrategy: ahasend.ExponentialBackoff, BaseDelay: time.Second, MaxDelay: 30 * time.Second, } client.SetRetryConfig(config) ``` ## Option 2: SMTP with gomail If you're migrating from another provider and want to change as little code as possible, or your framework already has SMTP support built in, use gomail with AhaSend's SMTP relay. ### Install gomail ```bash go get gopkg.in/gomail.v2 ``` ### SMTP settings ```plaintext Host: send.ahasend.com Ports: 25, 587, 2525 (all support STARTTLS) Auth: Required ``` SMTP credentials are separate from your API key. Create them in your dashboard under Credentials. ### Send via SMTP ```go package main import ( "fmt" "log" "os" gomail "gopkg.in/gomail.v2" ) func main() { m := gomail.NewMessage() m.SetAddressHeader("From", "hello@yourdomain.com", "Your App") m.SetAddressHeader("To", "recipient@example.com", "Recipient Name") m.SetHeader("Subject", "Hello from AhaSend") m.SetBody("text/plain", "This email was sent via AhaSend's SMTP relay.") m.AddAlternative("text/html", "This email was sent via AhaSend's SMTP relay.
") d := gomail.NewDialer( "send.ahasend.com", 587, os.Getenv("AHASEND_SMTP_USER"), os.Getenv("AHASEND_SMTP_PASSWORD"), ) if err := d.DialAndSend(m); err != nil { log.Fatalf("Failed to send: %v", err) } fmt.Println("Email sent.") } ``` ## Which method should you use? Use the **Go SDK** if you're building a new application or want full visibility into delivery status, bounce handling, webhook events, and delivery statistics. The SDK handles retries, rate limiting, and idempotency so you don't have to. Use **SMTP** if you're migrating from another provider and want to swap out credentials with minimal code changes. ## Production-ready examples The SDK repository includes 11 complete, runnable examples: - [`send_email.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/send_email.go) - Basic sending - [`send_with_attachments.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/send_with_attachments.go) - File attachments - [`batch_send.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/batch_send.go) - Bulk sending - [`scheduled_send.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/scheduled_send.go) - Schedule future delivery - [`webhook_processing.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/webhook_processing.go) - Complete webhook server - [`error_handling.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/error_handling.go) - Robust error handling - [`idempotency.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/idempotency.go) - Prevent duplicate sends - [`statistics.go`](https://github.com/AhaSend/ahasend-go/blob/main/examples/statistics.go) - Delivery analytics Run any example: ```bash export AHASEND_API_KEY="your-api-key" export AHASEND_ACCOUNT_ID="your-account-id" go run examples/send_email.go ``` Note: all example files use `//go:build ignore` so they can be run individually without conflicting with `go build ./...`. ## Next steps - Set up [open and click tracking](https://ahasend.com/docs/tracking/open-tracking) - Configure [sandbox mode](https://ahasend.com/docs/send-api/sandbox) for safe testing in development - Read the full [API reference](https://ahasend.com/docs/api-reference) - Browse the [Go package documentation](https://pkg.go.dev/github.com/AhaSend/ahasend-go) Questions? Reach us at [support@ahasend.com](mailto:support@ahasend.com) or join the [Discord](https://discord.gg/SfzuwZ8c).