RailsNotes UI Email Templates & Components

Getting Started

These docs walk you through adding, updating and using the ActionMailer component library in your Ruby on Rails apps.

Requirements

Each ActionMailer UI component is a ViewComponent under the hood. Make sure you have the latest version of the ViewComponent gem installed by adding it to your Gemfile

# Should be included in :development, :test and :production groups
bundle add view_component

Downloading the components.zip file is the easiest way to get started with these ActionMailer components. Visit the components page and click the [Download .zip] button in the ActionMailer components section (make sure you're logged in).

components.zip includes everything you need to get started -

  • components/, which contains every ActionMailer Email:: component.
  • templates/, which contains every ActionMailer template. These are great starting points for your mailer templates.

Copy the components/ folder from components.zip into the app/components directory of your Ruby on Rails app.

Note: If you're already using ViewComponents, you already have an app/components/ folder. In that case, just copy the components/email folder.

This will make the components available in ALL your views (not just your mailer templates). You probably won't use these outside of your emails though, hence the Email:: namespace.

Copy individual components (alternative)

If you don't want to download the components.zip, or if you want to grab the "stock" version of a component (perhaps you went a bit too gung-ho with modifying one 😅), you can do that too.

The source code for each component can be copied directly. Visit the components page and navigate to the components you want to download.

The following components are REQUIRED

  • base.rb — all other components inherit from ::Base
  • colors.rb — most components use colors from this module
  • reset_stylesheet.rb — each ::Container will try to render this module
  • One of the ::Container components (full, plain or bg) — all other email components must be rendered into a ::Container component.

Copy each of these components into a correctly named file inside the app/components/email folder of your Ruby on Rails app.

Note: I highly recommend you just grab the components.zip file and copy the components across that way. It's much easier and will save you some headaches.

Build your mailers

Once you've installed the components correctly, they'll be available in your mailer views under the Email:: namespace. For example, the button component is available as Email::Button.

For more information, I recommend you look at the included mailer templates, which will show you how to correctly use these components (plus some more advanced techniques).

Updating components

Occasionally, the RailsNotes UI ActionMailer component library will be updated. New templates may also be released.

Some updates may include breaking changes to components. I do my best to minimize these (I know that patching components can be a pain!), but they might be needed for bug fixes or major new features. See the changelog for info on the latest updates.

You're never forced to update your components. The code is already in your app! However, keeping your components up to date will give you bug fixes and improvements. New templates will also use the latest versions of components.

If you follow the recommended way to update — overwriting all your components with their new versions — updating should be painless. I strongly recommend you follow the recommended update method.

The recommended way to update your components, as with installing them, is to download the new components.zip file and replace the entire app/components/email folder in your app. Just copy the components/email folder from the new components.zip file into the app/components folder of your Ruby on Rails app, and overwrite the old app/components/email folder.

Replacing the app/components/email folder in your Ruby on Rails app will overwrite any edits you made to your component files! (but not your mailer templates).

Updating individual files (alternative)

You can update components manually by copying across their new versions. You might do this if you've directly edited some components (ie: edited text.rb), and want to selectively update your components to preserve their edits. You can do this by visiting each component's page, and copying across the updated code.

In general, I recommend you avoid updating your components manually (file-by-file). It's error-prone and may lead to bugs. It's a lot easier to copy across the new files from components.zip, and will help preserve your sanity.

Any new templates will also be included in a new version of the components.zip file. As usual, you can also grab them from the RailsNotes UI website.

Why make updating so convoluted? Why no gem?

I want to give you full control over your email components. Emails are a core part of your app! I want to give you the power to dive deep into your components and see what's going on — packaging them as a gem would hinder this. It would also force you to rely on me to keep the gem server live.

By giving you the code directly, you get full control over your components and aren't forced to rely on me. The tradeoff is that updates become a bit more involved.

In general, I plan to avoid updating old components where I can help it! I get that it's cumbersome to update your components (despite my best efforts to streamline things). For bug fixes though, there's not really another option.

As long as you don't edit component files directly, updating should be pretty painless. Just copy across the new components, overwrite the old ones, and (possibly) tweak your custom mailer templates.

Building your mailer templates

This section is a mini-teardown of a typical email template built with the ActionMailer component library. This will help you build your own mailer templates.

A typical mailer template

Here's a basic mailer template, built with RailsNotes UI components —

<%= render Email::Bg::Container.new do |email| %>
  <% email.with_masthead_text(text: "RailsNotes UI", href: "https://railsnotesui.xyz") %>

  <%# Headings and some body text %>
  <%= render Email::Heading.new(text: "Thanks for joining!", align: "center") %>
  <%= render Email::Text.new(text: "Thank you for joining my mailing list!") %>

  <%# Button inside a gray container %>
  <%= render Email::Colorblock.new(color: Email::Colors::GRAY_50) do %>
    <%= render Email::Button.new(
      text: "Buy something now →",
      href: "https://example.com/checkout"
    ) %>
  <% end %>

  <%# Text with a link. We pass a block to Email::Text, so we can use the link_to helper %>
  <%= render Email::Text.new do %>
    If you have any questions, send an email
    to <%= link_to "[email protected]", "[email protected]" %>.
  <% end %>

  <%# The ::Footer is optional, and must be the last component inside the container. %>
  <%= render Email::Footer.new do %>
    © RailsNotes 2023
  <% end %>
<% end %>

What's going on here?

  1. All mailer templates start with a ::Container. The container wraps all the other parts of our email. In this case, we use the Email::Bg::Container component.
  2. We then render an Email::MastheadText component by calling email.with_masthead_text. This is ViewComponent Slot Syntax. A masthead is optional and sits above the main email container.
  3. We then render some text components by passing a text: string to them directly.
  4. Next, we render an Email::Colorblock. The ::Coloblock component wraps other email components and renders them inside a colored container. We override the default color with Email::Colors::GRAY_50, and pass in a block containing a button.
  5. Next, we render some more text, but this time we pass ::Text a block. This lets us use the link_to helper to generate a link.
  6. Finally, we render a ::Footer at the end of our mailer template. Rendering a footer is optional, but it must go at the end of your mailer template (if you use it).

That's it!

Override default styles in base.rb

The Email::Base component, located in base.rb, is unique and worth looking at.

This component is where we define default styles for most other components.

For example, we define default styles for the Email::Text component inside the TextStyles class -


class Email::Base < ViewComponent::Base
  # ...

  class TextStyles
    SIZE = "16px"
    COLOR = Email::Colors::GRAY_900 # [dark variant available]
  end
end

Inside the Email::Text component (see text.rb), we inherit the default styles -


class Email::Text < Email::Base
  def initialize(
    # ...
    size: TextStyles::SIZE,
    color: TextStyles::COLOR,
  )
    @size = size
    @color = color
  end
end

This gives us a way to override the global styles of individual components. If you want to make your body text a bit larger, rather than passing size: "18px" each time you create an Email::Text component, you can adjust it directly in base.rb. You would just set TextStyles.SIZE = '18px'.

I go over this a bit more in the next section, so if you're feeling a bit lost or confused, read on. You can also email me directly at [email protected] if you're having trouble.

Default Values, Colors:: and Dark Mode.

Component Defaults

Most components only have 1-2 required parameters. The other parameters are set by default but can be overridden if you want to customize your components more thoroughly.

For example, look at the Email::Button component —

# app/components/email/button.rb
# #
class Email::Button < Email::Base
  def initialize(
    text:,
    href:,
    color: ButtonStyles::BACKGROUND_COLOR, # inherited from Email::Base
    text_color: ButtonStyles::TEXT_COLOR, # inherited from Email::Base
    margin: "30px 0 30px 0",
    border_radius: "8px"
  )
    ...
  end

  ...
end

The Email::Button component only requires the text: and href: attributes. Everything else has a default value, but you can override them!

Overriding default values

Here are some examples of rendering components and overriding their default values (using the Email::Button component).

In the first example, we don't pass in any parameters, which raises an ArgumentError. In the second example, we only pass the required parameters, and in the third example, we override a default value.

# no params, raises ArgumentError, "missing keyword"
#
Email::Button.new

# required params only
#
Email::Button.new(
  text: "Click me",
  href: "https://example.com"
)

# overriding an optional param
#
Email::Button.new(
  text: "Click me",
  href: "https://example.com",
  color: Email::Colors::GREEN_500 # using the included Colors:: module
)

For more examples check out the example templates, or the file-level comments for each component (which include some usage examples).

Default Values

You can edit the Email::Base class to customize the default styles of your components. For example, you could change all your buttons to be green.

The Email::Base class (inside components/email/base.rb) holds default values for all other components. These values then get inherited by each component.

For example, this is how the default background color of a button is set to Email::Colors::RED_500

# Email::Base contains the default styles for most components
#
class Email::Base < ViewComponent::Base
  ...

    class ButtonStyles
      BACKGROUND_COLOR = Email::Colors::RED_500 # [dark variant available]
      ...
    end

  ...
end

If you wanted to change the default button color to green, you could do BACKGROUND_COLOR = Email::Colors::GREEN_500.

Dark Mode

Some components support dark variants, however, they can only be customized globally inside base.rb. This is because we insert dark variants as @media queries into the Email::Container components.

Inside base.rb, the styling for dark variants lives inside the DarkStyles class —

# Styles for dark mode rendering
# NOTE: These styles can _only_ be configured here (due to how we handle media queries).
#       You can't adjust the dark mode display for individual elements.
class DarkStyles
  # whether dark mode media queries are included in your emails
  # set this false to disable dark mode
  # useful if your mailer templates use dark colors even in "light" mode
  ENABLED = true

  # Email::Container
  CONTAINER_COLOR = Email::Colors::GRAY_800
  CONTAINER_SURROUNDS_COLOR = Email::Colors::GRAY_900
  CONTAINER_TEXT_COLOR = Email::Colors::GRAY_100

  # Email::Button
  BUTTON_COLOR = Email::Colors::GRAY_100
  BUTTON_TEXT_COLOR = Email::Colors::GRAY_900

  # Email::Colorblock
  COLORBLOCK_COLOR = Email::Colors::GRAY_900
end

If you want to change how your email looks in dark mode, you need to edit these values.

You can also completely disable dark mode by setting ENABLED=false. This will remove all the @media queries associated with dark mode from the ::Container components. This can be useful if you're building a template that's dark by default, and dont want anything to change on a dark color scheme.

Colors:: module

I've added a Colors:: module into the project, to save you looking up #hex codes all day.

This module is based on the TailwindCSS color palette and uses naming like Email::Colors::WHITE and Email::Colors::RED_500 to correspond to hex strings.

Using this module is optional! But I find it very handy — It's a great way to quickly tweak the colors in your email, and gives you a good starting point.

If you prefer to use #hex codes though, you can! Anywhere you see a Colors:: value, you can replace it with a plain #hex string. If you peek inside colors.rb, you'll see that this module just maps names like Email::Colors::RED_500 to their matching hex strings.

Top-level documentation

Most components have documentation and examples as file-level comments. The example code should be copy-pastable straight into your ERB templates.

Here's the comment at the top of the Email::Button component -

# Email::Button component [supports dark variant]
# REQUIRED: text:, href:
# NOTES:
# - href: needs to be an absolute url (good="https://example.com", bad="example.com")
#
#
# Example 1 (simple):
# <%= render Email::Button.new(
#       text: "Simple Button",
#       href: "https://example.com",
#     ) %>
#
# Example 2 (override styles):
# <%= render Email::Button.new(
#       text: "Advanced button → ",
#       href: "https://example.com",
#       color: Email::Colors::ORANGE_500,
#       text_color: Email::Colors::ORANGE_50,
#       text_size: "20px",
#       width: "48px",
#       height: "16px",
#       border_radius: "60px",
#       margin: "60px auto 0 auto"
#     ) %>