mailer

package
v0.3.5 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package mailer provides a universal email sending interface with template rendering.

The package separates email sending (via providers) from template rendering, allowing easy swapping of email providers while keeping the same template system.

Architecture

The package consists of three main components:

  • Sender: Interface that email providers implement
  • Renderer: Converts markdown templates with YAML frontmatter to HTML
  • Mailer: High-level client combining Sender and Renderer

Usage

Basic usage with the built-in Resend provider:

import (
	"context"
	"os"

	"github.com/dmitrymomot/forge/pkg/mailer"
	"github.com/dmitrymomot/forge/pkg/mailer/resend"
)

func main() {
	ctx := context.Background()

	// Create the provider
	sender := resend.New(resend.Config{
		APIKey:      os.Getenv("RESEND_API_KEY"),
		SenderEmail: "team@example.com",
		SenderName:  "Team",
	})

	// Create the renderer with embedded templates
	renderer := mailer.NewRenderer(emails.FS, mailer.RendererConfig{})

	// Create the mailer
	m := mailer.New(sender, renderer, mailer.Config{
		FallbackSubject: "Notification",
		DefaultLayout:   "base.html",
	})

	// Send templated email
	err := m.Send(ctx, mailer.SendParams{
		To:       "user@example.com",
		Template: "welcome.md",
		Data:     map[string]any{"Name": "John"},
	})
	if err != nil {
		panic(err)
	}
}

Templates

Templates are markdown files with optional YAML frontmatter:

---
Subject: Welcome {{.Name}}!
---

# Welcome

Hello {{.Name}}, welcome to our service!

[!button|Get Started]({{.URL}})

Subject fields support Go template syntax ({{.Variable}}) for dynamic subjects.

Sending Emails

Mailer provides two methods for sending emails:

  • Send: Renders a template and sends the email
  • SendRaw: Sends a pre-built Email without rendering

SendParams supports optional overrides for subject, layout, sender, reply-to, CC, BCC, and attachments.

Email Tags

The Email type supports provider-specific tags for categorization:

email := &mailer.Email{
	To:      []string{"user@example.com"},
	Subject: "Welcome",
	HTML:    "<p>Hello!</p>",
	Tags:    mailer.SimpleTags("welcome", "onboarding"),
}

Custom Providers

Implement the Sender interface to add support for other email providers:

type MySender struct{}

func (s *MySender) Send(ctx context.Context, email *mailer.Email) error {
	// Send email using your provider's API
	return nil
}

// Use with mailer
m := mailer.New(&MySender{}, renderer, cfg)

Background Jobs

Users create their own typed tasks for background email delivery:

type SendWelcomeTask struct {
	mailer *mailer.Mailer
}

type WelcomePayload struct {
	Email string
	Name  string
}

func (t *SendWelcomeTask) Name() string { return "send_welcome" }

func (t *SendWelcomeTask) Handle(ctx context.Context, p WelcomePayload) error {
	return t.mailer.Send(ctx, mailer.SendParams{
		To:       p.Email,
		Template: "welcome.md",
		Data:     p,
	})
}

Errors

The package defines several error variables for specific failure cases:

  • ErrNoRecipient: No recipient specified
  • ErrNoSubject: No subject provided
  • ErrNoContent: No HTML content provided
  • ErrTemplateNotFound: Template file not found
  • ErrLayoutNotFound: Layout file not found
  • ErrRenderFailed: Template rendering failed
  • ErrSendFailed: Email sending failed
  • ErrInvalidFrontmatter: Invalid YAML frontmatter

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoRecipient indicates no recipient was specified.
	ErrNoRecipient = errors.New("email must have at least one recipient")

	// ErrNoSubject indicates no subject was provided.
	ErrNoSubject = errors.New("email must have a subject")

	// ErrNoContent indicates no HTML content was provided.
	ErrNoContent = errors.New("email must have HTML content")

	// ErrTemplateNotFound indicates the template file was not found.
	ErrTemplateNotFound = errors.New("template not found")

	// ErrLayoutNotFound indicates the layout file was not found.
	ErrLayoutNotFound = errors.New("layout not found")

	// ErrRenderFailed indicates template rendering failed.
	ErrRenderFailed = errors.New("failed to render template")

	// ErrSendFailed indicates email sending failed.
	ErrSendFailed = errors.New("failed to send email")

	// ErrInvalidFrontmatter indicates invalid YAML frontmatter.
	ErrInvalidFrontmatter = errors.New("invalid frontmatter")
)
View Source
var KindButton = ast.NewNodeKind("Button")

KindButton is the node kind for ButtonNode.

Functions

func NewButtonExtension

func NewButtonExtension() goldmark.Extender

NewButtonExtension creates a new button extension for goldmark.

func NewButtonParser

func NewButtonParser() parser.InlineParser

NewButtonParser creates a new button inline parser.

func NewButtonRenderer

func NewButtonRenderer(opts ...html.Option) renderer.NodeRenderer

NewButtonRenderer creates a new button node renderer.

func Recipient

func Recipient(name, email string) string

Recipient formats a name and email into RFC 5322 address format. Returns "Name <email>" if name is provided, otherwise just email.

Types

type Attachment

type Attachment struct {
	Filename    string // Display name for the attachment
	ContentType string // MIME type (e.g., "application/pdf")
	ContentID   string // Optional Content-ID for inline attachments
	Content     []byte // Raw file content
}

Attachment represents an email attachment.

type ButtonExtension

type ButtonExtension struct{}

ButtonExtension is a goldmark extension for button links.

func (*ButtonExtension) Extend

func (e *ButtonExtension) Extend(m goldmark.Markdown)

type ButtonNode

type ButtonNode struct {
	ast.BaseInline
	URL   []byte
	Label []byte
}

ButtonNode represents a button link in the AST.

func (*ButtonNode) Dump

func (n *ButtonNode) Dump(source []byte, level int)

func (*ButtonNode) Kind

func (n *ButtonNode) Kind() ast.NodeKind

type Config

type Config struct {
	FallbackSubject string `env:"FALLBACK_SUBJECT" envDefault:"Notification"`
	DefaultLayout   string `env:"DEFAULT_LAYOUT" envDefault:"base.html"`
}

Config holds mailer configuration. Embed this in your app config for env parsing with caarlos0/env.

type Email

type Email struct {
	Headers     map[string]string // Custom headers
	Tags        Tags              // Provider-specific tags/categories
	Subject     string            // Email subject
	HTML        string            // HTML body content
	Text        string            // Plain text alternative
	From        string            // Override default sender (if provider allows)
	ReplyTo     string            // Reply-to address
	To          []string          // Recipients (at least one required)
	CC          []string          // Carbon copy recipients
	BCC         []string          // Blind carbon copy recipients
	Attachments []Attachment      // File attachments
}

Email represents a fully-prepared email message ready for sending.

type Mailer

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

Mailer provides high-level email sending with template rendering.

func New

func New(sender Sender, renderer *Renderer, cfg Config) *Mailer

New creates a new Mailer with the given sender and renderer.

func (*Mailer) Send

func (m *Mailer) Send(ctx context.Context, params SendParams) error

Send renders a template and sends an email. Subject resolution: params.Subject > template metadata > config fallback.

func (*Mailer) SendRaw

func (m *Mailer) SendRaw(ctx context.Context, email *Email) error

SendRaw sends a pre-built email without template rendering.

type RenderResult

type RenderResult struct {
	Metadata map[string]any
	HTML     string
	Text     string // Plain text from processed markdown (before HTML conversion)
}

RenderResult contains the rendered HTML, plain text, and extracted metadata.

type Renderer

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

Renderer converts markdown templates with YAML frontmatter to HTML.

func NewRenderer

func NewRenderer(filesystem fs.FS, cfg RendererConfig) *Renderer

NewRenderer creates a new renderer with the given config.

func (*Renderer) Render

func (r *Renderer) Render(layout, templateName string, data any) (*RenderResult, error)

Render processes a markdown template with layout. Returns the rendered HTML, plain text, and extracted metadata.

type RendererConfig

type RendererConfig struct {
	TemplateDir string `env:"TEMPLATE_DIR" envDefault:"."`
	LayoutDir   string `env:"LAYOUT_DIR"   envDefault:"layouts"`
}

RendererConfig configures the renderer.

type SendParams

type SendParams struct {
	To       string // Single recipient (most common case)
	Template string // Template filename (e.g., "welcome.md")
	Data     any    // Template data

	// Optional overrides
	Subject     string       // Override template subject
	Layout      string       // Override default layout
	From        string       // Override default sender
	ReplyTo     string       // Reply-to address
	CC          []string     // Carbon copy
	BCC         []string     // Blind carbon copy
	Attachments []Attachment // File attachments
}

SendParams contains parameters for sending a templated email.

type Sender

type Sender interface {
	// Send delivers an email message.
	// The Email must have To, Subject, and HTML already set.
	// Returns an error if delivery fails.
	Send(ctx context.Context, email *Email) error
}

Sender defines the minimal interface that email providers must implement. It accepts a fully-prepared Email and handles the actual delivery.

type Tags

type Tags map[string]any

Tags represents email tags/categories that can be either presence-only (using struct{}{}) or key-value pairs (using string values). This abstraction works across different email providers:

  • Postmark: uses only tag names
  • Resend: uses name-value pairs (presence-only tags become name="true")

func SimpleTags

func SimpleTags(names ...string) Tags

SimpleTags creates presence-only tags from a list of tag names. These are converted to appropriate format by each provider adapter.

type Template

type Template struct {
	Metadata map[string]any
	Body     string
}

Template represents an email template with metadata and body.

func ParseTemplate

func ParseTemplate(content []byte) (*Template, error)

ParseTemplate parses a template file content and extracts front matter metadata and markdown body.

Directories

Path Synopsis
Package smtp provides an SMTP adapter that implements mailer.Sender using only the Go standard library.
Package smtp provides an SMTP adapter that implements mailer.Sender using only the Go standard library.

Jump to

Keyboard shortcuts

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