mail

package module
v0.0.0-...-c2e9f61 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 13 Imported by: 0

README

mail

An email-sending interface for Go: a Mailer that sends a Message, and a Body abstraction so the same message can carry a plain string, raw HTML, a rendered Go template, or (via the mjml subpackage) a rendered MJML document. The smtp subpackage sends over SMTP; keeping the interface in the root package lets application code depend on mail.Mailer without importing an SMTP client or, unless it actually renders MJML, the MJML/wazero runtime.

The root mail package has no dependency of its own: it works in any Go program. It is developed as part of the gp-system tooling and used by the gpsystem kit, but does not require it.

Queued (asynchronous) sending is not part of this package. Dispatch a mail event through your event system and call Mailer.Send from a listener/worker.

Install

go get github.com/gp-system/mail@v0.1.0   # or @latest for the newest tag

MJML rendering and SMTP sending are separate subpackages with their own dependencies, so importing the root package alone stays light:

go get github.com/gp-system/mail/mjml@v0.1.0   # github.com/Boostport/mjml-go (wazero)
go get github.com/gp-system/mail/smtp@v0.1.0   # github.com/wneessen/go-mail + OTel

Usage

Building a message

Message is built fluently; construction never fails outright, but Validate (called by every Mailer.Send) reports a missing recipient or body:

msg := mail.NewMessage().
	WithToNamed("Anna", "anna@example.com").
	WithSubject("Welcome").
	WithHTML("<p>Hi Anna!</p>").
	WithText("Hi Anna!")

WithFrom/WithFromNamed override the driver's configured default sender; WithCc/WithBcc/WithReplyTo add the usual extra addresses; WithAttachment/WithAttachmentReader attach a file, WithEmbed attaches an inline resource referenced from HTML as cid:<filename>.

Bodies
Constructor Content
mail.Text(s) plain-text only
mail.HTML(s).WithText(alt) raw HTML, with an optional text/plain alternative
mail.Template(fsys, name, data).WithTextTemplate(name) html/template HTML part, optional text/template alternative, same fs.FS
mail.Parsed(htmlTpl, textTpl, data) pre-parsed templates, for callers that manage their own template cache
mjml.Template(fsys, name, data).WithTextTemplate(name) MJML source executed as a Go template, then compiled to responsive HTML
mjml.String(source, data) same, from an inline MJML string

mjml.Template/mjml.String take mjml.Minify(bool) and mjml.ValidationLevel("strict"|"soft"|"skip") options; both default to minified output with soft validation.

Sending

Three Mailer implementations ship in the root package for environments without a real transport:

  • mail.NewDiscard() validates and renders, then drops the message (no transport configured, and logging is unwanted).
  • mail.NewLog(logger) renders and logs instead of sending (subject and sizes at Info; the full rendered body at Debug only, since it may carry PII).
  • mail.NewMemory() captures every sent message in-process (Messages(), Reset()); safe for concurrent use, intended for tests.

The smtp subpackage sends over real SMTP:

type Config struct {
	Mail smtp.Config `envPrefix:"MAIL_"`
}

mapping to MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD, MAIL_AUTH, MAIL_TLS, MAIL_FROM_ADDRESS, MAIL_FROM_NAME, MAIL_TIMEOUT. smtp.New/smtp.MustNew build a client and verify the connection with a dial+close at construction, so a misconfigured server fails fast at startup rather than on the first send. smtp.NewIfConfigured/ smtp.MustNewIfConfigured fall back to mail.NewDiscard() when Host is empty, so dev/CI can run without a mail server configured while production still gets a verified SMTP client:

mailer := smtp.MustNewIfConfigured(ctx, cfg.Mail)
err := mailer.Send(ctx, msg)

Design rules

  • The interface has no transport opinion. Mailer/Message/Body live in the root package with no dependency of their own; SMTP (and its go-mail/OTel dependencies) and MJML (and its wazero runtime) are opt-in subpackages, so code that only needs mail.Mailer never pays for either.
  • Render errors are wrapped like transmission errors. Body.Render is called inside the driver's send span (see smtp.Client.Send), so a broken template and a broken SMTP connection surface the same way to callers.
  • The client verifies at construction, not first use. smtp.New dials and closes before returning, so a misconfigured server fails at startup.
  • Recipients are never logged, only counted. Message.Recipients() is documented for span attributes and tests by count only; nothing in this package logs an address.

Documentation

Overview

Package mail defines the email-sending interface used by kit consumers: a Mailer that sends a Message, and a Body abstraction so the same message can carry a plain string, raw HTML, a rendered Go template, or (via the mjml subpackage) a rendered MJML document. The smtp subpackage sends over SMTP; keeping the interface here lets application code depend on mail.Mailer without importing an SMTP client or, unless it actually renders MJML, the MJML/wazero runtime.

