Go
Send transactional emails from your Go application using the Veil Mail Go SDK.
Prerequisites
- Go 1.21 or later
- A Veil Mail API key
- A verified domain
1. Install the SDK
go get github.com/Resonia-Health/veilmail-go2. 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_xxxxxmain.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})
}