action-mailer-patterns
SKILL.md
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(notdeliver_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
Weekly Installs
19
Repository
thibautbaissac/…i_agentsGitHub Stars
407
First Seen
Jan 23, 2026
Security Audits
Installed on
opencode13
claude-code12
codex12
gemini-cli10
cursor10
github-copilot10