Pre-order

Action Mailer

Phlex components excel at creating email templates, though email styling requires a different approach due to the limitations and inconsistencies of email clients. Unlike web browsers, email clients have varying levels of CSS support, making inline styles the most reliable approach.

Using with Action Mailer

Integrating Phlex components with Action Mailer is straightforward and provides the same benefits you get with web views: better performance, type safety, and Ruby’s composability.

Basic Action Mailer integration

First, create your email-specific component base class:

class Email::Base < Phlex::HTML
  # Include any email-specific helpers
  include Rails.application.routes.url_helpers

  # Set default host for URL generation
  def default_url_options
    { host: Rails.application.config.action_mailer.default_url_options[:host] }
  end
end

Then use Phlex components in your mailer classes:

🔓 Unlock content

Pre-order this course to unlock this video, source code, and content. You'll also get to work with Brad to fine tune the course cirriculum.

Pre-order video course for $379 $249
class UserMailer < ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
  def ▓▓▓▓▓▓▓▓▓▓▓▓▓(▓▓▓▓)
    ▓▓▓▓▓ = ▓▓▓▓
    ▓▓▓▓(
      to: ▓▓▓▓▓.▓▓▓▓▓,
      subject: 'Welcome to our application!'
    ) do |format|
      format.▓▓▓▓ { ▓▓▓▓▓▓ ▓▓▓▓▓::▓▓▓▓▓▓▓▓▓▓▓▓.▓▓▓(▓▓▓▓▓) }
      format.▓▓▓▓ { ▓▓▓▓▓▓ ▓▓▓▓▓::▓▓▓▓▓▓▓▓▓▓▓▓.▓▓▓(▓▓▓▓▓, format: :text) }
    end
  end
end

Creating email templates

▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓

class Email::WelcomeEmail < ▓▓▓▓▓::▓▓▓▓
  def ▓▓▓▓▓▓▓▓▓▓(▓▓▓▓, format: :html)
    ▓▓▓▓▓ = ▓▓▓▓
    ▓▓▓▓▓▓▓ = format
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓
    if ▓▓▓▓▓▓▓ == :html
      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    else
      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    end
  end

  private

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    ▓▓▓▓▓▓ ▓▓▓▓▓::▓▓▓▓▓▓.▓▓▓(title: "Welcome!") do
      ▓▓(style: ▓▓▓▓▓▓▓▓▓▓▓▓[:heading]) { "Welcome, #{▓▓▓▓▓.▓▓▓▓}!" }
      p(style: ▓▓▓▓▓▓▓▓▓▓▓▓[:paragraph]) do
        ▓▓▓▓▓ "Thanks for joining our application. "
        (href: ▓▓▓▓▓▓▓▓▓▓▓▓▓, style: ▓▓▓▓▓▓▓▓▓▓▓▓[:link]) { "Get started here" }
        ▓▓▓▓▓ "."
      end
    end
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    ▓▓▓▓▓ <<~▓▓▓▓
      Welcome, #{▓▓▓▓▓.▓▓▓▓}!

      Thanks for joining our application.
      Get started here: #{▓▓▓▓▓▓▓▓▓▓▓▓▓}
    ▓▓▓▓
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓
    {
      heading: {
        color: '#333333',
        font_family: 'Arial, sans-serif',
        font_size: '24px',
        margin: '0 0 16px 0'
      },
      paragraph: {
        color: '#666666',
        font_family: 'Arial, sans-serif',
        font_size: '16px',
        line_height: '1.5',
        margin: '0 0 16px 0'
      },
      link: {
        color: '#007bff',
        text_decoration: 'none'
      }
    }
  end
end

Styling emails

▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓

▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓

Email-specific component library

▓▓▓▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓

class Email::Layout < ▓▓▓▓▓::▓▓▓▓
  def ▓▓▓▓▓▓▓▓▓▓(title: nil)
    ▓▓▓▓▓▓ = ▓▓▓▓▓
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓(&▓▓▓▓▓▓▓)
    ▓▓▓▓ do
      ▓▓▓▓ do
        ▓▓▓▓(charset: "utf-8")
        ▓▓▓▓(name: "viewport", content: "width=device-width, initial-scale=1.0")
        ▓▓▓▓▓ { ▓▓▓▓▓▓ } if ▓▓▓▓▓▓
      end

      ▓▓▓▓(style: ▓▓▓▓▓▓▓▓▓▓▓) do
        ▓▓▓▓▓(style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓) do
          ▓▓ do
            ▓▓ do
              ▓▓▓▓▓▓ ▓▓▓▓▓::▓▓▓▓▓▓.▓▓▓
              ▓▓▓(style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓, &▓▓▓▓▓▓▓)
              ▓▓▓▓▓▓ ▓▓▓▓▓::▓▓▓▓▓▓.▓▓▓
            end
          end
        end
      end
    end
  end

  private

  def ▓▓▓▓▓▓▓▓▓▓▓
    {
      margin: '0',
      padding: '0',
      background_color: '#f4f4f4',
      font_family: 'Arial, sans-serif'
    }
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      width: '100%',
      max_width: '600px',
      margin: '0 auto',
      background_color: '#ffffff'
    }
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      padding: '20px',
      color: '#333333',
      font_size: '16px',
      line_height: '1.6'
    }
  end
end

class Email::Header < ▓▓▓▓▓::▓▓▓▓
  def ▓▓▓▓▓▓▓▓▓▓▓▓▓
    ▓▓▓(style: ▓▓▓▓▓▓▓▓▓▓▓▓▓) do
      (href: ▓▓▓▓▓▓▓▓, style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓) do
        ▓▓▓(
          src: ▓▓▓▓▓▓▓▓▓('logo.png'),
          alt: 'Company Logo',
          style: ▓▓▓▓▓▓▓▓▓▓▓
        )
      end
    end
  end

  private

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      padding: '20px',
      text_align: 'center',
      background_color: '#ffffff',
      border_bottom: '1px solid #eeeeee'
    }
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      text_decoration: 'none'
    }
  end

  def ▓▓▓▓▓▓▓▓▓▓▓
    {
      max_width: '200px',
      height: 'auto',
      display: 'block',
      margin: '0 auto'
    }
  end
end

class Email::Footer < ▓▓▓▓▓::▓▓▓▓
  def ▓▓▓▓▓▓▓▓▓▓▓▓▓
    ▓▓▓(style: ▓▓▓▓▓▓▓▓▓▓▓▓▓) do
      p(style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓) do
        ▓▓▓▓▓ #{▓▓▓▓.▓▓▓▓▓▓▓.▓▓▓▓} Your Company Name. All rights reserved."
      end
      p(style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓) do
        (href: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓, style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓) { "Unsubscribe" }
        ▓▓▓▓▓ " | "
        (href: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓, style: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓) { "Privacy Policy" }
      end
    end
  end

  private

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      padding: '20px',
      text_align: 'center',
      background_color: '#f8f9fa',
      border_top: '1px solid #eeeeee'
    }
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      margin: '0 0 10px 0',
      font_size: '12px',
      color: '#666666',
      line_height: '1.4'
    }
  end

  def ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
    {
      color: '#007bff',
      text_decoration: 'none'
    }
  end
end

Email styling best practices

  1. ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓
  2. ▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓
  3. ▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓
  4. ▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓
  5. ▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓ ▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓
  6. ▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓

Testing email components

▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓

▓ ▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓
require 'rails_helper'

▓▓▓▓▓.▓▓▓▓▓▓▓▓ ▓▓▓▓▓::▓▓▓▓▓▓▓▓▓▓▓▓ do
  ▓▓▓(:user) { ▓▓▓▓▓▓(:user, name: 'John Doe', email: 'john@example.com') }
  ▓▓▓(:component) { ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓.▓▓▓(▓▓▓▓) }

  ▓▓ 'renders welcome message' do
    ▓▓▓▓ = ▓▓▓▓▓▓▓▓▓.▓▓▓▓
    ▓▓▓▓▓▓(▓▓▓▓).▓▓ include('Welcome, John Doe!')
    ▓▓▓▓▓▓(▓▓▓▓).▓▓ include('Get started here')
  end

  ▓▓ 'includes proper email structure' do
    ▓▓▓▓ = ▓▓▓▓▓▓▓▓▓.▓▓▓▓
    ▓▓▓▓▓▓(▓▓▓▓).▓▓ include('<table')
    ▓▓▓▓▓▓(▓▓▓▓).▓▓ include('style=')
  end
end

▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