Action Mailer Patterns for Rails 8
Overview
Action Mailer handles transactional emails in Rails:
-
HTML and text email templates
-
Layouts for consistent styling
-
Previews for development
-
Background delivery via Active Job
-
Internationalized emails
Quick Start
Generate mailer
bin/rails generate mailer User welcome password_reset
This creates:
- app/mailers/user_mailer.rb
- app/views/user_mailer/welcome.html.erb
- app/views/user_mailer/welcome.text.erb
- spec/mailers/user_mailer_spec.rb (if using RSpec)
Project Structure
app/ ├── mailers/ │ ├── application_mailer.rb # Base mailer │ └── user_mailer.rb ├── views/ │ ├── layouts/ │ │ └── mailer.html.erb # Email layout │ └── user_mailer/ │ ├── welcome.html.erb │ ├── welcome.text.erb │ ├── password_reset.html.erb │ └── password_reset.text.erb spec/ ├── mailers/ │ ├── user_mailer_spec.rb │ └── previews/ │ └── user_mailer_preview.rb
TDD Workflow
Mailer Progress:
- Step 1: Write mailer spec (RED)
- Step 2: Run spec (fails)
- Step 3: Create mailer method
- Step 4: Create email templates
- Step 5: Run spec (GREEN)
- Step 6: Create preview
- Step 7: Test delivery integration
Configuration
Base Setup
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" } config.action_mailer.smtp_settings = { address: "smtp.example.com", port: 587, user_name: Rails.application.credentials.smtp[:user_name], password: Rails.application.credentials.smtp[:password], authentication: "plain", enable_starttls_auto: true }
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
Testing Mailers
Mailer Spec
spec/mailers/user_mailer_spec.rb
require "rails_helper"
RSpec.describe UserMailer, type: :mailer do describe "#welcome" do let(:user) { create(:user, email_address: "user@example.com", name: "John") } let(:mail) { described_class.welcome(user) }
it "renders the headers" do
expect(mail.subject).to eq(I18n.t("user_mailer.welcome.subject"))
expect(mail.to).to eq(["user@example.com"])
expect(mail.from).to eq(["noreply@example.com"])
end
it "renders the HTML body" do
expect(mail.html_part.body.to_s).to include("John")
expect(mail.html_part.body.to_s).to include("Welcome")
end
it "renders the text body" do
expect(mail.text_part.body.to_s).to include("John")
expect(mail.text_part.body.to_s).to include("Welcome")
end
it "includes login link" do
expect(mail.html_part.body.to_s).to include(new_session_url)
end
end
describe "#password_reset" do let(:user) { create(:user) } let(:token) { "reset-token-123" } let(:mail) { described_class.password_reset(user, token) }
it "renders the headers" do
expect(mail.subject).to eq(I18n.t("user_mailer.password_reset.subject"))
expect(mail.to).to eq([user.email_address])
end
it "includes reset link with token" do
expect(mail.html_part.body.to_s).to include(token)
end
it "expires link information" do
expect(mail.html_part.body.to_s).to include("24 hours")
end
end end
Testing Delivery
spec/services/user_registration_service_spec.rb
RSpec.describe UserRegistrationService do describe "#call" do it "sends welcome email" do expect { described_class.new.call(user_params) }.to have_enqueued_mail(UserMailer, :welcome) end end end
Integration test
RSpec.describe "User Registration", type: :request do it "sends welcome email after registration" do expect { post registrations_path, params: valid_params }.to have_enqueued_mail(UserMailer, :welcome) 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
attachments.inline["logo.png"] = File.read(Rails.root.join("app/assets/images/logo.png"))
mail(to: @user.email_address, subject: t(".subject"))
end end
Mailer with Dynamic Sender
class NotificationMailer < ApplicationMailer def notify(recipient, sender, message) @recipient = recipient @sender = sender @message = message
mail(
to: @recipient.email_address,
from: "#{@sender.name} <notifications@example.com>",
reply_to: @sender.email_address,
subject: t(".subject", sender: @sender.name)
)
end end
Email Templates
Always create both HTML and text versions. Use I18n for all text content.
See templates.md for complete HTML template, text template, and email layout examples.
Previews
Create previews so you can visually verify emails during development without sending them.
See previews.md for basic previews and previews with multiple states.
Internationalization
Use I18n.with_locale inside the mailer method to send emails in the user's preferred language.
See i18n.md for locale file examples (EN/FR) and localized delivery implementation.
Delivery Methods
Immediate Delivery (Avoid in production)
UserMailer.welcome(user).deliver_now
Background Delivery (Preferred)
Uses Active Job
UserMailer.welcome(user).deliver_later
With options
UserMailer.welcome(user).deliver_later(wait: 5.minutes) UserMailer.welcome(user).deliver_later(wait_until: Date.tomorrow.noon) UserMailer.welcome(user).deliver_later(queue: :mailers)
From Services
class UserRegistrationService def call(params) user = User.create!(params) UserMailer.welcome(user).deliver_later success(user) end end
Common Patterns
Conditional 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"))
end end
Bulk Emails with Batching
class NewsletterJob < ApplicationJob def perform User.subscribed.find_each(batch_size: 100) do |user| NewsletterMailer.weekly(user).deliver_later end end end
Email Callbacks
class ApplicationMailer < ActionMailer::Base after_action :log_delivery
private
def log_delivery Rails.logger.info("Sending #{action_name} to #{mail.to}") end end
Checklist
-
Mailer spec 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 )
-
Email layout styled
-
All specs GREEN
References
-
templates.md — HTML template, text template, and email layout examples
-
previews.md — ActionMailer::Preview examples with single and multiple states
-
i18n.md — Locale file examples and localized email delivery