Last update: 2025-12-13
Sending Emails in Go
Here's a simple implementation of a email/SMTP helper package in Go:
package mailx
import (
"bytes"
"log/slog"
"net/mail"
"net/smtp"
"strings"
"unicode"
)
type Emailer interface {
Email(*Message) error
}
type Message struct {
From mail.Address
To []string
Subject string
PlainTextBody string
}
type MultiEmailer struct {
Emailers []Emailer
Logger *slog.Logger
}
func (e MultiEmailer) Email(msg *Message) (err error) {
for _, v := range e.Emailers {
err = v.Email(msg)
if err != nil {
e.Logger.Error("send email", "error", err)
continue
}
break
}
return err
}
type SMTPEmailer struct {
RelayAddress string // Ex: "smtp.example.org:587"
AuthHost string // Ex: "smtp.example.org".
AuthUsername string // Ex: "myaccount@example.org".
AuthPassword string // Ex: "123456".
}
func (e *SMTPEmailer) Email(msg *Message) error {
auth := smtp.PlainAuth("", e.AuthUsername, e.AuthPassword, e.AuthHost)
return smtp.SendMail(e.RelayAddress, auth, msg.From.Address, msg.To, DATA(msg))
}
// Generates a SMTP DATA string.
func DATA(e *Message) []byte {
d := &bytes.Buffer{}
d.WriteString("From: " + e.From.String() + "\r\n")
d.WriteString("To: " + strings.Join(e.To, "; ") + "\r\n")
d.WriteString("Subject: " + e.Subject + "\r\n")
d.WriteString("MIME-Version: " + "1.0" + "\r\n")
d.WriteString("Content-Type: " + "text/plain" + "\r\n")
d.WriteString("\r\n")
d.WriteString(e.PlainTextBody)
d.WriteString("\r\n")
return d.Bytes()
}
type MockEmailer struct {
Logger *slog.Logger // Mock emails will be logged here.
Err error // To mock failed send.
}
func (e *MockEmailer) Email(msg *Message) error {
e.Logger.Info("mock email",
"error", e.Err,
"from_name", msg.From.Name,
"from_address", msg.From.Address,
"to", msg.To,
"subject", msg.Subject,
"body", msg.PlainTextBody,
)
return e.Err
}
// Sanitizes plain-text body for usage in DATA SMTP command.
func Sanitize(v string) (safe string) {
v = strings.ReplaceAll(v, "\r", "")
for _, c := range v {
// Prevent CR (a malicious user could trigger end of DATA with '\r\n' sequence).
if c == '\r' {
continue
}
// Prevent non-graphic characters (a malicious user could insert ANSI escape sequences).
if !unicode.IsGraphic(c) && c != '\n' {
continue
}
safe += string(c)
}
return safe
}