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.

app/models/user.rb
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:

app/mailers/notification_mailer.rb
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.

app/mailers/notification_mailer.rb
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.

app/views/notification_mailer/new_account.text.erb
Hello <%= @user.name %>,

your new account is active.

Have a great day!
  A Robot
app/views/notification_mailer/new_account.html.erb
<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:

app/models/user.rb
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:

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/environments/development.rb
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/environments/production.rb
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:

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:

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:

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:

app/views/notification_mailer/new_account.html.erb
<!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.