Action Mailer
Even if we mainly use Ruby on Rails to generate web pages, it sometimes is useful to be able to send an email.
So let’s go and build an example with minimal user management for a web shop that automatically sends an email to the user when a new user is created:
$ rails new webshop
[...]
$ cd webshop
$ bin/rails generate scaffold User name email
[...]
$ bin/rails db:migrate
[...]
For the user model we create a minimal validation in
app/models/user.rb so that we can be sure that each user has a
name and a syntactically correct email address.
class User < ApplicationRecord
validates :name, presence: true
validates :email,
presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP }
end
Older editions of this book used a hand-crafted email regex.
Ruby’s standard library ships
URI::MailTo::EMAIL_REGEXP, which is maintained for you and
handles the common edge cases well enough for most web apps.
|
There is a generator with the name mailer that creates the files
required for mailing. First we have a look at the output of
bin/rails generate mailer without passing any further arguments:
$ bin/rails generate mailer
Usage:
bin/rails generate mailer NAME [method method] [options]
[...]
Example:
========
bin/rails generate mailer Notifications signup forgot_password invoice
creates a Notifications mailer class, views, and test:
Mailer: app/mailers/notifications_mailer.rb
Views: app/views/notifications_mailer/signup.text.erb [...]
Test: test/mailers/notifications_mailer_test.rb
That is just what we expected. So let’s now create the mailer notification:
$ bin/rails generate mailer Notification new_account
create app/mailers/notification_mailer.rb
invoke erb
create app/views/notification_mailer
create app/views/notification_mailer/new_account.text.erb
create app/views/notification_mailer/new_account.html.erb
invoke test_unit
create test/mailers/notification_mailer_test.rb
create test/mailers/previews/notification_mailer_preview.rb
In the file app/mailers/notification_mailer.rb you will find the
controller for it:
class NotificationMailer < ApplicationMailer
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.notification_mailer.new_account.subject
#
def new_account
@greeting = "Hi"
mail to: "to@example.org"
end
end
We change the new_account method to accept a parameter with
new_account(user) and some code to use that to send the
confirmation email.
class NotificationMailer < ApplicationMailer
def new_account(user)
@user = user
mail(to: user.email, subject: "Account #{user.name} is active")
end
end
Now we create the views for this method. Actually we have to breathe life into two files:
-
app/views/notification_mailer/new_account.text.erb -
app/views/notification_mailer/new_account.html.erb
If you want to send a non-HTML (plain text) only email you can
delete the file
app/views/notification_mailer/new_account.html.erb. Otherwise
Action Mailer will generate a multipart email which can be read
either as modern HTML or as traditional plain text.
Hello <%= @user.name %>,
your new account is active.
Have a great day!
A Robot
<p>Hello <%= @user.name %>,</p>
<p>your new account is active.</p>
<p>Have a great day!<br>
A Robot</p>
As we want to send this email after creating a User, we still
need to add an after_create callback which triggers the
delivery:
class User < ApplicationRecord
validates :name, presence: true
validates :email,
presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP }
after_create :send_welcome_email
private
def send_welcome_email
NotificationMailer.new_account(self).deliver_later
end
end
Let’s create a new User in the console.
It’ll take a moment for Active Job to send the email — the
call deliver_later enqueues the mail job through Active
Job (which in Rails 8 is backed by Solid Queue). Be
patient.
|
$ bin/rails console
Loading development environment (Rails 8.1.3)
irb(main):001> User.create(name: "Wintermeyer", email: "sw@wintermeyer-consulting.de")
TRANSACTION (0.1ms) BEGIN
User Create (0.4ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) RETURNING "id"
Enqueued ActionMailer::MailDeliveryJob (Job ID: de33ce3d-...) to Async(default) with arguments: "NotificationMailer", "new_account", "deliver_now", {:args=>[#<GlobalID:0x... @uri=#<URI::GID gid://webshop/User/1>>]}
TRANSACTION (3.2ms) COMMIT
=> #<User id: 1, name: "Wintermeyer", email: "sw@wintermeyer-consulting.de", created_at: "2026-04-19 11:21:32", updated_at: "2026-04-19 11:21:32">
irb(main):002> Performing ActionMailer::MailDeliveryJob (Job ID: de33ce3d-...) from Async(default)
Rendering layout layouts/mailer.html.erb
Rendered notification_mailer/new_account.html.erb within layouts/mailer
Rendered notification_mailer/new_account.text.erb within layouts/mailer
NotificationMailer#new_account: processed outbound mail in 12.3ms
Sent mail to sw@wintermeyer-consulting.de (8.4ms)
Date: Sun, 19 Apr 2026 11:21:43 +0200
From: from@example.com
To: sw@wintermeyer-consulting.de
Subject: Account Wintermeyer is active
[...]
That was straightforward. In development mode we see the email in the log. In production mode it would be sent to the configured SMTP gateway.
Have a look at app/views/layouts/mailer.html.erb and
app/views/layouts/mailer.text.erb to set a generic
envelope (e.g. add a signature) for your email content.
They work like app/views/layouts/application.html.erb
does for HTML views.
|
Mailer Previews
Rails ships a browser-based preview for any mailer. The scaffolded
preview file lives at
test/mailers/previews/notification_mailer_preview.rb:
class NotificationMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/notification_mailer/new_account
def new_account
NotificationMailer.new_account(User.first)
end
end
Once you run bin/rails server, open
http://localhost:3000/rails/mailers — you’ll see a list of every
mailer in the project and can click into each preview. Every
change you make to the mailer or its views is picked up
immediately without restarting the server.
Configuring the Email Server
Rails can use a local sendmail or an external SMTP server for
delivering the emails.
Sending via Local Sendmail
If you want to send emails in the traditional way via local
sendmail, insert the following lines into your configuration
file config/environments/development.rb (for the development
environment) or config/environments/production.rb (for your
production environment):
config.action_mailer.delivery_method = :sendmail
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
Sending via Direct SMTP
If you want to send email directly via an SMTP server, insert
the following lines into config/environments/development.rb
(development) or config/environments/production.rb (production):
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.example.com",
port: 587,
domain: "example.com",
user_name: Rails.application.credentials.dig(:smtp, :username),
password: Rails.application.credentials.dig(:smtp, :password),
authentication: :plain,
enable_starttls_auto: true
}
Of course you need to adapt the values for :address,
:domain, :user_name and :password according to your
configuration. Store credentials in Rails' encrypted credentials
file (see Credentials) rather
than committing them to git.
Using a Transactional Email Service
In production you usually don’t run your own SMTP server. You use a transactional email service such as Postmark, SendGrid, Mailgun, Amazon SES or Brevo. They all speak SMTP, so the configuration looks the same as above — just plug in the provider’s SMTP host, port and credentials. Rails 8 also ships with Action Mailbox for receiving incoming mail (webhooks from Postmark, Mailgun, SendGrid, Mandrill, SES), but that is beyond the scope of this chapter.
Custom X-Header
If you feel the urge to integrate an additional X-header then
this is no problem. Here is an example for expanding
app/mailers/notification_mailer.rb:
class NotificationMailer < ApplicationMailer
def new_account(user)
@user = user
headers["X-Priority"] = "3"
mail(to: user.email, subject: "The account #{user.name} is active.")
end
end
This means the sent email would look like this:
Sent mail to sw@wintermeyer-consulting.de (50ms)
Date: Sun, 19 Apr 2026 11:35:21 +0200
From: from@example.com
To: sw@wintermeyer-consulting.de
Subject: The account Wintermeyer is active.
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-Priority: 3
Hello Wintermeyer,
your new account is active.
Have a great day!
A Robot
Attachments
Email attachments can be defined too.
As an example we add the Rails logo public/icon.png to an email
as an attachment in app/mailers/notification_mailer.rb:
class NotificationMailer < ApplicationMailer
def new_account(user)
@user = user
attachments["icon.png"] = Rails.root.join("public/icon.png").read
mail(to: user.email, subject: "The account #{user.name} is active.")
end
end
Rails 5 apps had a Rails logo at
app/assets/images/rails.png. That file is no longer part
of a fresh Rails 8 app. The example above uses the generic
public/icon.png that every Rails 8 app ships with; feel
free to drop in your own file.
|
Inline Attachments
For inline attachments in HTML emails you need to use the
method inline when calling attachments. In our example
controller app/mailers/notification_mailer.rb:
class NotificationMailer < ApplicationMailer
def new_account(user)
@user = user
attachments.inline["icon.png"] = Rails.root.join("public/icon.png").read
mail(to: user.email, subject: "The account #{user.name} is active.")
end
end
In the HTML email you can access the hash attachments[] via
image_tag. In our example
app/views/notification_mailer/new_account.html.erb would look
like this:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
</head>
<body>
<%= image_tag attachments["icon.png"].url, alt: "Rails Logo" %>
<p>Hello <%= @user.name %>,</p>
<p>your new account is active.</p>
<p><i>Have a great day!</i></p>
<p>A Robot</p>
</body>
</html>
Further Information
The Rails guides have a very extensive entry on Action Mailer at https://guides.rubyonrails.org/action_mailer_basics.html. For receiving email, see the Action Mailbox guide: https://guides.rubyonrails.org/action_mailbox_basics.html.