Send email with Go
Standard library only: net/http and encoding/json.
You need a verified sending domain and an API key from Settings › API Keys. If neither exists yet, the quickstart covers both in a couple of minutes.
Send
package main
import (
"bytes"
"encoding/json"
"net/http"
"os"
"time"
)
type email struct {
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
HTML string `json:"html"`
IdempotencyKey string `json:"idempotencyKey"`
}
func send(e email) (*http.Response, error) {
body, err := json.Marshal(e)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", "https://sending.dev/api/v1/emails", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("SENDING_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
return client.Do(req)
}A successful call answers 202 with the message id: accepted and queued, not yet delivered. Delivery, opens, bounces and complaints arrive later, on webhooks or in the dashboard.
What changes in Go
The json tags matter: the API reads idempotencyKey in camelCase, and Go would otherwise marshal the field as IdempotencyKey.
The thing that catches people
http.DefaultClient has no timeout. A request that hangs holds the goroutine forever, which is how a send path quietly becomes a leak under load.
Next
- All the fields: cc and bcc, reply-to, attachments, templates, scheduling.
- Webhooks: delivery, bounce and complaint events, signed with HMAC.
- Domains: SPF, DKIM and MAIL FROM, and why sending is refused until they are in place.
- MCP: the same operations as tools, when the one writing the code is an agent.