Queued (asynchronous) sending is not part of this package. Dispatch a mail event through the events package and call Mailer.Send from a listener; see the mail library docs for the pattern.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoBody = errors.New("mail: no body")

ErrNoBody is returned by Validate/Send when a message has no Body set.

View Source
var ErrNoRecipient = errors.New("mail: no recipient")

ErrNoRecipient is returned by Validate/Send when a message has no To, Cc, or Bcc recipient.

Functions

This section is empty.

Types

type Address

type Address struct {
	Name    string
	Address string
}

Address is a recipient or sender.

type Attachment

type Attachment struct {
	Filename    string
	ContentType string
	Inline      bool
	// contains filtered or unexported fields
}

Attachment is a file attached to, or embedded (inline) in, a Message. Inline attachments are referenced from HTML as cid:<Filename>.

func (Attachment) Reader

func (a Attachment) Reader() io.Reader

Reader returns the attachment content. It is consumed once, when the driver sends the message.

type Body

type Body interface {
	Render(ctx context.Context) (Content, error)
}

Body produces message content at send time. Drivers call Render inside their send span, so render errors are wrapped and traced alongside transmission errors.

func Parsed

func Parsed(html *htmltemplate.Template, text *texttemplate.Template, data any) Body

Parsed returns a Body executing pre-parsed templates against data. Either html or text may be nil.

func Text

func Text(s string) Body

Text returns a plain-text-only body.

type Content

type Content struct {
	HTML string
	Text string
}

Content is the rendered message payload. An empty HTML means the message is text-only; an empty Text means it is HTML-only. At least one must be set.

type HTMLBody

type HTMLBody struct {
	// contains filtered or unexported fields
}

HTMLBody is a raw-HTML body with an optional hand-written text/plain alternative. There is no automatic HTML-to-text derivation: set WithText explicitly, or leave the message HTML-only.

func HTML

func HTML(s string) *HTMLBody

HTML returns a raw-HTML body.

func (*HTMLBody) Render

func (b *HTMLBody) Render(context.Context) (Content, error)

Render implements Body.

func (*HTMLBody) WithText

func (b *HTMLBody) WithText(s string) *HTMLBody

WithText attaches a text/plain alternative.

type Mailer

type Mailer interface {
	Send(ctx context.Context, msg *Message) error
}

Mailer sends one fully-built message synchronously.

func NewDiscard

func NewDiscard() Mailer

NewDiscard returns a Mailer that validates and renders each message but never transmits or logs it.

func NewLog

func NewLog(logger *slog.Logger) Mailer

NewLog returns a Mailer that renders each message and logs it instead of sending. Subject and recipient/rendered-size are logged at Info; the full rendered body is logged at Debug only, since it may carry PII.

type Memory

type Memory struct {
	// contains filtered or unexported fields
}

