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, no credit card required)
- A verified sending domain
If you haven't set up your domain yet, follow the domain configuration guide 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
go get github.com/AhaSend/ahasend-goThe 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:
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.
Send your first email
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: "[email protected]"},
Recipients: []ahasend.Recipient{
{Email: "[email protected]"},
},
Subject: "Welcome to Your App",
HtmlContent: ahasend.PtrString("<h1>Welcome!</h1><p>Thanks for signing up.</p>"),
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:
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:
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(`
<h1>Welcome, %s!</h1>
<p>Your account is ready. Log in at any time.</p>
<p>Questions? Just reply to this email.</p>
`, userName)
textBody := fmt.Sprintf(
"Welcome, %s! Your account is ready. Questions? Just reply to this email.",
userName,
)
message := ahasend.CreateMessageRequest{
From: ahasend.SenderAddress{
Email: "[email protected]",
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, set the secret as an environment variable, then handle events:
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 in the SDK repository.
Rate limiting and retry configuration
The SDK manages rate limiting automatically. For high-volume workloads you can tune it:
// 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
go get gopkg.in/gomail.v2SMTP settings
Host: send.ahasend.com
Ports: 25, 587, 2525 (all support STARTTLS)
Auth: RequiredSMTP credentials are separate from your API key. Create them in your dashboard under Credentials.
Send via SMTP
package main
import (
"fmt"
"log"
"os"
gomail "gopkg.in/gomail.v2"
)
func main() {
m := gomail.NewMessage()
m.SetAddressHeader("From", "[email protected]", "Your App")
m.SetAddressHeader("To", "[email protected]", "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", "<p>This email was sent via AhaSend's SMTP relay.</p>")
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- Basic sendingsend_with_attachments.go- File attachmentsbatch_send.go- Bulk sendingscheduled_send.go- Schedule future deliverywebhook_processing.go- Complete webhook servererror_handling.go- Robust error handlingidempotency.go- Prevent duplicate sendsstatistics.go- Delivery analytics
Run any example:
export AHASEND_API_KEY="your-api-key"
export AHASEND_ACCOUNT_ID="your-account-id"
go run examples/send_email.goNote: 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
- Configure sandbox mode for safe testing in development
- Read the full API reference
- Browse the Go package documentation
Questions? Reach us at [email protected] or join the Discord.