render

package
v1.19.2 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 6 Imported by: 0

README

Mail Render Package

Go Version

Production-ready email template rendering library for Go with theme support, bidirectional text, template variables, and HTML/plain text generation using the Hermes v2 engine.


Table of Contents


Overview

The mail/render package provides a high-level API for generating professional transactional emails using the Hermes v2 template engine. It wraps Hermes with additional features like theme management, text direction control, template variable replacement, and configuration-based initialization.

Design Philosophy
  1. Template-First: Pre-designed themes for consistent branding
  2. Localization-Ready: Built-in RTL support for international audiences
  3. Type-Safe: Structured configuration with validation
  4. Production-Ready: Deep cloning for concurrent operations
  5. Framework-Agnostic: Works with any SMTP/email sending library

Key Features

  • Multiple Themes: Default (classic) and Flat (modern) visual styles
  • Bidirectional Text: LTR/RTL support for internationalization
  • Template Variables: {{variable}} replacement across all content
  • Rich Content: Tables, actions/buttons, dictionaries, custom markdown
  • Dual Output: Generate both HTML and plain text versions
  • Configuration-Based: JSON/YAML/TOML with validation
  • Thread-Safe Cloning: Deep copy for concurrent email generation
  • CSS Inlining: Optional inline CSS for maximum email client compatibility
  • Structured Errors: Integration with github.com/nabbar/golib/errors

Installation

go get github.com/nabbar/golib/mail/render

Dependencies:

  • github.com/go-hermes/hermes/v2 - Email template engine
  • github.com/go-playground/validator/v10 - Configuration validation
  • github.com/nabbar/golib/errors - Structured error handling

Architecture

Package Structure

The package is organized around the Mailer interface with support components:

render/
├── interface.go         # Mailer interface and New() constructor
├── email.go            # Internal email implementation
├── config.go           # Configuration struct with validation
├── render.go           # HTML/text generation and ParseData
├── themes.go           # Theme enumeration and parsing
├── direction.go        # Text direction enumeration
├── error.go            # Error codes and messages
└── doc.go             # Package documentation
Component Overview
┌───────────────────────────────────────────────────┐
│                 Mailer Interface                  │
│  Theme, Direction, Product, Body, Generate()      │
└──────────────┬─────────────┬────────────┬─────────┘
               │             │            │
      ┌────────▼─────┐  ┌────▼────┐  ┌────▼─────┐
      │   Config     │  │  email  │  │  Hermes  │
      │              │  │         │  │          │
      │ Validation   │  │ State   │  │ Renderer │
      │ NewMailer()  │  │ Clone() │  │ Themes   │
      └──────────────┘  └─────────┘  └──────────┘
Component Purpose Thread-Safe
Mailer Interface for email configuration and generation Via Clone()
Config Structured configuration with validation
email Internal state management ❌ (use Clone())
Hermes Template rendering engine
Workflow Diagram
   User Code
       │
       ▼
   New() / Config.NewMailer()
       │
       ▼
   Configure (SetTheme, SetBody, etc.)
       │
       ▼
   ParseData(variables) [Optional]
       │
       ▼
   GenerateHTML() / GeneratePlainText()
       │
       ▼
   Hermes Rendering Engine
       │
       ▼
   HTML/Text Buffer Output
       │
       ▼
   Send via SMTP (external)

Performance

Generation Benchmarks

Based on comprehensive testing with 123 test specs and 89.6% code coverage:

Operation Duration (Normal) Duration (Race) Memory Notes
New() ~100 ns ~1.5 µs O(1) Struct initialization
Config.NewMailer() ~300 ns ~4 µs O(1) With validation
Clone() Simple ~1 µs ~10 µs O(1) Empty body
Clone() Complex ~10 µs ~100 µs O(n) Deep copy of tables
ParseData Simple ~350 ns ~4 µs O(1) Few variables
ParseData Complex ~1.2 µs ~20 µs O(n*m) Many variables
GenerateHTML Simple ~2.7 ms ~47 ms ~100 KB Basic email
GenerateHTML Complex ~3.1 ms ~51 ms ~200 KB Tables + actions
GeneratePlainText ~3.6 ms ~48 ms ~50 KB Text conversion
ParseTheme ~83 ns ~1.5 µs O(1) String parsing
ParseTextDirection ~54 ns ~495 ns O(1) String parsing
Config.Validate() ~31 µs ~112 µs O(1) Field validation
Complete Workflow ~3.1 ms ~51 ms ~250 KB Config → HTML

Measured on Linux/AMD64, Go 1.21+

Memory Efficiency
  • Email Generation: ~100-200 KB per email (HTML)
  • Plain Text: ~50 KB per email
  • Clone Operations: Proportional to body content size
  • ParseData: In-place replacements (no extra allocation)
Throughput Capacity
Performance Characteristics:
├─ Email Generation: ~300 emails/second (single thread)
├─ With Concurrency: ~3000 emails/second (10 goroutines)
├─ ParseData: ~1M operations/second
└─ Clone: ~100K clones/second (simple bodies)

Bottleneck: Hermes rendering (HTML generation)
Thread Safety Notes
  • Not Thread-Safe: Individual Mailer instances
  • Concurrent Pattern: Clone() for each goroutine
  • Race Detection: Zero data races (verified with -race)
  • Production: Use worker pool pattern for high-volume

Use Cases

This library is designed for transactional email scenarios:

User Onboarding

  • Welcome emails with verification links
  • Account activation and password reset
  • Multi-step onboarding sequences
  • Personalized greetings and instructions

Notifications & Alerts

  • Order confirmations and invoices
  • Shipping notifications with tracking
  • Account activity alerts (login, changes)
  • System status updates

Marketing Automation

  • Newsletter templates
  • Promotional campaigns
  • Event invitations
  • Product announcements

E-Commerce

  • Order receipts with itemized tables
  • Cart abandonment reminders
  • Product review requests
  • Loyalty program updates

SaaS Applications

  • Trial expiration reminders
  • Feature announcement emails
  • Usage reports and analytics
  • Subscription renewals

Quick Start

Basic Email Generation

Simple email with intro and outro:

package main

import (
    "fmt"
    "github.com/nabbar/golib/mail/render"
    "github.com/go-hermes/hermes/v2"
)

func main() {
    // Create mailer
    mailer := render.New()
    mailer.SetName("My Company")
    mailer.SetLink("https://example.com")
    mailer.SetLogo("https://example.com/logo.png")
    mailer.SetTheme(render.ThemeFlat)

    // Configure email body
    body := &hermes.Body{
        Name:   "John Doe",
        Intros: []string{"Welcome to our service!"},
        Outros: []string{"Thank you for signing up."},
    }
    mailer.SetBody(body)

    // Generate HTML
    htmlBuf, err := mailer.GenerateHTML()
    if err != nil {
        panic(err)
    }

    fmt.Println("HTML length:", htmlBuf.Len())
    
    // Generate plain text
    textBuf, _ := mailer.GeneratePlainText()
    fmt.Println("Text length:", textBuf.Len())
}
Configuration-Based Initialization

Using structured configuration (suitable for JSON/YAML/TOML):

package main

import (
    "encoding/json"
    "os"
    "github.com/nabbar/golib/mail/render"
    "github.com/go-hermes/hermes/v2"
)

func main() {
    // Load from JSON config file
    data, _ := os.ReadFile("email-config.json")
    
    var config render.Config
    json.Unmarshal(data, &config)
    
    // Validate configuration
    if err := config.Validate(); err != nil {
        panic(err)
    }
    
    // Create mailer from config
    mailer := config.NewMailer()
    
    // Generate email
    htmlBuf, _ := mailer.GenerateHTML()
}

Example email-config.json:

{
  "theme": "flat",
  "direction": "ltr",
  "name": "My Company",
  "link": "https://example.com",
  "logo": "https://example.com/logo.png",
  "copyright": "© 2024 My Company",
  "troubleText": "Need help? Contact support@example.com",
  "disableCSSInline": false,
  "body": {
    "name": "{{user}}",
    "intros": ["Welcome to our service!"],
    "outros": ["Thank you for signing up."]
  }
}
Email with Tables and Actions

Rich email with itemized data and call-to-action:

body := &hermes.Body{
    Name:   "John Doe",
    Intros: []string{"Your order has been confirmed:"},
    Dictionary: []hermes.Entry{
        {Key: "Order ID", Value: "ORD-123456"},
        {Key: "Date", Value: "2024-01-15"},
        {Key: "Total", Value: "$129.99"},
    },
    Tables: []hermes.Table{{
        Data: [][]hermes.Entry{
            {
                {Key: "Item", Value: "Product A"},
                {Key: "Quantity", Value: "2"},
                {Key: "Price", Value: "$49.99"},
            },
            {
                {Key: "Item", Value: "Product B"},
                {Key: "Quantity", Value: "1"},
                {Key: "Price", Value: "$30.00"},
            },
        },
        Columns: hermes.Columns{
            CustomWidth: map[string]string{
                "Item":     "50%",
                "Quantity": "20%",
                "Price":    "30%",
            },
        },
    }},
    Actions: []hermes.Action{
        {
            Instructions: "View your order details:",
            Button: hermes.Button{
                Text: "View Order",
                Link: "https://example.com/orders/123456",
                Color: "#3869D4",
            },
        },
    },
    Outros: []string{"Need help? Reply to this email."},
}

mailer.SetBody(body)
htmlBuf, _ := mailer.GenerateHTML()
Template Variable Replacement

Dynamic content with variable substitution:

// Configure email with placeholders
mailer.SetName("{{company}}")
body := &hermes.Body{
    Name:   "{{username}}",
    Intros: []string{"Your verification code is {{code}}"},
    Actions: []hermes.Action{
        {
            Instructions: "Click below to verify:",
            Button: hermes.Button{
                Text: "Verify Email",
                Link: "{{verification_url}}",
            },
        },
    },
}
mailer.SetBody(body)

// Replace variables
mailer.ParseData(map[string]string{
    "{{company}}":          "Acme Inc",
    "{{username}}":         "John Doe",
    "{{code}}":             "837492",
    "{{verification_url}}": "https://example.com/verify?token=abc123",
})

// Generate final email
htmlBuf, _ := mailer.GenerateHTML()
Concurrent Email Generation

Thread-safe pattern for bulk email sending:

package main

import (
    "sync"
    "github.com/nabbar/golib/mail/render"
    "github.com/go-hermes/hermes/v2"
)

func main() {
    // Base template
    baseMailer := render.New()
    baseMailer.SetName("My Company")
    baseMailer.SetTheme(render.ThemeFlat)
    
    users := []struct{ Name, Email string }{
        {"John Doe", "john@example.com"},
        {"Jane Smith", "jane@example.com"},
        // ... more users
    }
    
    var wg sync.WaitGroup
    for _, user := range users {
        wg.Add(1)
        go func(name, email string) {
            defer wg.Done()
            
            // Clone for thread safety
            mailer := baseMailer.Clone()
            
            // Customize per user
            body := &hermes.Body{
                Name:   name,
                Intros: []string{"Welcome!"},
            }
            mailer.SetBody(body)
            
            // Generate and send
            htmlBuf, _ := mailer.GenerateHTML()
            sendEmail(email, htmlBuf.String())
        }(user.Name, user.Email)
    }
    
    wg.Wait()
}

Template System

Template Variables

Variables use the {{variable}} syntax and can appear anywhere in the email content:

Supported Locations:

  • Product fields (name, link, logo, copyright, troubleText)
  • Body fields (name, greeting, signature, title)
  • Intro and outro texts
  • Dictionary entries (keys and values)
  • Table data (keys and values)
  • Action instructions and button properties
  • Free-form Markdown content

Example:

// Define template
body := &hermes.Body{
    Name:   "{{user_name}}",
    Title:  "Order {{order_id}}",
    Intros: []string{"Hi {{user_name}}, your order for {{product}} is ready!"},
    Dictionary: []hermes.Entry{
        {Key: "Order", Value: "{{order_id}}"},
        {Key: "Total", Value: "{{total}}"},
    },
}

// Populate variables
mailer.ParseData(map[string]string{
    "{{user_name}}": "John Doe",
    "{{order_id}}":  "ORD-12345",
    "{{product}}":   "Widget Pro",
    "{{total}}":     "$99.99",
})
Email Body Structure

The hermes.Body struct supports rich content:

type hermes.Body struct {
    Name         string           // Recipient name
    Intros       []string         // Opening paragraphs
    Dictionary   []Entry          // Key-value pairs
    Tables       []Table          // Data tables
    Actions      []Action         // Call-to-action buttons
    Outros       []string         // Closing paragraphs
    Greeting     string           // Custom greeting
    Signature    string           // Custom signature
    Title        string           // Email title/subject
    FreeMarkdown Markdown         // Custom HTML/Markdown
}

Content Flow:

  1. Greeting (or default "Hi")
  2. Name
  3. Title (optional)
  4. Intros
  5. Dictionary (if present)
  6. Tables (if present)
  7. Actions (if present)
  8. Outros
  9. Signature (or default "Yours truly")
Tables

Tables display structured data with optional column customization:

table := hermes.Table{
    Title: "Order Items",  // Optional table title
    Data: [][]hermes.Entry{
        {{Key: "Product", Value: "Widget A"}, {Key: "Price", Value: "$10"}},
        {{Key: "Product", Value: "Widget B"}, {Key: "Price", Value: "$20"}},
    },
    Columns: hermes.Columns{
        CustomWidth: map[string]string{
            "Product": "70%",
            "Price":   "30%",
        },
        CustomAlignment: map[string]string{
            "Price": "right",
        },
    },
}
Actions and Buttons

Actions create call-to-action sections with buttons:

action := hermes.Action{
    Instructions: "Click below to confirm:",
    InviteCode:   "ABC-123",  // Optional code display
    Button: hermes.Button{
        Text:      "Confirm Email",
        Link:      "https://example.com/confirm",
        Color:     "#3869D4",     // Primary button color
        TextColor: "#FFFFFF",     // Button text color
    },
}

Themes & Localization

Available Themes

ThemeDefault - Classic email design with centered layout

┌──────────────────────────────────────────┐
│              [LOGO]                      │
│                                          │
│  Hi John Doe,                            │
│                                          │
│  Welcome to our service! We're excited   │
│  to have you on board.                   │
│                                          │
│  ┌────────────────────────────────────┐  │
│  │     [VIEW ORDER]                   │  │
│  └────────────────────────────────────┘  │
│                                          │
│  Thank you,                              │
│  My Company Team                         │
│                                          │
│  © 2024 My Company                       │
└──────────────────────────────────────────┘

ThemeFlat - Modern, minimalist design

───────────────────────────────────────────────
[LOGO]  My Company

Hi John Doe,

Welcome to our service! We're excited to have
you on board.

┌─────────────────────┐
│  VIEW ORDER →       │
└─────────────────────┘

Thank you,
My Company Team

───────────────────────────────────────────────
© 2024 My Company
Theme Selection
// Via constant
mailer.SetTheme(render.ThemeDefault)
mailer.SetTheme(render.ThemeFlat)

// Via string (case-insensitive)
theme := render.ParseTheme("flat")
mailer.SetTheme(theme)

// From configuration
config := render.Config{Theme: "default", ...}
mailer := config.NewMailer()
Text Direction (Internationalization)

Support for both LTR and RTL languages:

LeftToRight (default) - Western languages

mailer.SetTextDirection(render.LeftToRight)
// English, French, Spanish, German, Italian, etc.

RightToLeft - Middle Eastern languages

mailer.SetTextDirection(render.RightToLeft)
// Arabic, Hebrew, Persian, Urdu, etc.

Parsing from String:

// Supported formats (case-insensitive)
dir := render.ParseTextDirection("ltr")     // LeftToRight
dir = render.ParseTextDirection("rtl")      // RightToLeft
dir = render.ParseTextDirection("left-to-right")
dir = render.ParseTextDirection("right-to-left")

RTL Email Example:

mailer := render.New()
mailer.SetTextDirection(render.RightToLeft)
mailer.SetName("شركتي")  // Arabic: "My Company"

body := &hermes.Body{
    Name:   "أحمد",        // Ahmed
    Intros: []string{"مرحبا بك"},  // Welcome
}
mailer.SetBody(body)
CSS Inlining

Control CSS inlining for maximum email client compatibility:

// Enable inline CSS (default, recommended)
mailer.SetCSSInline(false)  // false = inline enabled

// Disable inline CSS (for modern clients)
mailer.SetCSSInline(true)   // true = inline disabled

When to disable CSS inlining:

  • Testing during development
  • Modern email clients only
  • Custom CSS post-processing

Why inline CSS matters:

  • Gmail strips <style> tags
  • Outlook has limited CSS support
  • Mobile clients vary widely
  • Inlining ensures consistent rendering

Best Practices

Configuration Management

✅ Use Config Struct

// Store in config file (JSON/YAML)
config := render.Config{
    Theme: "flat",
    Name:  "My Company",
    // ... other fields
}

// Validate before use
if err := config.Validate(); err != nil {
    log.Fatalf("invalid config: %v", err)
}

mailer := config.NewMailer()

❌ Avoid Hardcoding

// Don't hardcode in every function
func sendEmail() {
    mailer := render.New()
    mailer.SetName("My Company")  // Repeated everywhere
    mailer.SetLink("https://...")
    // ...
}
Error Handling

✅ Check All Errors

htmlBuf, err := mailer.GenerateHTML()
if err != nil {
    if err.Code() == render.ErrorMailerHtml {
        // Handle HTML generation error
    }
    return fmt.Errorf("generate email: %w", err)
}

❌ Ignore Errors

htmlBuf, _ := mailer.GenerateHTML()  // Don't do this!
Resource Cleanup

✅ Always Close Buffers

func generateEmail(mailer render.Mailer) (*bytes.Buffer, error) {
    htmlBuf, err := mailer.GenerateHTML()
    if err != nil {
        return nil, err
    }
    // Buffer returned for use
    return htmlBuf, nil
}
Concurrent Operations

✅ Clone for Each Goroutine

baseMailer := render.New()
// Configure base template...

for _, user := range users {
    go func(u User) {
        mailer := baseMailer.Clone()  // Independent copy
        // Customize and send...
    }(user)
}

❌ Share Mailer Across Goroutines

mailer := render.New()

for _, user := range users {
    go func(u User) {
        mailer.SetBody(...)  // RACE CONDITION!
    }(user)
}
Template Organization

✅ Reusable Templates

// Define template factory
func newWelcomeEmail(name, email string) render.Mailer {
    mailer := render.New()
    // Base configuration
    mailer.SetName("My Company")
    mailer.SetTheme(render.ThemeFlat)
    
    // User-specific
    body := &hermes.Body{
        Name:   name,
        Intros: []string{"Welcome!"},
    }
    mailer.SetBody(body)
    return mailer
}

// Use
mailer := newWelcomeEmail("John", "john@example.com")
Variable Naming

✅ Clear Delimiters

// Use double braces for clarity
"{{user_name}}"
"{{order_id}}"
"{{verification_token}}"

❌ Ambiguous Patterns

// Avoid single braces or short names
"{name}"  // Might conflict with JSON
"$n"      // Unclear what this represents
Testing Emails

✅ Test Both Formats

func TestEmail(t *testing.T) {
    mailer := setupTestMailer()
    
    // Test HTML
    htmlBuf, err := mailer.GenerateHTML()
    assert.NoError(t, err)
    assert.Contains(t, htmlBuf.String(), "Welcome")
    
    // Test plain text
    textBuf, err := mailer.GeneratePlainText()
    assert.NoError(t, err)
    assert.Contains(t, textBuf.String(), "Welcome")
}
Performance Optimization

✅ Reuse Base Mailers

// Create once, clone many
var baseMailer = initializeBase()

func sendToUser(user User) {
    mailer := baseMailer.Clone()
    // Fast, only clones state
}

✅ Batch Variable Replacement

// Single ParseData call
mailer.ParseData(map[string]string{
    "{{name}}":  name,
    "{{email}}": email,
    "{{code}}":  code,
    // ... all variables at once
})

❌ Multiple ParseData Calls

// Inefficient: iterates content multiple times
mailer.ParseData(map[string]string{"{{name}}": name})
mailer.ParseData(map[string]string{"{{email}}": email})
mailer.ParseData(map[string]string{"{{code}}": code})

Testing

Test Suite: 123 specs using Ginkgo v2 and Gomega with gmeasure benchmarks (89.6% coverage)

# Run tests
go test ./...

# With coverage
go test -cover ./...

# With race detection (recommended)
CGO_ENABLED=1 go test -race ./...

# With benchmarks
go test -bench=. ./...

Coverage Areas:

  • Mailer interface operations
  • Theme and text direction parsing
  • Configuration validation
  • HTML and plain text generation
  • Template variable replacement
  • Deep cloning and thread safety
  • Error handling and edge cases
  • Concurrent operations

Quality Assurance:

  • ✅ Zero data races (verified with -race)
  • ✅ Thread-safe Clone() operations
  • ✅ Deep copy validation
  • ✅ 89.6% code coverage

See TESTING.md for detailed testing documentation.


Contributing

Contributions are welcome! Please follow these guidelines:

Code Contributions

  • Do not use AI to generate package implementation code
  • AI may assist with tests, documentation, and bug fixing
  • All contributions must pass CGO_ENABLED=1 go test -race
  • Maintain or improve test coverage (≥85%)
  • Follow existing code style and patterns

Documentation

  • Update README.md for new features
  • Add examples for common use cases
  • Keep TESTING.md synchronized with test changes
  • Update GoDoc comments

Testing

  • Write tests for all new features
  • Test edge cases and error conditions
  • Verify thread safety with race detector
  • Add benchmarks for performance-critical code

Pull Requests

  • Provide clear description of changes
  • Reference related issues
  • Include test results
  • Update documentation

See CONTRIBUTING.md for detailed guidelines.


Future Enhancements

Potential improvements for future versions:

Additional Themes

  • Corporate theme (formal business style)
  • Newsletter theme (multi-section layout)
  • Notification theme (minimal alert style)
  • Custom theme builder API

Enhanced Localization

  • Pre-translated greeting/signature templates
  • Date/time formatting per locale
  • Number formatting (currency, decimals)
  • Language-specific typography

Template Features

  • Conditional sections (if/else)
  • Loops for repeated content
  • Nested variable support
  • Template inheritance

Rich Content

  • Image embedding (data URIs)
  • Custom fonts
  • Progress bars
  • Star ratings
  • Social media buttons

Performance

  • Template caching
  • Compiled templates
  • Streaming generation (large emails)
  • Parallel rendering

Integrations

  • Direct SMTP sending (combine with mail/smtp)
  • Email validation
  • Preview in browser
  • A/B testing support

Developer Experience

  • Email preview server
  • Visual template editor
  • CLI tool for testing
  • Migration helpers (from other libraries)

Suggestions and contributions are welcome via GitHub issues.


AI Transparency Notice

In accordance with Article 50.4 of the EU AI Act, AI assistance has been used for testing, documentation, and bug fixing under human supervision.


License

MIT License - See LICENSE file for details.


Resources

Documentation

Overview

Package render provides email template rendering functionality using the Hermes library. It offers a high-level API for generating both HTML and plain text email content with support for themes, text direction, and template variable replacement.

Overview

The render package wraps the github.com/go-hermes/hermes/v2 library to provide an easy-to-use interface for creating professional-looking transactional emails. It supports multiple themes, bidirectional text, and rich content including tables, actions, and custom formatting.

Key Features

  • Multiple visual themes (Default, Flat)
  • Bidirectional text support (LTR/RTL)
  • Template variable replacement with {{variable}} syntax
  • Rich content support (tables, actions, buttons, dictionary entries)
  • HTML and plain text generation
  • Configuration via struct with validation
  • Deep cloning for concurrent operations
  • Integration with github.com/nabbar/golib/errors for structured error handling

Basic Usage

Creating a simple email:

mailer := render.New()
mailer.SetName("My Company")
mailer.SetLink("https://example.com")
mailer.SetLogo("https://example.com/logo.png")
mailer.SetTheme(render.ThemeFlat)

body := &hermes.Body{
    Name:   "John Doe",
    Intros: []string{"Welcome to our service!"},
    Outros: []string{"Thank you for signing up."},
}
mailer.SetBody(body)

htmlBuf, err := mailer.GenerateHTML()
textBuf, err := mailer.GeneratePlainText()

Configuration-Based Usage

Using a configuration struct (suitable for JSON/YAML/TOML):

config := render.Config{
    Theme:       "flat",
    Direction:   "ltr",
    Name:        "My Company",
    Link:        "https://example.com",
    Logo:        "https://example.com/logo.png",
    Copyright:   "© 2024 My Company",
    TroubleText: "Need help? Contact support@example.com",
    Body: hermes.Body{
        Name:   "John Doe",
        Intros: []string{"Welcome!"},
    },
}

if err := config.Validate(); err != nil {
    // Handle validation error
}

mailer := config.NewMailer()
htmlBuf, err := mailer.GenerateHTML()

Template Variable Replacement

The package supports template variable replacement with the {{variable}} syntax:

mailer := render.New()
mailer.SetName("{{company}}")

body := &hermes.Body{
    Name:   "{{user}}",
    Intros: []string{"Your verification code is {{code}}"},
    Actions: []hermes.Action{
        {
            Instructions: "Click below to verify:",
            Button: hermes.Button{
                Text: "Verify Email",
                Link: "{{verification_link}}",
            },
        },
    },
}
mailer.SetBody(body)

mailer.ParseData(map[string]string{
    "{{company}}":           "Acme Inc",
    "{{user}}":              "John Doe",
    "{{code}}":              "123456",
    "{{verification_link}}": "https://example.com/verify?token=abc123",
})

htmlBuf, err := mailer.GenerateHTML()

Advanced Content

Creating emails with tables and actions:

body := &hermes.Body{
    Name:   "John Doe",
    Intros: []string{"Here is your monthly report:"},
    Dictionary: []hermes.Entry{
        {Key: "Transaction ID", Value: "TXN-123456"},
        {Key: "Date", Value: "2024-01-15"},
    },
    Tables: []hermes.Table{{
        Data: [][]hermes.Entry{
            {
                {Key: "Item", Value: "Product A"},
                {Key: "Quantity", Value: "2"},
                {Key: "Price", Value: "$50.00"},
            },
            {
                {Key: "Item", Value: "Product B"},
                {Key: "Quantity", Value: "1"},
                {Key: "Price", Value: "$30.00"},
            },
        },
        Columns: hermes.Columns{
            CustomWidth: map[string]string{
                "Item":     "50%",
                "Quantity": "20%",
                "Price":    "30%",
            },
        },
    }},
    Actions: []hermes.Action{
        {
            Instructions: "View full details:",
            Button: hermes.Button{
                Text: "View Invoice",
                Link: "https://example.com/invoice/123456",
            },
        },
    },
    Outros: []string{"Thank you for your business!"},
}

mailer.SetBody(body)
htmlBuf, err := mailer.GenerateHTML()

Themes

The package supports multiple themes:

  • ThemeDefault: Classic, centered email design
  • ThemeFlat: Modern, minimalist email design

Theme selection:

mailer.SetTheme(render.ThemeFlat)
// or
theme := render.ParseTheme("flat")
mailer.SetTheme(theme)

Text Direction

Support for both LTR and RTL languages:

// For LTR languages (English, French, Spanish, etc.)
mailer.SetTextDirection(render.LeftToRight)

// For RTL languages (Arabic, Hebrew, Persian, etc.)
mailer.SetTextDirection(render.RightToLeft)

// Or parse from string
direction := render.ParseTextDirection("rtl")
mailer.SetTextDirection(direction)

Thread Safety

Mailer instances are not thread-safe. For concurrent operations, use Clone():

baseMailer := render.New()
baseMailer.SetName("My Company")
baseMailer.SetTheme(render.ThemeFlat)

// Create independent copies for concurrent use
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(index int) {
        defer wg.Done()
        mailer := baseMailer.Clone()
        body := &hermes.Body{
            Name: fmt.Sprintf("User %d", index),
        }
        mailer.SetBody(body)
        htmlBuf, _ := mailer.GenerateHTML()
        // Send email...
    }(i)
}
wg.Wait()

Error Handling

The package uses github.com/nabbar/golib/errors for structured error handling:

htmlBuf, err := mailer.GenerateHTML()
if err != nil {
    if err.Code() == render.ErrorMailerHtml {
        // Handle HTML generation error
    }
    log.Printf("Error: %v", err)
}

Error codes:

  • ErrorParamEmpty: Required parameters are missing
  • ErrorMailerConfigInvalid: Configuration validation failed
  • ErrorMailerHtml: HTML generation failed
  • ErrorMailerText: Plain text generation failed

Integration with Email Sending

The generated content can be used with various email sending packages:

mailer := render.New()
// ... configure mailer ...

htmlBuf, err := mailer.GenerateHTML()
textBuf, err := mailer.GeneratePlainText()

// Using standard net/smtp
msg := "From: sender@example.com\r\n" +
      "To: recipient@example.com\r\n" +
      "Subject: Welcome\r\n" +
      "MIME-Version: 1.0\r\n" +
      "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
      htmlBuf.String()

// Or use github.com/nabbar/golib/mail/smtp for full SMTP support

Dependencies

This package depends on:

  • github.com/go-hermes/hermes/v2: Email template rendering engine
  • github.com/nabbar/golib/errors: Structured error handling
  • github.com/go-playground/validator/v10: Configuration validation

Performance Considerations

  • Email generation typically takes 1-5ms depending on content complexity
  • Clone operations are fast (~1µs for simple, ~10µs for complex content)
  • Template variable replacement is performed in-place with O(n*m) complexity
  • CSS inlining (default) adds processing time but improves email client compatibility

Best Practices

  • Always validate configuration before creating a mailer from Config
  • Use Clone() for concurrent operations to avoid race conditions
  • Generate both HTML and plain text versions for better compatibility
  • Test emails in multiple email clients (Gmail, Outlook, Apple Mail, etc.)
  • Keep logo images under 200KB for faster loading
  • Use HTTPS URLs for all links and images
  • Include clear call-to-action buttons with descriptive text
  • Provide a plain text alternative for accessibility

For complete email workflow:

  • github.com/nabbar/golib/mail/smtp: SMTP client for sending emails
  • github.com/nabbar/golib/mail/sender: High-level email sending with attachments
  • github.com/nabbar/golib/errors: Error handling and logging

References

For more information on email body structure and available fields:

Index

Constants

View Source
const (
	// ErrorParamEmpty indicates that required parameters are missing or empty.
	// This error is returned when mandatory configuration or data is not provided.
	ErrorParamEmpty liberr.CodeError = iota + liberr.MinPkgMailer

	// ErrorMailerConfigInvalid indicates that the mailer configuration is invalid.
	// This error is returned by Config.Validate() when validation fails.
	ErrorMailerConfigInvalid

	// ErrorMailerHtml indicates a failure during HTML email generation.
	// This typically occurs when the Hermes library encounters an error
	// while processing the email template.
	ErrorMailerHtml

	// ErrorMailerText indicates a failure during plain text email generation.
	// This typically occurs when the Hermes library encounters an error
	// while converting the email to plain text format.
	ErrorMailerText
)

Error codes for the render package. These codes are used with the github.com/nabbar/golib/errors package to provide structured error handling.

All error codes start from liberr.MinPkgMailer to avoid conflicts with other packages in the golib ecosystem.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Theme specifies the email template theme ("default" or "flat")
	Theme string `json:"theme,omitempty" yaml:"theme,omitempty" toml:"theme,omitempty" mapstructure:"theme,omitempty" validate:"required"`
	// Direction specifies the text direction ("ltr" or "rtl")
	Direction string `` /* 135-byte string literal not displayed */
	// Name is the product/company name displayed in the email
	Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,omitempty" mapstructure:"name,omitempty" validate:"required"`
	// Link is the primary product/company URL (must be a valid URL)
	Link string `json:"link,omitempty" yaml:"link,omitempty" toml:"link,omitempty" mapstructure:"link,omitempty" validate:"required,url"`
	Logo string `json:"logo,omitempty" yaml:"logo,omitempty" toml:"logo,omitempty" mapstructure:"logo,omitempty" validate:"required,url"`
	// Copyright is the copyright text displayed in the footer
	Copyright string `` /* 135-byte string literal not displayed */
	// TroubleText is the help/support text displayed for troubleshooting
	TroubleText string `` /* 143-byte string literal not displayed */
	// DisableCSSInline controls whether to disable CSS inlining in HTML output
	DisableCSSInline bool `` /* 143-byte string literal not displayed */
	// Body contains the email body content structure
	// See github.com/go-hermes/hermes/v2 Body for field details
	Body libhms.Body `json:"body" yaml:"body" toml:"body" mapstructure:"body" validate:"required"`
}

Config represents the configuration structure for creating a Mailer instance. It supports multiple serialization formats (JSON, YAML, TOML) and includes validation tags for ensuring configuration correctness.

All fields except DisableCSSInline are required and must pass validation. The Link and Logo fields must be valid URLs.

This struct is designed to be used with configuration files or environment variables via libraries like Viper or similar configuration management tools.

Example JSON configuration:

{
  "theme": "flat",
  "direction": "ltr",
  "name": "My Company",
  "link": "https://example.com",
  "logo": "https://example.com/logo.png",
  "copyright": "© 2024 My Company",
  "troubleText": "Need help? Contact support@example.com",
  "disableCSSInline": false,
  "body": {
    "name": "John Doe",
    "intros": ["Welcome to our service!"]
  }
}

func (Config) NewMailer

func (c Config) NewMailer() Mailer

NewMailer creates a new Mailer instance from the configuration. It parses the theme and direction strings and initializes all fields.

Note: This method does not validate the configuration. Call Validate() before NewMailer() to ensure the configuration is valid.

Returns:

  • A configured Mailer instance ready to use

Example:

config := render.Config{
    Theme: "flat",
    Direction: "ltr",
    Name: "My Company",
    Link: "https://example.com",
    Logo: "https://example.com/logo.png",
    Copyright: "© 2024",
    TroubleText: "Need help?",
    Body: hermes.Body{Name: "User"},
}
if err := config.Validate(); err == nil {
    mailer := config.NewMailer()
    htmlBuf, _ := mailer.GenerateHTML()
}

func (Config) Validate

func (c Config) Validate() liberr.Error

Validate validates the Config struct using validator tags. It checks all required fields and validates URL formats for Link and Logo.

Returns:

  • nil if validation succeeds
  • liberr.Error containing all validation errors if validation fails

Validation rules:

  • All fields except DisableCSSInline are required
  • Link must be a valid URL
  • Logo must be a valid URL

Example:

config := render.Config{
    Theme: "flat",
    Direction: "ltr",
    Name: "Company",
    Link: "https://example.com",
    Logo: "https://example.com/logo.png",
    Copyright: "© 2024",
    TroubleText: "Help",
    Body: hermes.Body{},
}
if err := config.Validate(); err != nil {
    // Handle validation errors
}

type Mailer

type Mailer interface {
	// Clone creates a deep copy of the Mailer instance.
	// All slices and nested structures are copied independently to avoid
	// shared references, making the clone safe for concurrent use.
	Clone() Mailer

	// SetTheme sets the visual theme for the email template.
	// Available themes: ThemeDefault, ThemeFlat
	SetTheme(t Themes)
	// GetTheme returns the currently configured theme.
	GetTheme() Themes

	// SetTextDirection sets the text direction for the email content.
	// Use LeftToRight for LTR languages, RightToLeft for RTL languages.
	SetTextDirection(d TextDirection)
	// GetTextDirection returns the currently configured text direction.
	GetTextDirection() TextDirection

	// SetBody sets the email body content.
	// See github.com/go-hermes/hermes/v2 Body for structure details.
	SetBody(b *hermes.Body)
	// GetBody returns the current email body content.
	GetBody() *hermes.Body

	// SetCSSInline controls CSS inlining in HTML output.
	// Set to true to disable CSS inlining (useful for some email clients).
	SetCSSInline(disable bool)

	// SetName sets the product/company name displayed in the email.
	SetName(name string)
	// GetName returns the configured product/company name.
	GetName() string

	// SetCopyright sets the copyright text displayed in the email footer.
	SetCopyright(copy string)
	// GetCopyright returns the configured copyright text.
	GetCopyright() string

	// SetLink sets the primary product/company link (usually a URL).
	SetLink(link string)
	// GetLink returns the configured product/company link.
	GetLink() string

	SetLogo(logoUrl string)
	GetLogo() string

	// SetTroubleText sets the help/support text displayed when actions fail.
	// Example: "Having trouble? Contact support@example.com"
	SetTroubleText(text string)
	// GetTroubleText returns the configured trouble text.
	GetTroubleText() string

	// ParseData replaces template variables in all email content.
	// Variables in the format "{{key}}" are replaced with corresponding values.
	// This affects all text fields including product info, body content, tables, and actions.
	//
	// Example:
	//   data := map[string]string{
	//       "{{user}}": "John Doe",
	//       "{{code}}": "123456",
	//   }
	//   mailer.ParseData(data)
	ParseData(data map[string]string)

	// GenerateHTML generates the HTML version of the email.
	// Returns a buffer containing the complete HTML document.
	GenerateHTML() (*bytes.Buffer, liberr.Error)
	// GeneratePlainText generates the plain text version of the email.
	// Returns a buffer containing formatted plain text content.
	GeneratePlainText() (*bytes.Buffer, liberr.Error)
}

Mailer defines the interface for generating and managing email templates. It provides methods to configure email properties, manage template data, and generate both HTML and plain text versions of emails.

The Mailer uses the github.com/go-hermes/hermes/v2 library for template rendering and supports features like:

  • Theme selection (Default, Flat)
  • Text direction (LTR, RTL)
  • Template variable replacement
  • Product information (name, logo, link, copyright)
  • Rich body content (intros, outros, tables, actions)

Thread Safety: Mailer instances are not thread-safe. Use Clone() to create independent copies for concurrent operations.

Example:

mailer := render.New()
mailer.SetName("My Company")
mailer.SetTheme(render.ThemeFlat)
body := &hermes.Body{
    Name: "John Doe",
    Intros: []string{"Welcome to our service!"},
}
mailer.SetBody(body)
htmlBuf, err := mailer.GenerateHTML()

func New

func New() Mailer

New creates a new Mailer instance with default configuration. The default configuration includes:

  • Theme: ThemeDefault
  • Text Direction: LeftToRight
  • CSS Inlining: Enabled
  • Empty product information and body content

Example:

mailer := render.New()
mailer.SetName("My Company")
mailer.SetLink("https://example.com")

type TextDirection

type TextDirection uint8

TextDirection represents the text reading direction for email content. This affects how the email content is laid out and displayed in email clients.

Common use cases:

  • LeftToRight: For languages like English, French, Spanish, etc.
  • RightToLeft: For languages like Arabic, Hebrew, Persian, etc.
const (
	// LeftToRight indicates left-to-right text direction.
	// Used for most Western languages (English, French, Spanish, German, etc.).
	LeftToRight TextDirection = iota

	// RightToLeft indicates right-to-left text direction.
	// Used for RTL languages (Arabic, Hebrew, Persian, Urdu, etc.).
	RightToLeft
)

func ParseTextDirection

func ParseTextDirection(direction string) TextDirection

ParseTextDirection parses a text direction string and returns the corresponding TextDirection enum. The parsing is case-insensitive and supports multiple formats.

Supported formats:

  • "ltr", "LTR" -> LeftToRight
  • "rtl", "RTL" -> RightToLeft
  • "left", "left-to-right", "Left->Right" -> LeftToRight
  • "right", "right-to-left", "Right->Left" -> RightToLeft

If the direction string is not recognized or empty, LeftToRight is returned as the default.

Example:

dir := render.ParseTextDirection("rtl")
dir = render.ParseTextDirection("right-to-left")
dir = render.ParseTextDirection("RTL")
dir = render.ParseTextDirection("unknown") // Returns LeftToRight

func (TextDirection) String

func (d TextDirection) String() string

String returns the string representation of the text direction.

Returns:

  • "Left->Right" for LeftToRight
  • "Right->Left" for RightToLeft

Example:

dir := render.RightToLeft
fmt.Println(dir.String()) // Output: "Right->Left"

type Themes

type Themes uint8

Themes represents the available email template themes. Each theme provides a different visual style for the generated emails.

The themes are based on github.com/go-hermes/hermes/v2 theme implementations.

const (
	// ThemeDefault is the default theme provided by Hermes.
	// It offers a classic, clean email design with a centered layout.
	ThemeDefault Themes = iota

	// ThemeFlat is the flat theme provided by Hermes.
	// It offers a modern, minimalist email design with flat colors.
	ThemeFlat
)

func ParseTheme

func ParseTheme(theme string) Themes

ParseTheme parses a theme name string and returns the corresponding Themes enum value. The parsing is case-insensitive.

Supported theme names:

  • "default" -> ThemeDefault
  • "flat" -> ThemeFlat

If the theme name is not recognized, ThemeDefault is returned.

Example:

theme := render.ParseTheme("flat")
theme = render.ParseTheme("FLAT")     // Also works
theme = render.ParseTheme("unknown")  // Returns ThemeDefault

func (Themes) String

func (t Themes) String() string

String returns the string representation of the theme. The string value matches the theme name from the Hermes library.

Example:

theme := render.ThemeFlat
fmt.Println(theme.String()) // Output: "flat"

Jump to

Keyboard shortcuts

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