Go

Send transactional emails from your Go application using the Veil Mail Go SDK.

Prerequisites

1. Install the SDK

go get github.com/Resonia-Health/veilmail-go

2. Configure the Client

Create a client instance. Store your API key in an environment variable — never commit it to source control.

.env
VEILMAIL_API_KEY=veil_live_xxxxx
VEILMAIL_WEBHOOK_SECRET=whsec_xxxxx
main.go
package main

import (
	"os"

	veilmail "github.com/Resonia-Health/veilmail-go"
)

var client = veilmail.NewClient(os.Getenv("VEILMAIL_API_KEY"))

3. Send Your First Email

Add an HTTP handler that sends a transactional email:

main.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	veilmail "github.com/Resonia-Health/veilmail-go"
)

var client = veilmail.NewClient(os.Getenv("VEILMAIL_API_KEY"))

func sendWelcomeHandler(w http.ResponseWriter, r *http.Request) {
	var body struct {
		Email string `json:"email"`
		Name  string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "Invalid request", http.StatusBadRequest)
		return
	}

	result, err := client.Emails().Send(context.Background(), &veilmail.SendEmailParams{
		From:    "hello@yourdomain.com",
		To:      body.Email,
		Subject: fmt.Sprintf("Welcome, %s!", body.Name),
		HTML:    fmt.Sprintf("<h1>Welcome</h1><p>Hi %s, thanks for joining.</p>", body.Name),
	})
	if err != nil {
		http.Error(w, "Failed to send email", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(map[string]string{"emailId": result.ID})
}

func main() {
	http.HandleFunc("POST /api/send-welcome", sendWelcomeHandler)
	log.Fatal(http.ListenAndServe(":3000", nil))
}

4. Send with a Template

Use templates to manage email content outside your code:

handlers/billing.go
func sendInvoiceHandler(w http.ResponseWriter, r *http.Request) {
	var body struct {
		Email     string  `json:"email"`
		InvoiceID string  `json:"invoiceId"`
		Amount    float64 `json:"amount"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "Invalid request", http.StatusBadRequest)
		return
	}

	result, err := client.Emails().Send(context.Background(), &veilmail.SendEmailParams{
		From:       "billing@yourdomain.com",
		To:         body.Email,
		Subject:    fmt.Sprintf("Invoice #%s", body.InvoiceID),
		TemplateID: "template_xxxxx",
		Variables: map[string]string{
			"invoiceId": body.InvoiceID,
			"amount":    fmt.Sprintf("$%.2f", body.Amount),
			"dueDate":   time.Now().AddDate(0, 0, 30).Format("01/02/2006"),
		},
	})
	if err != nil {
		http.Error(w, "Failed to send email", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(map[string]string{"emailId": result.ID})
}

5. Handle Webhooks

Receive real-time event notifications. Use io.ReadAll(r.Body) to read the raw body for signature verification.

webhooks.go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"encoding/json"
	"io"
	"net/http"
	"os"
)

func verifySignature(payload []byte, signature, secret string) bool {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write(payload)
	expected := hex.EncodeToString(mac.Sum(nil))

	return subtle.ConstantTimeCompare([]byte(signature), []byte(expected)) == 1
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	signature := r.Header.Get("X-Veilmail-Signature")
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read body", http.StatusBadRequest)
		return
	}

	secret := os.Getenv("VEILMAIL_WEBHOOK_SECRET")
	if signature == "" || !verifySignature(body, signature, secret) {
		http.Error(w, "Invalid signature", http.StatusUnauthorized)
		return
	}

	var event struct {
		Type string                 `json:"type"`
		Data map[string]interface{} `json:"data"`
	}
	if err := json.Unmarshal(body, &event); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	switch event.Type {
	case "email.delivered":
		log.Printf("Delivered: %s", event.Data["emailId"])
	case "email.bounced":
		log.Printf("Bounced: %s", event.Data["emailId"])
		// Remove from mailing list or flag user
	case "email.complained":
		log.Printf("Complaint: %s", event.Data["emailId"])
		// Unsubscribe the user
	case "subscriber.unsubscribed":
		log.Printf("Unsubscribed: %s", event.Data["subscriberId"])
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
}

Important: Always read the entire request body with io.ReadAll before parsing JSON. The raw bytes are needed for HMAC signature verification.

6. Manage Subscribers

Add and manage subscribers from your API:

handlers/subscribers.go
// Add a subscriber when a user signs up
func subscribeHandler(w http.ResponseWriter, r *http.Request) {
	var body struct {
		Email     string `json:"email"`
		FirstName string `json:"firstName"`
		LastName  string `json:"lastName"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "Invalid request", http.StatusBadRequest)
		return
	}

	subscriber, err := client.Audiences().Subscribers("audience_xxxxx").Add(
		context.Background(),
		&veilmail.AddSubscriberParams{
			Email:     body.Email,
			FirstName: body.FirstName,
			LastName:  body.LastName,
			Metadata:  map[string]string{"source": "website"},
		},
	)
	if err != nil {
		http.Error(w, "Failed to subscribe", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(map[string]string{"subscriberId": subscriber.ID})
}

// Unsubscribe
func unsubscribeHandler(w http.ResponseWriter, r *http.Request) {
	var body struct {
		SubscriberID string `json:"subscriberId"`
	}
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "Invalid request", http.StatusBadRequest)
		return
	}

	err := client.Audiences().Subscribers("audience_xxxxx").Remove(
		context.Background(),
		body.SubscriberID,
	)
	if err != nil {
		http.Error(w, "Failed to unsubscribe", http.StatusInternalServerError)
		return
	}

	json.NewEncoder(w).Encode(map[string]bool{"success": true})
}

7. Error Handling

The SDK returns typed errors you can inspect with helper functions:

handlers/email.go
import veilmail "github.com/Resonia-Health/veilmail-go"

func sendHandler(w http.ResponseWriter, r *http.Request) {
	var body struct {
		Email string `json:"email"`
	}
	json.NewDecoder(r.Body).Decode(&body)

	result, err := client.Emails().Send(context.Background(), &veilmail.SendEmailParams{
		From:    "hello@yourdomain.com",
		To:      body.Email,
		Subject: "Hello",
		HTML:    "<p>Hi!</p>",
	})
	if err != nil {
		if veilmail.IsRateLimitError(err) {
			http.Error(w, "Rate limited, try again later", http.StatusTooManyRequests)
		} else if veilmail.IsAuthenticationError(err) {
			http.Error(w, "Authentication failed", http.StatusUnauthorized)
		} else {
			http.Error(w, "Internal error", http.StatusInternalServerError)
		}
		return
	}

	json.NewEncoder(w).Encode(map[string]string{"id": result.ID})
}