Memory is a Mailer that renders and captures every message instead of sending it (Laravel's array driver). Safe for concurrent use.

func NewMemory

func NewMemory() *Memory

NewMemory returns an empty Memory mailer.

func (*Memory) Messages

func (m *Memory) Messages() []SentMessage

Messages returns everything sent so far.

func (*Memory) Reset

func (m *Memory) Reset()

Reset clears the captured messages.

func (*Memory) Send

func (m *Memory) Send(ctx context.Context, msg *Message) error

Send renders msg and appends it to the captured list.

type Message

type Message struct {
	// contains filtered or unexported fields
}

Message is built fluently; construction never fails outright, but Validate (called by drivers before Send) reports missing recipients or body.

func NewMessage

func NewMessage() *Message

NewMessage starts an empty message. WithFrom defaults to the driver's configured sender when left unset.

func (*Message) Attachments

func (m *Message) Attachments() []Attachment

Attachments returns a copy of the attached and embedded files, in the order added.

func (*Message) BccAddresses

func (m *Message) BccAddresses() []Address

BccAddresses returns a copy of the Bcc recipients.

func (*Message) CcAddresses

func (m *Message) CcAddresses() []Address

CcAddresses returns a copy of the Cc recipients.

func (*Message) Content

func (m *Message) Content(ctx context.Context) (Content, error)

Content renders the body. Drivers call this inside their send span.

func (*Message) Headers

func (m *Message) Headers() map[string]string

Headers returns a copy of the custom headers set on the message.

func (*Message) Recipients

func (m *Message) Recipients() []Address

Recipients returns To+Cc+Bcc combined, for span attributes and tests — never logged or traced by address, only by count.

func (*Message) ReplyToAddress

func (m *Message) ReplyToAddress() Address

ReplyToAddress returns the configured Reply-To address, or the zero value if unset.

func (*Message) Sender

func (m *Message) Sender() Address

Sender returns the configured From address; the zero value means "use the driver's default sender".

func (*Message) Subject

func (m *Message) Subject() string

Subject returns the message subject.

func (*Message) ToAddresses

func (m *Message) ToAddresses() []Address

ToAddresses returns a copy of the To recipients.

func (*Message) Validate

func (m *Message) Validate() error

Validate reports ErrNoRecipient or ErrNoBody. Drivers call it first in Send.

func (*Message) WithAttachment

func (m *Message) WithAttachment(filename string, data []byte, contentType string) *Message

WithAttachment adds an attachment from memory. contentType may be empty, in which case the driver sniffs it from the filename/content.

func (*Message) WithAttachmentReader

func (m *Message) WithAttachmentReader(filename string, r io.Reader, contentType string) *Message

WithAttachmentReader adds an attachment streamed from r; r is read once, at send time.

func (*Message) WithBcc

func (m *Message) WithBcc(addresses ...string) *Message

WithBcc adds one or more Bcc recipients.

func (*Message) WithBccNamed

func (m *Message) WithBccNamed(name, address string) *Message

WithBccNamed adds a Bcc recipient with a display name.

func (*Message) WithBody

func (m *Message) WithBody(b Body) *Message

WithBody sets the message body. The last call wins.

func (*Message) WithCc

func (m *Message) WithCc(addresses ...string) *Message

WithCc adds one or more Cc recipients.

func (*Message) WithCcNamed

func (m *Message) WithCcNamed(name, address string) *Message

WithCcNamed adds a Cc recipient with a display name.

func (*Message) WithEmbed

func (m *Message) WithEmbed(filename string, data []byte, contentType string) *Message

WithEmbed adds an inline resource, referenced from HTML as cid:<filename>.

func (*Message) WithFrom

func (m *Message) WithFrom(address string) *Message

WithFrom sets the sender address, overriding the driver's configured default.

func (*Message) WithFromNamed

func (m *Message) WithFromNamed(name, address string) *Message

WithFromNamed sets the sender address with a display name.

func (*Message) WithHTML

func (m *Message) WithHTML(s string) *Message

WithHTML sets a raw HTML body. Shorthand for WithBody(mail.HTML(s)).

func (*Message) WithHeader

func (m *Message) WithHeader(key, value string) *Message

WithHeader sets a custom top-level header (e.g. "List-Unsubscribe").

func (*Message) WithReplyTo

func (m *Message) WithReplyTo(address string) *Message

WithReplyTo sets the Reply-To address.

func (*Message) WithSubject

func (m *Message) WithSubject(s string) *Message

WithSubject sets the message subject.

func (*Message) WithText

func (m *Message) WithText(s string) *Message

WithText sets a plain-text body. Shorthand for WithBody(mail.Text(s)).

func (*Message) WithTo

func (m *Message) WithTo(addresses ...string) *Message

WithTo adds one or more To recipients.

func (*Message) WithToNamed

func (m *Message) WithToNamed(name, address string) *Message

WithToNamed adds a To recipient with a display name.

type SentMessage

type SentMessage struct {
	From        Address
	To, Cc, Bcc []Address
	Subject     string
	Content     Content
	Attachments []string // filenames only
}

SentMessage is a rendered, captured message.

type TemplateBody

type TemplateBody struct {
	// contains filtered or unexported fields
}

TemplateBody renders an html/template as the HTML part, with an optional text/template alternative rendered from the same filesystem.

func Template

func Template(fsys fs.FS, name string, data any) *TemplateBody

Template renders name from fsys with html/template, escaping data per HTML context. Pair it with WithTextTemplate for a text/plain alternative.

func (*TemplateBody) Render

func (b *TemplateBody) Render(context.Context) (Content, error)

Render implements Body.

func (*TemplateBody) WithFuncs

func (b *TemplateBody) WithFuncs(fm htmltemplate.FuncMap) *TemplateBody

WithFuncs adds a FuncMap before parsing the HTML template.

func (*TemplateBody) WithTextTemplate

func (b *TemplateBody) WithTextTemplate(name string) *TemplateBody

WithTextTemplate renders name from the same fsys with text/template as the text/plain alternative.

Directories

Path Synopsis
Package mjml renders MJML email bodies as a mail.Body.
Package mjml renders MJML email bodies as a mail.Body.
Package smtp implements mail.Mailer over SMTP using github.com/wneessen/go-mail.
Package smtp implements mail.Mailer over SMTP using github.com/wneessen/go-mail.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL