Skip to content

gusdeyw/go-htmlemail

Repository files navigation

htmlemail

Go Version MIT License Build Status

A powerful and flexible Go package for generating dynamic HTML email content from templates. Focus on creating beautiful, data-driven HTML emails without worrying about SMTP complexities.

Features

  • 🎨 Dynamic HTML Generation - Transform templates with placeholder replacement
  • Multiple Template Styles - Support for $variable$, {{variable}}, and %variable% formats
  • 🏗️ Fluent API - Chainable methods for clean, readable code
  • File & String Templates - Load templates from files or create them inline
  • 🔧 Struct Mapping - Automatically map struct fields to template variables
  • 🎯 Advanced Templates - Go template engine support with loops and conditionals
  • CSS Integration - Add styles inline or in <head> section
  • Well Tested - Comprehensive test coverage with edge cases
  • 🔍 Template Validation - Detect unresolved placeholders and missing data

Installation

go get -u github.com/gusdeyw/htmlemail

Quick Start

Your Original Approach (Enhanced)

If you're coming from a simple placeholder replacement approach:

package main

import "github.com/gusdeyw/htmlemail"

func main() {
    // Load HTML template
    html, _ := htmlemail.ReadHTMLFile("./templates/booking.html")
    
    // Replace placeholders (your original approach)
    html, _ = htmlemail.InsertHTMLInformation(html, "hotel_name", "Grand Hotel")
    html, _ = htmlemail.InsertHTMLInformation(html, "guest_name", "John Doe")
    html, _ = htmlemail.InsertHTMLInformation(html, "booking_code", "BK001")
    
    // html now contains the final email content
}

Template-Based Approach (Recommended)

For more complex scenarios with better structure:

package main

import "github.com/gusdeyw/htmlemail"

func main() {
    // Create template from string
    template := htmlemail.NewEmailTemplateFromString(`
        <h1>Hello $name$!</h1>
        <p>Your order #$order_id$ totaling $total$ is ready.</p>
    `)
    
    // Set variables and render
    html, err := template.
        SetPlaceholder("name", "Alice").
        SetPlaceholder("order_id", "12345").
        SetPlaceholder("total", "$99.99").
        Render()
        
    if err != nil {
        panic(err)
    }
    
    // Use html for email sending
}

Struct-Based Population

Perfect for existing data structures:

type BookingData struct {
    HotelName    string
    GuestName    string
    BookingCode  string
    CheckIn      string
    CheckOut     string
    RoomType     string
    TotalAmount  string
}

func main() {
    booking := BookingData{
        HotelName:   "Luxury Resort",
        GuestName:   "Jane Smith", 
        BookingCode: "BK002",
        CheckIn:     "2024-12-25",
        CheckOut:    "2024-12-28",
        RoomType:    "Ocean Suite",
        TotalAmount: "$1,200.00",
    }

    template := htmlemail.NewEmailTemplateFromString(`
        <h1>$hotel_name$ - Booking Confirmation</h1>
        <p>Dear $guest_name$,</p>
        <p>Booking: $booking_code$</p>
        <p>Dates: $check_in$ to $check_out$</p>
        <p>Room: $room_type$</p>
        <p>Total: $total_amount$</p>
    `)
    
    // Automatically maps struct fields to template variables
    html := template.SetStructData(booking).RenderSafe()
}

Table Generation

The package includes powerful HTML table generation capabilities, perfect for invoices, reports, and data tables in emails.

Your Fixed-Style Table (Exact Match)

For data with date, rate_type, and amount fields (matching your original approach):

tableData := []map[string]interface{}{
    {
        "date":      "2024-12-01",
        "rate_type": "Standard Rate", 
        "amount":    150.00,
    },
    {
        "date":      "2024-12-02", 
        "rate_type": "Weekend Rate",
        "amount":    200.50,
    },
}

// Your exact styling with automatic totals
tableHTML := htmlemail.BuildFixedStyledHTMLTable(tableData)

Configurable Tables

For flexible table generation with custom styling:

options := htmlemail.TableOptions{
    TableStyle:  `cellpadding="5" cellspacing="0" style="border-collapse:collapse;width:100%"`,
    HeaderStyle: `style="background-color:#4CAF50;color:white;padding:10px"`,
    CellStyle:   `style="padding:8px;border:1px solid #ddd"`,
    Columns: []htmlemail.TableColumn{
        {Key: "product", Header: "Product Name"},
        {Key: "price", Header: "Price", Style: `style="text-align:right"`},
        {Key: "quantity", Header: "Qty"},
    },
    ShowTotal:   true,
    TotalColumn: "price", // Which column to sum
}

tableHTML := htmlemail.BuildHTMLTable(data, options)

Embedding Tables in Templates

// Generate table
tableHTML := htmlemail.BuildFixedStyledHTMLTable(invoiceData)

// Embed in email template
template := htmlemail.LoadTemplateFromString(`
    <h1>Invoice for $customer_name$</h1>
    <p>Date: $invoice_date$</p>
    
    <h2>Charges:</h2>
    $charges_table$
    
    <p>Total Amount: $total_amount$</p>
`)

email, _ := template.
    SetVariable("customer_name", "John Doe").
    SetVariable("invoice_date", "2024-12-04").
    SetVariable("charges_table", tableHTML).
    SetVariable("total_amount", "$625.75").
    Render()

Helper Functions

// Convert various types to float64
amount, err := htmlemail.ToFloat64("123.45") // Returns 123.45

// Format currency
formatted := htmlemail.FormatMoneyFromFloat(123.45) // Returns "$123.45"

API Reference

Template Loading

// From file
template, err := htmlemail.LoadTemplate("./templates/email.html")

// From string  
template := htmlemail.LoadTemplateFromString("<h1>Hello $name$</h1>")

Setting Template Data

// Single variable
template.SetVariable("name", "John")

// Multiple variables
template.SetVariables(map[string]interface{}{
    "name": "John",
    "age":  25,
})

// From struct (auto-converts CamelCase to snake_case)
template.SetStruct(userData)

Rendering Templates

// Render with error checking (fails on unresolved placeholders)
html, err := template.Render()

// Render safely (ignores unresolved placeholders)  
html := template.RenderSafe()

// Render with specific placeholder style
html, err := template.RenderWithStyle(htmlemail.BraceStyle) // {{variable}}

// Render only specific variables
html, err := template.RenderPartial([]string{"name", "email"})

HTML Minification

Reduce HTML size by removing unnecessary whitespace and comments:

// Minify standalone HTML
minifiedHTML := htmlemail.MinifyHTML(`<html>  <body>  <h1>Hello World</h1>  </body>  </html>`)
// Result: <html><body><h1>Hello World</h1></body></html>

// Render template and minify output
html, err := template.RenderMinified()

// Render with specific style and minify
html, err := template.RenderWithStyleMinified(htmlemail.BraceStyle)

// Render safely and minify
html := template.RenderSafeMinified()

// Render with Go templates and minify
html, err := template.RenderWithGoTemplateMinified()

EmailBuilder with Minification

html, err := htmlemail.NewEmailBuilder().
    SetHTML("<html>  <body>  <h1>$title$</h1>  </body>  </html>").
    SetData("title", "Welcome").
    EnableMinification(). // Enable HTML minification
    Build()
// Result: <html><body><h1>Welcome</h1></body></html>

Advanced EmailBuilder

For complex emails with CSS and advanced features:

html, err := htmlemail.NewEmailBuilder().
    LoadHTMLFromFile("./templates/newsletter.html").
    AddCSSFromFile("./styles/email.css").
    SetInlineCSS(true).
    SetData("title", "Monthly Newsletter").
    SetData("month", "December").
    SetDataFromStruct(newsletterData).
    Build()

Go Template Engine

For advanced templating with loops and conditionals:

template := htmlemail.LoadTemplateFromString(`
    <h1>Hello {{.CustomerName}}!</h1>
    <ul>
    {{range .Items}}
        <li>{{.Name}} - ${{.Price}}</li>
    {{end}}
    </ul>
    {{if gt .Total 100}}
        <p>Free shipping applied!</p>
    {{end}}
`)

data := map[string]interface{}{
    "CustomerName": "Bob",
    "Items": []map[string]interface{}{
        {"Name": "Product A", "Price": 50},
        {"Name": "Product B", "Price": 75},
    },
    "Total": 125,
}

html, err := template.SetVariables(data).RenderWithGoTemplate()

Placeholder Styles

The package supports multiple placeholder formats:

// Dollar style (default, matches your original approach)
"Hello $name$, your order $order_id$ is ready"

// Brace style (Go template compatible)  
"Hello {{name}}, your order {{order_id}} is ready"

// Percent style
"Hello %name%, your order %order_id% is ready"

Legacy Functions

For backward compatibility with your original approach:

// Read HTML file (equivalent to your ReadHTMLFile)
html, err := htmlemail.ReadHTMLFile("./template.html")

// Single replacement (equivalent to your InsertHTMLInformation)
html, err := htmlemail.InsertHTMLInformation(html, "name", "John")

// Batch replacement (enhanced version)
replacements := map[string]string{
    "hotel_name": "Grand Hotel",
    "guest_name": "John Doe", 
    "booking_code": "BK001",
}
html, err := htmlemail.BatchInsertHTMLInformation(html, replacements)

Examples

Recreating Your Booking Email Function

Here's how to recreate your CreateEmailBodyForBooking function using this package:

type BookingEmailStruct struct {
    HotelName     string
    HotelAddress  string  
    HotelEmail    string
    Name          string
    Email         string
    Phone         string
    BookingCode   string
    BookingDate   string
    BookingStatus string
    ArrivalDate   string
    DepartureDate string
    RoomType      string
    RoomPrice     string
    NumberOfRooms string
}

func CreateEmailBodyForBooking(data BookingEmailStruct) (string, error) {
    // Load template (instead of hardcoded path)
    template, err := htmlemail.LoadTemplate("./email_template/book_details.html")
    if err != nil {
        return "", err
    }

    // Use struct-based population for cleaner code
    return template.SetStruct(data).Render()
}

// Or using the batch approach (closer to your original)
func CreateEmailBodyForBookingBatch(data BookingEmailStruct) (string, error) {
    html, err := htmlemail.ReadHTMLFile("./email_template/book_details.html")
    if err != nil {
        return "", err
    }

    replacements := map[string]string{
        "hotel_name":      data.HotelName,
        "hotel_address":   data.HotelAddress,
        "hotel_email":     data.HotelEmail,
        "name":            data.Name,
        "email":           data.Email,
        "phone":           data.Phone,
        "booking_code":    data.BookingCode,
        "booking_date":    data.BookingDate,
        "booking_status":  data.BookingStatus,
        "arrival_date":    data.ArrivalDate,
        "departure_date":  data.DepartureDate,
        "room_type":       data.RoomType,
        "room_price":      data.RoomPrice,
        "number_of_rooms": data.NumberOfRooms,
    }

    return htmlemail.BatchInsertHTMLInformation(html, replacements)
}

Newsletter with Dynamic Content

func CreateNewsletter(articles []Article, subscriber Subscriber) (string, error) {
    template := htmlemail.LoadTemplateFromString(`
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                .header { background: #3498db; color: white; padding: 20px; }
                .article { margin: 20px 0; padding: 15px; border-left: 3px solid #3498db; }
            </style>
        </head>
        <body>
            <div class="header">
                <h1>$newsletter_title$</h1>
                <p>Hello $subscriber_name$!</p>
            </div>
            
            {{range .Articles}}
            <div class="article">
                <h2>{{.Title}}</h2>
                <p>{{.Summary}}</p>
                <a href="{{.URL}}">Read More</a>
            </div>
            {{end}}
            
            <p>Thanks for subscribing!</p>
        </body>
        </html>
    `)

    data := map[string]interface{}{
        "Articles": articles,
    }

    return template.
        SetVariable("newsletter_title", "Tech Weekly").
        SetVariable("subscriber_name", subscriber.Name).
        SetVariables(data).
        RenderWithGoTemplate()
}

Error Handling

// Check for unresolved placeholders
template := htmlemail.LoadTemplateFromString("Hello $name$, your $item$ is ready")
template.SetVariable("name", "John")

unresolved := template.GetUnresolvedPlaceholders()
if len(unresolved) > 0 {
    fmt.Printf("Missing data for: %v\n", unresolved) // Output: [item]
}

// Render safely (ignores missing placeholders)
html := template.RenderSafe() // Output: "Hello John, your $item$ is ready"

// Or render strictly (returns error for missing placeholders)
html, err := template.Render()
if err != nil {
    log.Printf("Template error: %v", err)
}

Testing

Run the test suite:

go test -v

Run tests with coverage:

go test -v -cover

Run the examples:

cd example && go run main.go

Performance

  • Zero allocations for simple placeholder replacement
  • Compiled templates cached for repeated use
  • Batch operations for multiple replacements
  • Lazy evaluation - only processes used variables

License

This project is licensed under the MIT License - see the LICENSE file for details.

Roadmap

  • Dynamic placeholder replacement
  • Multiple placeholder styles
  • Struct-based template population
  • Go template engine integration
  • CSS integration and inlining
  • HTML minification
  • Email template library/gallery
  • HTML minification
  • Template inheritance/layout system
  • Advanced CSS inlining with external stylesheets
  • Template debugging and preview tools
  • Security enhancements (HTML sanitization, CSP headers)
  • Performance optimizations (template caching, streaming rendering)
  • Email service integrations (SendGrid, Mailgun, AWS SES)
  • Internationalization (i18n) support with locale-aware formatting
  • Accessibility features (alt text validation, semantic HTML checks)
  • CLI tools for template validation and preview
  • Plugin system for custom template functions
  • Monitoring and metrics (rendering performance, error tracking)
  • IDE integrations (VS Code extension, syntax highlighting)
  • Advanced table features (sorting, filtering, pagination)
  • Template versioning and migration tools
  • Web-based template editor and preview interface

About

A powerful and flexible Go package for generating dynamic HTML email content from templates. Focus on creating beautiful, data-driven HTML emails without worrying about SMTP complexities.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors