Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dchuk avatar

Action Mailer Patterns

  • 2 installs
  • Updated February 9, 2026
  • dchuk/rails_ai_agents

Builds transactional emails in Rails 8 with Action Mailer, covering HTML/text templates, layouts, previews, background delivery, and mailer tests.

About

Implements transactional emails in Rails 8 using Action Mailer with a TDD workflow. A developer uses it when creating email templates, password resets, notification emails, or mailer previews.

  • Covers HTML/text templates, layouts, and development previews
  • Background delivery via Active Job (Solid Queue) with Minitest mailer tests

Action Mailer Patterns by the numbers

  • 2 all-time installs (skills.sh)
  • Ranked #3,774 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dchuk/rails_ai_agents --skill action-mailer-patterns

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2
Last updatedFebruary 9, 2026
Repositorydchuk/rails_ai_agents

What it does

Builds transactional emails in Rails 8 with Action Mailer, covering HTML/text templates, layouts, previews, background delivery, and mailer tests.

Files

SKILL.mdMarkdownGitHub ↗

Action Mailer Patterns for Rails 8

Overview

Action Mailer handles transactional emails:

  • HTML and text email templates
  • Layouts for consistent styling
  • Previews for development
  • Background delivery via Active Job (Solid Queue)
  • Internationalized emails

Quick Start

bin/rails generate mailer User welcome password_reset

Configuration

# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options = { host: "example.com" }

Application Mailer

# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"
  layout "mailer"

  helper_method :app_name

  private

  def app_name
    Rails.application.class.module_parent_name
  end
end

TDD Workflow

Mailer Progress:
- [ ] Step 1: Write mailer test (RED)
- [ ] Step 2: Run test (fails)
- [ ] Step 3: Create mailer method
- [ ] Step 4: Create email templates
- [ ] Step 5: Run test (GREEN)
- [ ] Step 6: Create preview

Testing Mailers (Minitest)

Mailer Test

# test/mailers/user_mailer_test.rb
require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  setup do
    @user = users(:one)
  end

  test "welcome email renders headers" do
    mail = UserMailer.welcome(@user)

    assert_equal I18n.t("user_mailer.welcome.subject"), mail.subject
    assert_equal [@user.email_address], mail.to
    assert_equal ["noreply@example.com"], mail.from
  end

  test "welcome email renders HTML body" do
    mail = UserMailer.welcome(@user)

    assert_includes mail.html_part.body.to_s, @user.name
    assert_includes mail.html_part.body.to_s, "Welcome"
  end

  test "welcome email renders text body" do
    mail = UserMailer.welcome(@user)

    assert_includes mail.text_part.body.to_s, @user.name
  end

  test "welcome email includes login link" do
    mail = UserMailer.welcome(@user)

    assert_includes mail.html_part.body.to_s, new_session_url
  end

  test "password_reset email includes token" do
    token = "reset-token-123"
    mail = UserMailer.password_reset(@user, token)

    assert_equal [@user.email_address], mail.to
    assert_includes mail.html_part.body.to_s, token
  end
end

Testing Delivery

# test/integration/registration_test.rb
require "test_helper"

class RegistrationTest < ActionDispatch::IntegrationTest
  test "registration sends welcome email" do
    assert_enqueued_email_with UserMailer, :welcome do
      post registrations_path, params: {
        registration: { email: "new@example.com", name: "Test", password: "password123" }
      }
    end
  end
end

Testing with perform_enqueued_jobs

# test/integration/notification_test.rb
require "test_helper"

class NotificationTest < ActionDispatch::IntegrationTest
  test "sends notification email" do
    assert_emails 1 do
      perform_enqueued_jobs do
        NotificationMailer.daily_digest(users(:one)).deliver_later
      end
    end
  end
end

Mailer Implementation

Basic Mailer

# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def welcome(user)
    @user = user
    @login_url = new_session_url

    mail(to: @user.email_address, subject: t(".subject"))
  end

  def password_reset(user, token)
    @user = user
    @token = token
    @reset_url = edit_password_url(token: token)
    @expires_in = "24 hours"

    mail(to: @user.email_address, subject: t(".subject"))
  end
end

Mailer with Attachments

class ReportMailer < ApplicationMailer
  def monthly_report(user, report)
    @user = user
    @report = report

    attachments["report-#{Date.current}.pdf"] = report.to_pdf

    mail(to: @user.email_address, subject: t(".subject"))
  end
end

Bundled Notification Pattern

Send one email with multiple notifications instead of many emails:

class NotificationMailer < ApplicationMailer
  def daily_digest(user)
    @user = user
    @notifications = user.notifications.unread.today

    return if @notifications.empty?

    mail(to: @user.email_address, subject: t(".subject", count: @notifications.count))
  end
end

Email Templates

<%# app/views/user_mailer/welcome.html.erb %>
<h1><%= t(".greeting", name: @user.name) %></h1>
<p><%= t(".intro") %></p>
<p><%= link_to t(".login_button"), @login_url, class: "button" %></p>
<%# app/views/user_mailer/welcome.text.erb %>
<%= t(".greeting", name: @user.name) %>

<%= t(".intro") %>

<%= t(".login_prompt") %>: <%= @login_url %>

Delivery Methods

# Background delivery (preferred)
UserMailer.welcome(user).deliver_later

# With delay
UserMailer.welcome(user).deliver_later(wait: 5.minutes)

# Immediate (avoid in production)
UserMailer.welcome(user).deliver_now

Previews

# test/mailers/previews/user_mailer_preview.rb
class UserMailerPreview < ActionMailer::Preview
  def welcome
    user = User.first
    UserMailer.welcome(user)
  end

  def password_reset
    user = User.first
    UserMailer.password_reset(user, "preview-token-123")
  end
end

Access at: http://localhost:3000/rails/mailers

I18n for Emails

# config/locales/mailers/en.yml
en:
  user_mailer:
    welcome:
      subject: "Welcome to Our App!"
      greeting: "Hello %{name}!"
      intro: "Thanks for signing up."
      login_button: "Log In Now"
      login_prompt: "Log in here"
    password_reset:
      subject: "Reset Your Password"

Localized Delivery

class UserMailer < ApplicationMailer
  def welcome(user)
    @user = user
    I18n.with_locale(user.locale || I18n.default_locale) do
      mail(to: @user.email_address, subject: t(".subject"))
    end
  end
end

Checklist

  • [ ] Mailer test written first (RED)
  • [ ] Mailer method created
  • [ ] HTML template created
  • [ ] Text template created
  • [ ] Uses I18n for all text
  • [ ] Preview created
  • [ ] Uses deliver_later (not deliver_now)
  • [ ] All tests GREEN

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.