mailer

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

README

mailer

Email delivery with multiple providers. Start with this complete local dry run:

Usage

package main

import (
	"context"
	"log"

	hatmaxlog "hatmax.adrianpk.com/log"
	"hatmax.adrianpk.com/mailer"
)

func main() {
	mail := mailer.NewNoopMailer(hatmaxlog.NewTestLogger("info"))
	msg := &mailer.Message{
		From:    mailer.Address{Email: "noreply@example.com", Name: "Example"},
		To:      []mailer.Address{{Email: "user@example.com"}},
		Subject: "Welcome",
		HTML:    "<h1>Hello</h1>",
		Text:    "Hello",
	}
	if err := mail.Send(context.Background(), msg); err != nil {
		log.Fatal(err)
	}
}

No-op validates and logs the sender, recipients and subject without delivering mail. Active constructors are NewSMTPMailer(SMTPConfig), NewMailgunMailer(MailgunConfig), NewSendGridMailer(SendGridConfig) and NewSESMailer(ctx, SESConfig). SES construction also returns an error. Provider configuration and transport acceptance do not establish inbox receipt.

SMTP, Mailgun, SendGrid, and SES apply DefaultFrom before validation when msg.From.Email is empty. An explicit sender overrides the default. The complete default address, including its name, is used without changing msg. If neither address supplies an email, delivery fails before transport. Direct Message.Validate and NoopMailer still require an explicit sender.

Custom Message.Headers must have non-empty printable ASCII names without spaces or colons and values without CR or LF. All providers reject unsafe headers before transport; SMTP and SES raw MIME also check before serialization. Pass unfolded values, not pre-folded lines. Valid values remain unchanged. See the message reference.

SMTP honors required STARTTLS (StartTLS=true) and implicit TLS (TLS=true). Implicit TLS takes precedence when both are enabled. With neither enabled, SMTP upgrades opportunistically when the server advertises STARTTLS, otherwise allows plaintext. Failed TLS negotiation never falls back to plaintext. Certificates are verified unless InsecureSkipVerify is explicitly enabled. The entire SMTP transaction is limited to 30 seconds or a shorter caller deadline; cancellation closes the connection. See the transport reference.

Mailgun supports Message.Attachments through multipart file parts. Filenames and bytes are preserved, including empty files; an omitted MIME type uses application/octet-stream. Missing filenames or invalid MIME types fail before delivery. Messages without attachments retain URL-encoded delivery.

Runtime Resolution

With a loaded *config.Config, a log.Logger and optional mailer.SettingsProvider, choose one constructor in the composition root:

// Static configuration.
staticMail := mailer.New(cfg, logger)

// Alternative: apply settings overrides at construction.
settingsMail := mailer.NewWithSettings(settingsService, cfg, logger)

Settings are read once during construction, using a background context; changing them later requires constructing a new mailer. A successful settings read replaces its static value; a failed read retains it. Disabled mail, disabled/dry_run modes, unknown modes/providers, missing Mailgun or SendGrid credentials, and SES initialization failures resolve to no-op. SMTP errors surface on Send. Empty mode means active and empty provider means SMTP when enabled. See Mailer Reference.

API

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

Documentation

Index

Constants

View Source
const (
	SettingEnabled            = "mailer.enabled"
	SettingMode               = "mailer.mode"
	SettingProvider           = "mailer.provider"
	SettingFromEmail          = "mailer.from_email"
	SettingFromName           = "mailer.from_name"
	SettingSMTPHost           = "mailer.smtp_host"
	SettingSMTPPort           = "mailer.smtp_port"
	SettingSMTPUsername       = "mailer.smtp_username"
	SettingSMTPPassword       = "mailer.smtp_password"
	SettingSMTPTLS            = "mailer.smtp_tls"
	SettingSMTPStartTLS       = "mailer.smtp_starttls"
	SettingSMTPInsecureSkip   = "mailer.smtp_insecure_skip_verify"
	SettingMailgunAPIKey      = "mailer.mailgun_api_key"
	SettingMailgunDomain      = "mailer.mailgun_domain"
	SettingMailgunBaseURL     = "mailer.mailgun_base_url"
	SettingSendGridAPIKey     = "mailer.sendgrid_api_key"
	SettingSESRegion          = "mailer.ses_region"
	SettingSESAccessKeyID     = "mailer.ses_access_key_id"
	SettingSESSecretAccessKey = "mailer.ses_secret_access_key"
	SettingSESConfigSetName   = "mailer.ses_configuration_set_name"
)
View Source
const (
	ModeDisabled = "disabled"
	ModeDryRun   = "dry_run"
	ModeActive   = "active"
)

