# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
default from: 'noreply@example.com'
def welcome_email(user)
@user = user
@url = login_url
mail(
to: email_address_with_name(@user.email, @user.name),
subject: 'Welcome to My App!'
)
end
def password_reset(user)
@user = user
@token = user.generate_reset_token
mail(
to: @user.email,
subject: 'Reset your password'
)
end
def notification(user, message)
@user = user
@message = message
mail(
to: @user.email,
subject: "New notification: #{message.title}",
reply_to: 'support@example.com'
)
end
def weekly_digest(user, posts)
@user = user
@posts = posts
# Add custom headers
headers['X-Campaign-ID'] = 'weekly-digest'
# Attach file
attachments['report.pdf'] = File.read('path/to/report.pdf')
# Inline attachment for images in email
attachments.inline['logo.png'] = File.read('app/assets/images/logo.png')
mail(
to: @user.email,
subject: "Your weekly digest (#{posts.count} new posts)",
template_name: 'digest',
template_path: 'user_mailer'
)
end
def bulk_email(users, content)
@content = content
users.find_each do |user|
@user = user
mail(
to: user.email,
subject: content.subject
).deliver_later(wait: rand(60).seconds) # Spread out delivery
end
end
end
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
default from: 'noreply@example.com'
layout 'mailer'
private
def email_address_with_name(email, name)
"\"#{name}\" <#{email}>"
end
end
# Sending emails
# Synchronous (blocks until sent)
UserMailer.welcome_email(@user).deliver_now
# Asynchronous (queues for background delivery)
UserMailer.welcome_email(@user).deliver_later
# Delayed delivery
UserMailer.welcome_email(@user).deliver_later(wait: 1.hour)
UserMailer.welcome_email(@user).deliver_later(wait_until: Date.tomorrow.noon)
# Configuration
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.sendgrid.net',
port: 587,
domain: 'example.com',
user_name: ENV['SENDGRID_USERNAME'],
password: ENV['SENDGRID_PASSWORD'],
authentication: 'plain',
enable_starttls_auto: true
}
config.action_mailer.default_url_options = { host: 'example.com' }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
# app/views/user_mailer/welcome_email.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>Welcome to My App, <%= @user.name %>!</h1>
<p>
You have successfully signed up.
</p>
<p>
To login to the site, just follow this link:
<%= link_to 'Login', @url %>
</p>
<p>Thanks for joining!</p>
</body>
</html>
# app/views/user_mailer/welcome_email.text.erb (plain text version)
Welcome to My App, <%= @user.name %>!
===============================================
You have successfully signed up.
To login to the site, just follow this link: <%= @url %>
Thanks for joining!
# app/views/layouts/mailer.html.erb
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.header { background: #007bff; color: white; padding: 20px; }
.content { padding: 20px; }
.footer { background: #f8f9fa; padding: 10px; text-align: center; }
</style>
</head>
<body>
<div class="header">
<img src="<%= attachments['logo.png'].url %>" alt="Logo">
</div>
<div class="content">
<%= yield %>
</div>
<div class="footer">
<p>© 2024 My App. All rights reserved.</p>
<p>
<%= link_to 'Unsubscribe', unsubscribe_url %>
</p>
</div>
</body>
</html>
# Email previews
# test/mailers/previews/user_mailer_preview.rb
class UserMailerPreview < ActionMailer::Preview
def welcome_email
user = User.first
UserMailer.welcome_email(user)
end
def password_reset
user = User.first
UserMailer.password_reset(user)
end
def weekly_digest
user = User.first
posts = Post.published.limit(5)
UserMailer.weekly_digest(user, posts)
end
end
# Access previews at /rails/mailers
# Testing mailers
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
test "welcome email" do
user = users(:one)
email = UserMailer.welcome_email(user)
assert_emails 1 do
email.deliver_now
end
assert_equal ['noreply@example.com'], email.from
assert_equal [user.email], email.to
assert_equal 'Welcome to My App!', email.subject
assert_match user.name, email.body.encoded
end
end
# RSpec testing
RSpec.describe UserMailer do
describe '#welcome_email' do
let(:user) { create(:user) }
let(:mail) { UserMailer.welcome_email(user) }
it 'renders the headers' do
expect(mail.subject).to eq('Welcome to My App!')
expect(mail.to).to eq([user.email])
expect(mail.from).to eq(['noreply@example.com'])
end
it 'renders the body' do
expect(mail.body.encoded).to match(user.name)
end
end
end
# Interceptors (modify emails before sending)
# lib/email_interceptor.rb
class EmailInterceptor
def self.delivering_email(message)
# Redirect all emails to test address in staging
if Rails.env.staging?
message.to = ['staging@example.com']
message.subject = "[STAGING] #{message.subject}"
end
end
end
# config/initializers/email_interceptor.rb
ActionMailer::Base.register_interceptor(EmailInterceptor)
# Observers (hook into email lifecycle)
class EmailObserver
def self.delivered_email(message)
EmailLog.create!(
to: message.to.join(', '),
subject: message.subject,
sent_at: Time.current
)
end
end
ActionMailer::Base.register_observer(EmailObserver)
ActionMailer handles email delivery in Rails. Mailers are similar to controllers—actions generate email content. I use ActionMailer for welcome emails, password resets, notifications. Layouts apply consistent styling across emails. Previews enable viewing emails without sending. deliver_later queues emails via ActiveJob for async sending. Interceptors modify emails before delivery—useful for staging environments. Multi-part emails include both HTML and text versions. Attachments add files to emails. Testing uses ActionMailer::TestCase and email spy. Understanding email delivery configuration—SMTP, SendGrid, Postmark—is essential. ActionMailer integrates with email service providers for reliability and deliverability.