Frameworks and agents · Published 2026-09-29 · Updated 2026-09-30 · AgentiSend
Send email from Go
A Go program that posts one invoice with net/http, an idempotency key, and the code and fix of a refusal.
To send email from Go, post JSON to POST /emails with net/http. There is no Go package for this API, and this page does not invent one. The program below checks the address, sends with an idempotency key derived from the recipient, and prints the message id, or the refusal's code and fix. It is run by the test suite against a local AgentiSend server.
Install
No module to add. The program uses the standard library.
Environment
| Variable | Required | What it is |
|---|---|---|
AGENTISEND_API_KEY | yes | A key with sending_access. |
MAIL_FROM | yes | The From address, on a domain you have verified. |
AGENTISEND_BASE_URL | no | Defaults to https://api.agentisend.com. |
The route
// Go — one send with the standard library.
//
// There is no Go SDK. This program posts JSON to POST /emails with net/http.
// AGENTISEND_API_KEY is the key. MAIL_FROM is an address on a domain you have
// verified. AGENTISEND_BASE_URL is optional and defaults to the public API.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
)
var address = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)
func mailFrom() string {
from := os.Getenv("MAIL_FROM")
if from == "" {
panic("Set MAIL_FROM to an address on a domain you have verified.")
}
return from
}
func baseURL() string {
base := os.Getenv("AGENTISEND_BASE_URL")
if base == "" {
base = "https://api.agentisend.com"
}
return strings.TrimRight(base, "/")
}
type outcome struct {
Status int
Body map[string]any
}
// send posts one invoice note. The idempotency key is the recipient, so a
// retry of the same note replays, and a different note under that key is
// refused rather than mailed twice.
func send(email, note string) outcome {
email = strings.TrimSpace(email)
if !address.MatchString(email) {
return outcome{Status: 400, Body: map[string]any{
"error": "email must be one address, like you@example.com",
}}
}
payload, err := json.Marshal(map[string]string{
"from": mailFrom(),
"to": email,
"subject": "Your invoice",
"text": "Invoice note: " + note,
})
if err != nil {
panic(err)
}
req, err := http.NewRequest(http.MethodPost, baseURL()+"/emails", bytes.NewReader(payload))
if err != nil {
panic(err)
}
req.Header.Set("authorization", "Bearer "+os.Getenv("AGENTISEND_API_KEY"))
req.Header.Set("content-type", "application/json")
req.Header.Set("accept", "application/json")
req.Header.Set("idempotency-key", "invoice/"+email)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
raw, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var parsed map[string]any
if err := json.Unmarshal(raw, &parsed); err != nil {
panic(fmt.Sprintf("response was not JSON (%d): %s", resp.StatusCode, raw))
}
if resp.StatusCode >= 400 {
errObj, _ := parsed["error"].(map[string]any)
return outcome{Status: resp.StatusCode, Body: map[string]any{
"code": errObj["code"],
"fix": errObj["fix"],
}}
}
return outcome{Status: resp.StatusCode, Body: map[string]any{"id": parsed["id"]}}
}
func emit(step string, result outcome) {
out := map[string]any{"step": step, "status": result.Status}
for key, value := range result.Body {
out[key] = value
}
if err := json.NewEncoder(os.Stdout).Encode(out); err != nil {
panic(err)
}
}
func main() {
emit("send", send("go@example.com", "first"))
emit("retry", send("go@example.com", "first"))
emit("invalid", send("not-an-address", "first"))
emit("refused", send("go@example.com", "second"))
}Try it
go run .The program prints one JSON object per step: the send, the same request again, a malformed address answered before any request, and a changed note under the same key.
What comes back when it goes wrong
Every refused request carries code, message, fix and docs_url. The program prints code and fix with the API's status. The error catalogue lists every code.