Variables

View Source
var Schemas = []settings.Schema{
	{
		Key:         SettingEnabled,
		Type:        settings.Bool,
		Default:     "false",
		Label:       "Enable Mailer",
		Description: "Enable mail delivery provider",
	},
	{
		Key:         SettingMode,
		Type:        settings.Enum,
		Default:     ModeDisabled,
		Label:       "Mailer Runtime Mode",
		Description: "Controls mail delivery execution mode",
		Options:     []string{ModeDisabled, ModeDryRun, ModeActive},
		Labels:      []string{"Disabled", "Dry Run", "Active"},
	},
	{
		Key:         SettingProvider,
		Type:        settings.Enum,
		Default:     "smtp",
		Label:       "Mailer Provider",
		Description: "Mail delivery provider",
		Options:     []string{"smtp", "mailgun", "sendgrid", "ses"},
		Labels:      []string{"SMTP", "Mailgun", "SendGrid", "AWS SES"},
	},
	{Key: SettingFromEmail, Type: settings.String, Default: "noreply@localhost", Label: "From Email", MaxLength: 255},
	{Key: SettingFromName, Type: settings.String, Label: "From Name", MaxLength: 255},
	{Key: SettingSMTPHost, Type: settings.String, Label: "SMTP Host", MaxLength: 255},
	{Key: SettingSMTPPort, Type: settings.Int, Default: "587", Label: "SMTP Port"},
	{Key: SettingSMTPUsername, Type: settings.String, Label: "SMTP Username", MaxLength: 255},
	{Key: SettingSMTPPassword, Type: settings.String, Label: "SMTP Password", Secret: true, MaxLength: 255},
	{Key: SettingSMTPTLS, Type: settings.Bool, Default: "false", Label: "SMTP TLS"},
	{Key: SettingSMTPStartTLS, Type: settings.Bool, Default: "false", Label: "SMTP STARTTLS"},
	{Key: SettingSMTPInsecureSkip, Type: settings.Bool, Default: "false", Label: "SMTP Insecure Skip Verify"},
	{Key: SettingMailgunAPIKey, Type: settings.String, Label: "Mailgun API Key", Secret: true, MaxLength: 255},
	{Key: SettingMailgunDomain, Type: settings.String, Label: "Mailgun Domain", MaxLength: 255},
	{Key: SettingMailgunBaseURL, Type: settings.String, Label: "Mailgun Base URL", MaxLength: 255},
	{Key: SettingSendGridAPIKey, Type: settings.String, Label: "SendGrid API Key", Secret: true, MaxLength: 255},
	{Key: SettingSESRegion, Type: settings.String, Default: "us-east-1", Label: "SES Region", MaxLength: 64},
	{Key: SettingSESAccessKeyID, Type: settings.String, Label: "SES Access Key ID", Secret: true, MaxLength: 255},
	{Key: SettingSESSecretAccessKey, Type: settings.String, Label: "SES Secret Access Key", Secret: true, MaxLength: 255},
	{Key: SettingSESConfigSetName, Type: settings.String, Label: "SES Configuration Set Name", MaxLength: 255},
}

Functions

func RegisterSchemas

func RegisterSchemas(r *settings.Registry)

RegisterSchemas registers mailer settings schemas.

Types

type Address

type Address struct {
	Email string
	Name  string
}

Address represents an email address with optional display name.

func (Address) String

func (a Address) String() string

String returns the RFC 5322 formatted address.

type Attachment

type Attachment struct {
	Filename    string
	ContentType string
	Data        []byte
}

Attachment represents an email attachment.

type Config

type Config struct {
	// DefaultFrom replaces an empty Message.From.Email before active-provider
	// validation. Send does not change the caller's message.
	DefaultFrom Address
	Provider    string
}

Config holds common mailer configuration.

type Mailer

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

Mailer defines the interface for sending emails.

func New

func New(cfg *config.Config, log log.Logger) Mailer

New resolves mailer from static configuration.

func NewWithSettings

func NewWithSettings(settings SettingsProvider, cfg *config.Config, log log.Logger) Mailer

NewWithSettings resolves mailer from static config and dynamic settings overrides.

type MailgunConfig

type MailgunConfig struct {
	Config
	APIKey  string
	Domain  string
	BaseURL string
}

MailgunConfig holds Mailgun-specific configuration.

type MailgunMailer

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

MailgunMailer sends emails via Mailgun API.

func NewMailgunMailer

func NewMailgunMailer(cfg MailgunConfig) *MailgunMailer

NewMailgunMailer creates a new Mailgun mailer.

func (*MailgunMailer) Send

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

Send sends an email via Mailgun.

type Message

type Message struct {
	From        Address
	To          []Address
	CC          []Address
	BCC         []Address
	ReplyTo     *Address
	Subject     string
	Text        string
	HTML        string
	Attachments []Attachment
	// Headers holds unfolded custom fields. Names use printable ASCII except
	// spaces and colons; values must not contain CR or LF.
	Headers map[string]string
}

Message represents an email message.

func (*Message) AllRecipients

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

AllRecipients returns all recipients (To + CC + BCC).

func (*Message) HasAttachments

func (m *Message) HasAttachments() bool

HasAttachments returns true if the message has attachments.

func (*Message) IsMultipart

func (m *Message) IsMultipart() bool

IsMultipart returns true if the message has both text and HTML parts.

func (*Message) Validate

func (m *Message) Validate() error

Validate checks required fields and custom-header safety before delivery.

type NoopMailer

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

NoopMailer is a mailer that logs messages instead of sending them.

func NewNoopMailer

func NewNoopMailer(log log.Logger) *NoopMailer

NewNoopMailer creates a new noop mailer.

func (*NoopMailer) Send

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

Send logs the message instead of sending it.

type SESConfig

type SESConfig struct {
	Config
	Region               string
	AccessKeyID          string
	SecretAccessKey      string
	ConfigurationSetName string
}

SESConfig holds AWS SES-specific configuration.

type SESMailer

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

SESMailer sends emails via AWS Simple Email Service.

func NewSESMailer

func NewSESMailer(ctx context.Context, cfg SESConfig) (*SESMailer, error)

NewSESMailer creates a new AWS SES mailer.

func (*SESMailer) GetSendQuota

func (m *SESMailer) GetSendQuota(ctx context.Context) (*SESQuota, error)

GetSendQuota returns the current sending quota.

func (*SESMailer) Send

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

Send sends an email via AWS SES.

func (*SESMailer) Verify

func (m *SESMailer) Verify(ctx context.Context, email string) error

Verify sends a verification email to an address (required for sandbox mode).

type SESQuota

type SESQuota struct {
	Max24HourSend   float64
	SentLast24Hours float64
	MaxSendRate     float64
}

SESQuota represents SES sending quota.

func (*SESQuota) RemainingQuota

func (q *SESQuota) RemainingQuota() float64

RemainingQuota returns the remaining emails that can be sent in 24h.

type SMTPConfig

type SMTPConfig struct {
	Config
	Host     string
	Port     int
	Username string
	Password string
	// TLS requires implicit TLS and takes precedence over StartTLS.
	TLS bool
	// StartTLS requires an advertised, successful upgrade when TLS is false.
	// When both flags are false, an advertised upgrade remains opportunistic.
	StartTLS bool
	// InsecureSkipVerify disables certificate verification for both TLS paths.
	InsecureSkipVerify bool
}

SMTPConfig holds SMTP-specific configuration.

type SMTPMailer

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

SMTPMailer sends emails via SMTP.

func NewSMTPMailer

func NewSMTPMailer(cfg SMTPConfig) *SMTPMailer

NewSMTPMailer creates a new SMTP mailer.

func (*SMTPMailer) Send

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

Send sends an email via SMTP, respecting cancellation and a total 30-second transport timeout. A shorter caller deadline takes precedence.

type SendGridConfig

type SendGridConfig struct {
	Config
	APIKey string
}

SendGridConfig holds SendGrid-specific configuration.

type SendGridMailer

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

SendGridMailer sends emails via SendGrid API.

func NewSendGridMailer

func NewSendGridMailer(cfg SendGridConfig) *SendGridMailer

NewSendGridMailer creates a new SendGrid mailer.

func (*SendGridMailer) Send

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

Send sends an email via SendGrid.

type SettingsProvider

type SettingsProvider interface {
	GetString(ctx context.Context, key string) (string, error)
	GetInt(ctx context.Context, key string) (int, error)
	GetBool(ctx context.Context, key string) (bool, error)
}

SettingsProvider provides dynamic settings used to resolve mailer runtime configuration.

Jump to

Keyboard shortcuts

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