Julien's dev blog

Gist: Send emails (via SMTP relay) in Go

Send emails (via SMTP relay) in Go

Last updated on: 2025-01-04

package mailx

import (
	"bytes"
	"log/slog"
	"net/mail"
	"net/smtp"
	"strings"
)

type Emailer interface {
	Email(*Message) error
}

type Message struct {
	From          mail.Address
	To            []string
	Subject       string
	PlainTextBody string
}

type SMTPEmailer struct {
	AuthUsername string // Ex: "myaccount@example.org".
	AuthPassword string // Ex: "123456".
	AuthHost     string // Ex: "example.org".
	RelayAddress string // Ex: "smtp.example.org:587"
}

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
}
pkg/mailx/email.go