© Brady Somerville, Adam Gamble, Cloves Carneiro Jr and Rida Al Barazi 2020
B. Somerville et al.Beginning Rails 6https://doi.org/10.1007/978-1-4842-5716-6_12

12. Sending and Receiving Email

Brady Somerville1 , Adam Gamble2, Cloves Carneiro Jr.3 and Rida Al Barazi4
(1)
Bowling Green, KY, USA
(2)
Gardendale, AL, USA
(3)
Hollywood, FL, USA
(4)
FONTHILL, ON, Canada
 

It’s a rare web application that doesn’t need to send email from time to time. For example, you may want to send messages to welcome users who sign up on your website, send “reset password” links, or confirm orders placed with an online store. Rails ships with a library called Action Mailer, which provides developers with an easy-to-use yet powerful tool to handle email.

This chapter explains how Action Mailer works and how to use it in your applications. You first learn how to configure it, and then you’ll see a few examples of how to send email in various formats.

In addition to sending email, your Rails app can also receive email with the help of Action Mailbox—a new feature in Rails 6. With Action Mailbox, we can receive email destined for different email addresses, parse the email however we want, and then decide what action to take (if any) in response to that email. For example, we could allow authors to send an email to our app, which would then parse the email and create an unpublished article—great for allowing authors to save ideas for an article when it’s not convenient for them to use the browser. Toward the end of the chapter, we’ll add the ability for our blog to do just that.

Note

If you need to get the code at the exact point where you finished Chapter 11, download the source code zip file from the book’s page on www.apress.com and extract it on your computer.

Setting Up Action Mailer

Like Active Record and Action Pack, Action Mailer is one of the components that make up the Rails framework. It works much like the other components of Rails: mailers are implemented to behave like controllers, and mailer templates are implemented as views. Because it’s integrated into the framework, it’s easy to set up and use, and it requires very little configuration to get going.

When you send email using an email client such as Outlook or a web-based email application like Gmail or Yahoo Mail, your messages are sent via a mail server. Unlike a web server, Rails doesn’t provide a built-in mail server. You need to tell Action Mailer where your email server is located and how to connect to it. This sounds a bit complicated, but it’s really quite easy. Depending on the kind of computer you’re using, you may have a mail server built in (this is true of most UNIX systems). If not, you can use the same server that you use to process your regular email. If this is the case, you can find your server information in your email client settings, as provided by your Internet service provider (ISP) , or in the settings section of your web-based email application, like Gmail.

Configuring Mail Server Settings

Before you can send email from your Rails application, you need to tell Action Mailer how to communicate with your mail server. Action Mailer can be configured to send email using either sendmail or a Simple Mail Transfer Protocol (SMTP) server. SMTP is the core Internet protocol for relaying email messages between servers. If you’re on Linux, OS X, or any other UNIX-based system, you’re in luck: you can use sendmail, and as long as it’s in the standard location (/usr/bin/sendmail), you don’t need to configure anything. If you’re on Windows or if you want to use SMTP, you have some work to do.

Action Mailer options are set at the class level on ActionMailer::Base. The best place to set these options is in your environment files, located in the config directory of your application. You can also add your configuration in an initializer file in config/initializers; doing so ensures that your settings apply for all environments. In most cases, though, you have different settings for the development and production environments; so it may be wiser to add settings in any of the environment-specific configuration files (config/environments/*.rb), because this takes precedence over the global configuration.

This section describes how to set up Action Mailer to use SMTP, because it works on all systems and is the default delivery method. To do this, you supply the SMTP settings via the smtp_settings option. The smtp_settings method expects a hash of options, most of which are shown in Table 12-1.
Table 12-1

Server Connection Settings

Setting

Description

address

The address of your mail server. The default is localhost.

port

The port number of your mail server. The default is port 25.

domain

If your email server responds to different domain names, you may need to specify your domain name here.

authentication

If your mail server requires authentication, you need to specify the authentication type here. This can be one of :plain, :login, or :cram_md5.

user_name

The username you use to authenticate when you connect to the mail server, if your server requires authentication.

password

The password you use to authenticate when you connect to the mail server, if your server requires authentication.

Storing Sensitive Secrets

Since we know we’re going to need to set some sensitive information in our configuration—our user_name and password for our SMTP server—we need to know where to put them. We could just put them directly in our config file. While that would be the simplest choice, it’s not the safest choice. Our config files may be version controlled, deployed to servers, or copied to developers’ laptops—and our sensitive information will be sitting there in plain text, vulnerable to misuse. How can we prevent this?

Naturally, there are various ways to prevent this. A common approach is to put sensitive configuration values in environment variables which are not version controlled and must be manually added and updated to servers and workstations as they’re needed. The Rails application would then fetch these values from the special ENV hash. This approach works well, but can be tedious to maintain. For example, when a code change requires a new environment variable, care must be taken to ensure the servers are updated with the new environment variables in conjunction with deploying the code. This can be an error-prone manual task.

Rails now offers an integrated approach, called credentials. With Rails’ approach to handling sensitive information, we generate a secret key in config/master.key which is not to be version controlled. (But it can and should be shared with other developers and servers.) This secret key can then be used to encrypt and decrypt a YAML file which stores our sensitive data–in config/credentials.yml.enc. This strikes a good balance between security and convenience; we only share the master key once, and then future updates to the contents of config/credentials.yml.enc are shared via version control, and we avoid the risks of storing sensitive data in plain text.

To safely store our SMTP username and password, we will use Rails’ credentials system. (Note: If you downloaded the source code for this book, you received an encoded credentials file—config/credentials.yml.enc—for which you don’t have the key. Simply remove that file before beginning.) First, let’s run the Rails command to edit our encrypted credentials file:
> rails credentials:edit

Running this command will open an editor with the unencrypted contents of config/credentials.yml.enc. Now we can edit our sensitive credentials, and when we save and close our editor, the contents will be encrypted again and saved.

Easy enough, but there’s one complication—which editor will it use? Like many CLI (command-line interface) programs, rails credentials delegates that decision to the $EDITOR environment variable on your system. Depending on your system, this may be a console-based editor like nano or vim, or on Windows it could be Notepad. If the default editor isn’t to your liking, use your favorite search engine to find how to set your preferred default editor for command-line programs like rails credentials:edit.

After running rails credentials:edit to open your unencrypted credentials in your editor, edit your credentials file to match Listing 12-1, but with your SMTP username and password, of course. (Note: Your secret_key_base value will likely be different. That’s okay!)
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 42cd449ceeb465562463941be28c64e7786cfe482fcf8b5e4f51f5605c6b1a155b3cb2ef1baa221e27c5dc41b778a0dc91b26f956aa6a3f295ae098a67a3f891
smtp:
  user_name: "beginningrails@gmail.com"
  password: "changeme"
Listing 12-1

Adding SMTP Credentials via rails credentials:edit https://gist.github.com/nicedawg/d1691274c9b99de6cf81a80a89d3ae3f

Save and close your editor, and your config/credentials.yml.enc file will be created or updated to include your encrypted information. Now that we have our sensitive data securely stored, we can configure our SMTP settings in our config files and reference our encrypted credentials rather than store them in plain text.

Let’s configure our SMTP settings now. Listing 12-2 shows a typical configuration for a server that requires authentication, in this case, Gmail. You can use this sample configuration as a starting point to configure your connection. Change each of the settings to connect to your own SMTP server. You may need to search for correct SMTP settings for your particular email service. If you’re using sendmail as the delivery method, add config.action_mailer.delivery_method = :sendmail; then, everything should “just work.”

In addition to configuring our SMTP settings, we also go ahead and set default_url_options to include our host URL in the development environment so that links in our emails can point back to our app.
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.
  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false
  # Do not eager load code on boot.
  config.eager_load = false
  # Show full error reports.
  config.consider_all_requests_local = true
  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join('tmp', 'caching-dev.txt').exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true
    config.cache_store = :memory_store
    config.public_file_server.headers = {
      'Cache-Control' => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false
    config.cache_store = :null_store
  end
  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :local
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false
  config.action_mailer.default_url_options = { host: 'http://localhost:3000' }
  # Gmail SMTP server setup
  config.action_mailer.smtp_settings = {
    address: "smtp.gmail.com",
    enable_starttls_auto: true,
    port: 587,
    authentication: :plain,
    user_name: Rails.application.credentials.smtp[:user_name],
    password: Rails.application.credentials.smtp[:password],
  }
  config.action_mailer.perform_caching = false
  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log
  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load
  # Highlight code that triggered database queries in logs.
  config.active_record.verbose_query_logs = true
  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true
  # Suppress logger output for asset requests.
  config.assets.quiet = true
  # Raises error for missing translations.
  # config.action_view.raise_on_missing_translations = true
  # Use an evented file watcher to asynchronously detect changes in source code,
  # routes, locales, etc. This feature depends on the listen gem.
  config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
Listing 12-2

Sample Action Mailer Configuration Using SMTP, in config/environments/*.rb https://gist.github.com/nicedawg/0424d7892cbb3ef3e0fa87f2a777f40c

Make sure to modify the options to match your own connection details for your email provider. Restart your server if it’s running, and your application is ready to send email. If your server fails to restart, check the error messages and look closely at your config/environments/development.rb file and your encrypted credentials via rails credentials:edit to make sure your changes match the listings.

Note

If you need to use any advanced Action Mailer settings, the Rails API has a good chunk of information at https://api.rubyonrails.org/classes/ActionMailer/Base.html.

Configuring Application Settings

In addition to the mail server settings, Action Mailer has a set of configuration parameters you can tweak to make the library behave in specific ways according to the application or the environment. For reference, Table 12-2 lists the most common configuration options. Just like the server settings, these can be specified in an initializer file or in the environment-specific configuration files (config/environments/*.rb).
Table 12-2

Common Action Mailer Application Settings

Option

Description

raise_delivery_errors

Determines if exceptions should be raised when an error occurs during email delivery.

delivery_method

Determines which subsystem to use to deliver emails. Valid options are :smtp, :sendmail, :file, and :test. Additional options for the chosen subsystem may be required.

perform_deliveries

Indicates whether emails should actually be delivered.

deliveries

Keeps an array of all delivered emails when the delivery method is set to :test. This is useful when writing tests as we can inspect the delivered messages without sending them anywhere.

default_options

Allows you to specify default arguments for the mail method used inside mailers (e.g., setting default from or reply_to addresses).

default_url_options

Allows you to specify default arguments for URL helpers used inside your mailers (e.g., setting host so generated URLs have the correct domain name).

asset_host

Allows you to specify the base URL used when including assets like images in your emails.

Note

When you create a new Rails application, the configuration files automatically use sensible defaults for each of the development, test, and production environments. Take a quick look in config/environments to see how Action Mailer behaves in development, production, and test mode to make sure you understand your application’s behavior.

Sending Email

Now that you have Action Mailer configured, it’s time to see it in action. This section explores all the possibilities in the Action Mailer world, starting with basic text-only email and then adding extra email options such as attachments.

To demonstrate Action Mailer, let’s enhance the blog application by allowing users to send email to their friends, so they can share information about a specific article. This is a common feature in today’s web applications, affectionately referred to as “send to friend.”

By now, you know that Rails provides helpful generators to get started writing your own code. You saw generators in action when you created models and controllers in previous chapters. The mailer generator works just like the other generators.

Enter the following command to generate the NotifierMailer class with one method named email_friend:
$ rails g mailer Notifier email_friend
      create  app/mailers/notifier_mailer.rb
      invoke  erb
      create    app/views/notifier_mailer
      create    app/views/notifier_mailer/email_friend.text.erb
      create    app/views/notifier_mailer/email_friend.html.erb
      invoke  test_unit
      create    test/mailers/notifier_mailer_test.rb
      create    test/mailers/previews/notifier_mailer_preview.rb

As we can see, the generator created several files, which we’ll briefly describe before diving into more detail.

First, it created the NotifierMailer class in app/mailers/notifier_mailer.rb. By convention, any other mailers we create will be located in app/mailers as well. Inspecting NotifyMailer, we notice two things: First, the NotifyMailer class contains the email_friend method we requested on the command line. Second, we see that it is a subclass of ApplicationMailer class (found in app/mailers/application_mailer.rb), which is in turn a subclass of the ActionMailer::Base class . This gives us a chance to make app-wide changes to our mailers (by setting default options or changing layouts) while still inheriting all the features ActionMailer::Base provides.

Next, we see that it also created two template files in the views directory (email_friend.text.erb and email_friend.html.erb) which correspond to the email_friend method (action) found in our mailer class. These template files will control the HTML and text content of our emails. (Though most prefer to view the HTML version of an email, it’s still considered a best practice to include a plain-text alternative. As you can see, Action Mailer encourages this best practice.)

Doesn’t this look familiar? Just like controllers, Action Mailer classes contain methods that, when triggered, execute some code and render a related view of the same name, unless otherwise specified.

Lastly, the generator created a test file for our mailer, which we won’t use yet. It also created a preview file, which we will cover later in this chapter.

Before we dive into our NotifierMailer implementation, let’s take a quick look at our ApplicationMailer class, as seen in Listing 12-3.
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
Listing 12-3

ApplicationMailer Class in app/mailers/application_mailer.rb https://gist.github.com/nicedawg/d77cbbcf9705fbe917cf3caeddaaa5cb

We see a couple of things happening here. First, the default method is called on the hash from: ‘from@example.com. This sets the given email address as the default, making it unnecessary to specify the same From address for each mailer action we might add. It would be a good idea to go ahead and change this From address to be the same as the account you configured in your SMTP settings in config/environments/development.rb, to stave off any possible delivery problems.

We also see that the layout is set to mailer. Similar to how view templates rendered by controller actions are usually wrapped in a layout template, mailer templates are by default as well. This default “mailer” layout is defined in app/views/layouts/mailer.html.erb and app/views/layouts/mailer.text.erb. If you want to make changes that affect all (or most) of your mailer templates, these mailer layout template files are the perfect place to do so.

Now, we’re ready to look at the NotifierMailer class we generated. In Listing 12-4, we see that the email_friend method already has some code, which will be the starting point for most of the methods you write using Action Mailer.
class NotifierMailer < ApplicationMailer
  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.notifier_mailer.email_friend.subject
  #
  def email_friend
    @greeting = "Hi"
    mail to: "to@example.org"
  end
end
Listing 12-4

NotifierMailer Class in app/mailers/notifier.rb https://gist.github.com/nicedawg/7db1799fd4eaa504d1a81ddc41930333

We see a comment about setting up our subject line for the email which our email_friend mailer action will send in our I18n (internationalization) file. We won’t do that now, but we will cover internationalization in a later chapter in this book.

Next, in the email_friend method body, the first line defines an instance variable named @greeting; just like in controllers, instance variables are also available in your views.

Also in the email_friend method body, we see that the mail method is called with a parameter of to: "to@example.org", specifying the email address that will receive this message. The mail method accepts an options hash that specifies the various headers of the message. Table 12-3 lists the available options we can use to configure an individual message.
Table 12-3

Mail Method Options

Option

Description

Example

subject

The subject of the email message to be sent.

subject: "Action Mailer is powerful"

to

A string or array of email addresses to which the message will be sent.

to: "friend@example.com"

from

A string specifying the sender of the email message.

from: "sender@example.com"

reply_to

A string specifying the reply-to email address.

reply: "sender@example.com"

date

The date header. The default is the current date.

date: Time.now

cc

A string or array of email addresses to carbon copy with the message.

cc: "admin@example.com"

bcc

A string or array of email addresses to blind carbon copy with the message.

bcc: ["support@example.com", "sales@example.com"]

Handling Basic Email

Let’s start enhancing the blog application by adding “Notify a Friend” functionality to the article page. The first iteration is a very basic example that sends an email (with both HTML and plain-text formats) containing a brief message.

The first piece of the puzzle is to make a change to the routes file, to include a route for the action that will be called after the user submits the form. Let’s add a member route to articles using the member method to give a notify_friend_article route. Make sure your config/routes.rb file looks like the code in Listing 12-5.
Rails.application.routes.draw do
  root to: "articles#index"
  resources :articles do
    member do
      post :notify_friend
    end
    resources :comments
  end
  resources :users
  resource :session
  get "/login", to: "sessions#new", as: "login"
  get "/logout", to: "sessions#destroy", as: "logout"
end
Listing 12-5

Added a notify_friend Action to config/routes.rb: https://gist.github.com/nicedawg/1b848339e03a9ce2204836e744d9c272

Note

Using the member method inside your resources block helps define a route that requires the id of the resource. Custom member routes are similar to the default member routes, such as edit_article_path and article_path. Following the same convention, you can define collection routes using the collection method. Custom collection routes are similar to the default collection routes, such as articles_path, which don’t require an id.

Now that we have the route in place, let’s show users a link which, when clicked, shows a form where they can enter the email address of the friend to whom they want to send a message. (Please note, a feature like this could be abused. In a production environment, it may be necessary to add security measures to restrict usage of a form like this to prevent malicious users using your form to send unsolicited emails. That’s beyond the scope of this book, but be aware.)

Let’s update the article’s show view to include the new link and form partial directly after rendering the article’s partial. Add the code shown in Listing 12-6 in app/views/articles/show.html.erb.
<%= render partial: @article, locals: { cover_image_options: [500, 500] } %>
<%= link_to 'Email a friend', '#', onclick: "document.querySelector('#notify_friend').style.display = 'block';return false;" %>
<div id="notify_friend" style="display:none;">
  <%= render 'notify_friend', article: @article %>
</div>
<h3>Comments</h3>
<div id="comments">
  <%= render @article.comments %>
</div>
<%= link_to "new comment", new_article_comment_path(@article), remote: true, id: 'new_comment_link' %>
Listing 12-6

“Email a Friend” Link and Partial Added to app/views/articles/show.html.erb:https://gist.github.com/nicedawg/5fac226dd94990290eb2deb18d67951d

We added a link which may look a little strange. We set its URL to ‘#’, which is one way to make a link clickable without it navigating anywhere. We’re only using the link to trigger some JavaScript when clicked—namely, to display the “Notify a Friend” form. (We also added return false to the onclick handler to prevent the browser from navigating.)

We also added a partial—notify_friend—and passed the article as a local variable. This partial doesn’t exist yet, but we’ll create it next. We wrapped the partial in a container which will be hidden by default. We gave the container an id so that the preceding link can reference it and display the form. Generally, it’s best to keep your JavaScript behavior separate from your HTML—perhaps in app/javascript/—but this works for now and keeps us focused on our goal. And that’s okay!

Next, we need to add the partial for the “Notify a Friend” form we referenced in the preceding listing. Let’s create this partial in app/views/articles/_notify_friend.html.erb so it looks like the code in Listing 12-7.
<%= form_with(url: notify_friend_article_path(article)) do |form| %>
  <div class="field">
    <%= form.label :name, 'Your name' %>
    <%= form.text_field :name %>
  </div>
  <div class="field">
    <%= form.label :email, "Your friend's email" %>
    <%= form.text_field :email %>
  </div>
  <div class="actions">
    <%= form.submit 'Send' %> or
    <%= link_to 'Cancel', '#', onclick: "document.querySelector('#notify_friend').style.display='none';return false;" %>
  </div>
<% end %>
Listing 12-7

“Notify a Friend” Partial in app/views/articles/_notify_friend.html.erb: https://gist.github.com/nicedawg/c39b08e008df3daab297ba9998b0f178

This form is pretty standard. We configured it to send the name and email values to the new route we added. And remember that, by default, form_with will send its data via Ajax. The only other thing to note is the Cancel link; it’s similar to the “Email a Friend” link from the previous listing, except that it hides the form.

Now, when you go to any article page, you’ll see a link to email a friend. Because you don’t want to show the form all the time, you made the form hidden. If users are interested in recommending the article by sending an email to a friend, they can click the link, and the form will be revealed through the help of some simple JavaScript. The end result is shown in Figures 12-1 and 12-2.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig1_HTML.jpg
Figure 12-1

Article page without “Notify a Friend” form

../images/314622_4_En_12_Chapter/314622_4_En_12_Fig2_HTML.jpg
Figure 12-2

Article page with visible “Notify a Friend” form

The new form is ready to go, but the articles controller doesn’t know how to handle its submitted data yet. The form is configured to submit to an action called notify_friend, but that action doesn’t exist. Let’s update the articles controller and add the notify_friend method (and to find the article for it) as shown in Listing 12-8.
class ArticlesController < ApplicationController
  before_action :authenticate, except: [:index, :show]
  before_action :set_article, only: [:show, :notify_friend]
  # GET /articles
  # GET /articles.json
  def index
    @articles = Article.includes(:user).with_rich_text_body.with_attached_cover_image.all
  end
   # .... code omitted for brevity ...
  # DELETE /articles/1
  # DELETE /articles/1.json
  def destroy
    @article = current_user.articles.find(params[:id])
    @article.destroy
    respond_to do |format|
      format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
      format.json { head :no_content }
    end
  end
  def notify_friend
    NotifierMailer.email_friend(@article, params[:name], params[:email]).deliver
    redirect_to @article, notice: 'Successfully sent a message to your friend'
  end
  private
    # Use callbacks to share common setup or constraints between actions .
    def set_article
      @article = Article.find(params[:id])
    end
    # Never trust parameters from the scary internet, only allow the white list through.
    def article_params
      params.require(:article).permit(:title, :cover_image, :remove_cover_image, :location, :excerpt, :body, :published_at, category_ids: [])
    end
end
Listing 12-8

The notify_friend Action Added to app/controllers/articles_controller.rb: https://gist.github.com/nicedawg/9447cf251055501a1d3a4105af4fa208

First, we modified the before_action so that it would also set @article for our new action. Then, we added the notify_friend action to deliver our message. The notify_friend action is short and readable, but let’s dig a little deeper. Let’s use the rails console to see what’s going on:
> rails console
irb(main):001:0> NotifierMailer.email_friend
  Rendering notifier_mailer/email_friend.html.erb within layouts/mailer
 Rendered notifier_mailer/email_friend.html.erb within layouts/mailer (Duration: 1.1ms | Allocations: 226)
  Rendering notifier_mailer/email_friend.text.erb within layouts/mailer
 Rendered notifier_mailer/email_friend.text.erb within layouts/mailer (Duration: 0.4ms | Allocations: 101)
NotifierMailer#email_friend: processed outbound mail in 13.1ms
=> #<Mail::Message:70146235556720, Multipart: true, Headers: <From: from@example.com>, <To: to@example.org>, <Subject: Email friend>, <Mime-Version: 1.0>, <Content-Type: multipart/alternative; boundary="--==_mimepart_5e6ada2fab7e7_110243fcc3142bfd48643f"; charset=UTF-8>>
A lot happened there. We see that our mailer action rendered our mailer templates, and it constructed a message which it returned—apparently an instance of a class named Mail::Message. Let's inspect that instance to find out more about it:
irb(main):002:0> email = _
=> #<Mail::Message:70146235556720, Multipart: true, Headers: <From: from@example.com>, <To: to@example.org>, <Subject: Email friend>, <Mime-Version: 1.0>, <Content-Type: multipart/alternative; boundary="--==_mimepart_5e6ada2fab7e7_110243fcc3142bfd48643f"; charset=UTF-8>>
irb(main):003:0> email.class.name
=> "ActionMailer::MessageDelivery"

We used a handy shortcut—the underscore—as an alias for the last object returned by rails console and assigned its value to a variable we defined called email, for more convenient investigation.

Then, we asked for the name of its class and were surprised to find out that it’s ActionMailer::MessageDelivery, not Mail::Message as we thought. Why did that happen? ActionMailer::MessageDelivery is a thin wrapper around Mail::Message—it relies on Mail::Message for its expertise in manipulating emails and adds some methods to facilitate the delivery of these emails. It’s such a thin wrapper that it delegates almost every method call to the Mail::Message object it contains—even the inspect method inherited from the base Object class, which rails console uses to print the value of the last returned object. That’s really interesting!

Now, let’s see what we can do with this object:
irb(main):004:0> email.methods   # note: output shortened for brevity
=> [:subject, :subject=, :errors, :to_yaml, :decoded, :add_file, :filename, :from, :content_type, :to, :charset, :action, :<=>, :content_type=, :==, :[], :[]=, :sender, :boundary, :references, :attachment, :delivery_method, :inspect, :method_missing, :multipart?, :parts, :from_address, :recipients_addresses, :to_addresses, :cc_addresses, :x_original_to_addresses, :bcc_addresses, :to_s, :deliver, :deliver!, ..., :reply_to=, :resent_bcc, :body=, ... :message, :deliver_now!, :deliver_later, :deliver_now, :processed?, :deliver_later!, :__setobj__, :marshal_dump, :marshal_load]
irb(main):005:0> email.deliver
Delivered mail 5e6adb06191e8_110243fcc3142bfd48652b@bardy.local.mail (984.5ms)
Date: Thu, 12 Mar 2020 19:59:50 -0500
From: from@example.com
To: to@example.org
Message-ID: <5e6adb06191e8_110243fcc3142bfd48652b@bardy.local.mail>
Subject: Email friend
... omitted for brevity ...
=> #<Mail::Message:70146235556720, Multipart: true, Headers: <Date: Thu, 12 Mar 2020 19:59:50 -0500>, <From: from@example.com>, <To: to@example.org>, <Message-ID: <5e6adb06191e8_110243fcc3142bfd48652b@bardy.local.mail>>, <Subject: Email friend>, <Mime-Version: 1.0>, <Content-Type: multipart/alternative; boundary="--==_mimepart_5e6ada2fab7e7_110243fcc3142bfd48643f"; charset=UTF-8>, <Content-Transfer-Encoding: 7bit>>

We see that our email object has a long list of messages we can send it; many are for inspecting the details of the email message itself. Others are for managing the delivery of it. There were too many methods to include in the listing, but we included some of the more interesting ones. We see several methods with “deliver” in their name. We used :deliver in our controller already, but we also see :deliver_later and :deliver_now; the next chapter will explain those in more depth, though we can easily imagine what they might do.

Lastly, we ran email.deliver, and the rails console output indicated the delivery was performed—or at least attempted.

We don’t need to remember all these details every time we send an email; we just need to remember that we call the mailer action as a class method on the mailer class and then call deliver (e.g., NotifierMailer.email_friend.deliver).

We’re not quite ready to send our new email. In the previous code change, we added some code to send the article, the name of the sender, and the email address of the recipient to our mailer action—but the mailer action isn’t ready to receive it yet. Let’s update our mailer action to receive this data and to make use of it when constructing the message. Listing 12-9 shows these changes.
class NotifierMailer < ApplicationMailer
  def email_friend(article, sender_name, receiver_email)
    @article = article
    @sender_name = sender_name
    mail to: receiver_email, subject: 'Interesting Article'
  end
end
Listing 12-9

Updated NotifierMailer in app/mailers/notifier_mailer.rb: https://gist.github.com/nicedawg/a9d4779d99d442d9beddf76d169e92b6

We added three arguments which correspond to the arguments we added to the controller action in Listing 12-8: article, sender_name, and receiver_email. We assigned two of those values—article and sender_name—to instance variables so they will be available for use in our mailer templates. Then, we modified the mail method call to send the email to the receiver_email address with the subject “Interesting Article.”

We can now test to see if our email will actually be sent. It won’t have the content we want yet, but we’ll change that soon. Go ahead and try! Fill out the form in your browser, and send the message to your own email address. If all goes according to plan, you should receive an email that looks something like Figure 12-3.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig3_HTML.jpg
Figure 12-3

Message delivered to a user’s inbox

If you didn’t get the message, don’t worry. Sending email can be tricky, but there are a few things we can try.

First, look at your server output. Do you see any errors? If so, it may be a syntax error in your code which you can fix. Address any errors by making sure your code matches the preceding listings, and try again.

Does it look like everything was successful, but you still didn’t receive the message? Checking your Spam or Junk folder or simply waiting another minute could clear this up. However, there could be an SMTP error that’s being hidden. Edit your config/environments/development.rb file, and change the following option so it says config.action_mailer.raise_delivery_errors = true. (It was set to false, meaning SMTP delivery errors would be suppressed.) Then restart your Rails server and try to send the message again. Perhaps this will reveal the problem.

Email providers must continuously evolve to fight security risks and spam. It’s quite likely that a security feature or spam-blocking feature from your email provider is blocking your delivery attempts. For instance, if using a Gmail account with two-factor authentication enabled, you may need to go to your Google Account settings page, visit the Security section, and add an “app password” for your Rails app and then replace the password in your SMTP settings (via rails credentials:edit) with the new app password in order to send email from your Rails app.

Unfortunately, we cannot provide solutions for every type of SMTP delivery problem that might exist with every provider—and even if we did, the solutions would soon be obsolete! But knowing how to reveal the problem (via raise_delivery_errors) and using your favorite search engine, you’re bound to solve the problem. But if not, don’t worry. We can still preview the emails, even if they can’t be delivered right now.

Previewing Email

Hopefully, you were able to successfully send the email from your app. But if not, don’t worry. We can still preview what the email would look like with the help of another feature of Action Mailer—previews.

With Action Mailer’s previews, we can configure a mailer action to be previewed with some predefined data for its templates and then view the HTML and plain-text variations of that mailer action in our web browser—without having to send the email.

Certainly this is helpful when you’re having trouble sending emails from your development environment, but even if you don’t have any delivery problems, using Action Mailer previews helps shorten the feedback loop for making incremental changes to your mailers. Instead of filling out a form and waiting for the message to be sent to your email account, simply refresh your browser! We’ll walk through the steps necessary to make our email_friend mailer action previewable.

First, we need to define a subclass of ActionMailer::Preview specifically for previewing mailer actions in our NotifierMailer class. Thankfully, when we generated the mailer in an earlier section, it already created one for us, located in test/mailers/previews/notifier_mailer_preview.rb. It already has almost everything we need; however, we need to pass to the email_friend method the arguments which it expects. Modify your NotifierMailerPreview class so it matches Listing 12-10.
# Preview all emails at http://localhost:3000/rails/mailers/notifier_mailer
class NotifierMailerPreview < ActionMailer::Preview
  # Preview this email at http://localhost:3000/rails/mailers/notifier_mailer/email_friend
  def email_friend
    NotifierMailer.email_friend(Article.first, 'Sender T. Sendington', 'ree.seever@example.com')
  end
end
Listing 12-10

NotifierMailerPreview in test/mailers/previews/notifier_mailer_preview.rbhttps://gist.github.com/nicedawg/c5522dc35fcb2f4cdca3b3b29edab451

Instead of passing arguments to email_friend based on user input, we’re predefining the values which will be sent to the mailer action for the purpose of previewing.

Now, visit http://localhost:3000/rails/mailers in your browser. If all is well, you should see “Notifier Mailer” listed, with a link to “email_friend” nested underneath it. Click “email_friend,” and you should see the preview, similar to Figure 12-4.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig4_HTML.jpg
Figure 12-4

Action Mailer preview of NotifierMailer#email_friend

Notice that we see essentially every important part of the message—the From address, the Subject, the content, and more. Also, notice the Format menu; we see we’re currently viewing the HTML version of our email, but can easily switch to viewing the plain-text version as well.

Please be aware that email clients may not display the email exactly as it appears here. Email clients tend to support a subset of HTML and CSS, and each email client has its particular quirks. A good rule of thumb is to keep your layout and styles simple and to test your messages with a variety of popular email clients.

Now that we can preview our “Email a Friend” message, let’s add the content we want to send. We should include the sender’s name and a brief description of why we’re sending this email so the recipient understands why they’re receiving this email. We should also include the article’s title and a link back to the article. So let’s update our email_friend mailer action text and HTML templates to match Listings 12-11 and 12-12, respectively.
Your friend, <%= @sender_name %>, thinks you may like the following article:
<%= @article.title %>: <%= article_url(@article) %>
Listing 12-11

NotifierMailer Template in app/views/notifier_mailer/email_friend.text.erb: https://gist.github.com/nicedawg/a68c65b8b15b21be7a6346e8e3375969

<p>
  Your friend, <em><%= @sender_name %></em>, thinks you may like the following article:
</p>
<p>
  <%= link_to @article.title, article_url(@article) %>
</p>
Listing 12-12

HTML email_friend Template in app/views/notifier_mailer/email_friend.html.erb: https://gist.github.com/nicedawg/1cc96d3de7b9bdf3bb0980bd87a2803e

Now that we’ve updated our text and HTML versions of our mailer action, try previewing the email again in your browser by visiting http://localhost:3000/rails/mailers/notifier_mailer/email_friend. Much better!

If you were able to successfully send email from your Rails app via SMTP, try using the “Email a Friend” form to send yourself this email. It looks pretty good, as shown in Figure 12-5. If your users don’t have a rich email client and can’t read HTML mail, they are shown the plain-text version.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig5_HTML.jpg
Figure 12-5

HTML message delivered to a user’s inbox

Note

If you think maintaining both text and HTML versions of an email message is a lot of work, it may be safer to stick with the HTML message. While it is best practice to send both, most email users prefer the HTML version.

Adding Attachments

In some cases, you may want to add attachments to an email message. Action Mailer makes this a straightforward task by providing an attachments helper. You tell attachments which file you want to attach to the email, and it does its magic.

Let’s walk through an example of attaching a file to an email message. We could send the article’s cover image (if available) when a user sends an email about that article to a friend. To attach this image file to the email you created in the previous section, add a call to the attachments method in the email_friend method in the NotifierMailer class, as shown in Listing 12-13.
class NotifierMailer < ApplicationMailer
  def email_friend(article, sender_name, receiver_email)
    @article = article
    @sender_name = sender_name
    if @article.cover_image.present?
      attachments[@article.cover_image.filename.to_s] = @article.cover_image.download
    end
    mail to: receiver_email, subject: 'Interesting Article'
  end
end
Listing 12-13

Adding an Attachment to the Mailer in app/mailers/notifier_mailer.rb: https://gist.github.com/nicedawg/bdf43103d0cb0f896047d9c458600afc

First, since articles don’t require a cover image, we only attempt to add the attachment if a cover image is present. Then, we set the name of the attachment to the file name of the cover image. We could have named it anything we wanted, as long as it had the right extension so that the recipient can view it properly. To set the contents of the attachment, we used the download method—provided by Active Storage to give you the raw data of the attached file. The resulting message preview looks like Figure 12-6. Notice the link to the attachment.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig6_HTML.jpg
Figure 12-6

Message with an attachment delivered to a user’s inbox

Tip

In addition to sending dynamic attachments like the preceding example, you can also add a static attachment that exists on disk by using the File.read method. For instance, if you wanted to send an image found in app/assets/images/blog.png, you’d use File.read(Rails.root.join(‘app’, ‘assets’, ‘images’, ‘blog.png’)) when adding an attachment.

Letting Authors Know About Comments

Just to make sure you’ve grasped how to send email from your Rails applications, this section quickly goes over the complete flow to add another mailer action.

In Chapter 6, we added an after_create callback to the Comment model to email the author of an article after a comment was created—but we never implemented the email. Let’s do that now! We will change five files. First, we’ll add a new action to the Notifier Mailer class; next, we will add new HTML and text mailer templates with the contents of the email to send; then, we will add a previewer for our new mailer action so we can easily test it; finally, we add code to the after_create callback in the Comment model to invoke the mailer when a new comment is created. Listings 12-14 to 12-18, respectively, show the code for these modifications.
class NotifierMailer < ApplicationMailer
  def email_friend(article, sender_name, receiver_email)
    @article = article
    @sender_name = sender_name
    if @article.cover_image.present?
      attachments[@article.cover_image.filename.to_s] = @article.cover_image.download
    end
    mail to: receiver_email, subject: 'Interesting Article'
  end
  def comment_added(comment)
    @article = comment.article
    mail to: @article.user.email, subject: "New Comment for '#{@article.title}'"
  end
end
Listing 12-14

Adding the comment_added Method to app/mailers/notifier_mailer.rb: https://gist.github.com/nicedawg/b6572b22e3627a93072b5a1eb2dead50

<p>
  Someone added a comment to one of your articles <i><%= @article.title %></i>.
</p>
<p>
  Go read the comment:
  <%= link_to @article.title, article_url(@article) %>
</p>
Listing 12-15

The comment_added HTML Mailer Template in app/views/notifier_mailer/comment_added.html.erb: https://gist.github.com/nicedawg/f60a353d255661cdb181ad8306cecf62

Someone added a comment to one of your articles: "<%= @article.title"
Go read the comment:
  <%= article_url(@article) %>
Listing 12-16

The comment_added Text Mailer Template in app/views/notifier_mailer/comment_added.text.erb: https://gist.github.com/nicedawg/4d0690011557da8bf7526a76760995e3

# Preview all emails at http://localhost:3000/rails/mailers/notifier_mailer
class NotifierMailerPreview < ActionMailer::Preview
  # Preview this email at http://localhost:3000/rails/mailers/notifier_mailer/email_friend
  def email_friend
    NotifierMailer.email_friend(Article.first, 'Sender T. Sendington', 'ree.seever@example.com')
  end
  def comment_added
    comment = Article.first.comments.build(
      name: 'Anonymous Reader',
      email: 'guesswho@example.com',
      body: 'This article changed my life.',
    )
    NotifierMailer.comment_added(comment)
  end
end
Listing 12-17

Adding a Previewer for NotifierMailer#comment_added in test/mailers/previews/notifier_mailer_preview.rb: https://gist.github.com/nicedawg/8f6ebddb661a4939f7732dc9de000695

class Comment < ApplicationRecord
  belongs_to :article
  validates :name, :email, :body, presence: true
  validate :article_should_be_published
  after_create :email_article_author
  def article_should_be_published
    errors.add(:article_id, 'is not published yet') if article && !article.published?
  end
  def email_article_author
    NotifierMailer.comment_added(self).deliver
  end
end
Listing 12-18

Updates to app/model/comment.rb: https://gist.github.com/nicedawg/a523e8af12c2559d539351aeb8d9f66d

Most of the code changes we made probably look familiar to you. However, we did something in the preview class we should mention: we built a comment belonging to the first article to use in our mailer. Why did we do this? When the preview action runs, it is connected to your development database. If we had chosen to create a comment instead of building one, then every time you previewed the email, it would add another comment to your database. While that’s not necessarily a problem, you may not want that to happen. By building instead of creating, the comment is never saved to the database. Alternatively, we could have manually created a comment for the first article so we could preview this email—but if we deleted that comment during the course of development, this preview would suddenly be broken.

Another thing we should point out is the email_article_author method in the Comment model; we passed self as the argument to NotifierMailer.comment_added. That’s because the mailer action expects a comment, and in this context, the comment is the current object, which can be referred to as self.

Now that those changes have been made, create an article with your user account and add some comments. You should receive one email message per comment. If you want to, you could add the comment text to the email; that way, you wouldn’t need to go to the article page to read the comment. You could easily implement that by changing the mailer view.

Receiving Email via Action Mailbox

So far, you’ve seen that Action Mailer has extensive support for sending all types of email messages. But what if your application needs to receive email? Action Mailbox is a new feature in Rails 6 which makes it as easy as possible to receive emails into your app and process them.

Though Action Mailbox makes it as easy as it can to receive and parse emails, it can still be tricky to set up. Being able to actually receive email may involve domain name registration, deploying your application to a publicly available web host, signing up for third-party email services, or configuring mail servers to call a Rails command when receiving certain messages.

Walking you through the process of configuring your app to receive actual email would be too difficult, as the variety of systems and services to integrate with is too great and may require payment. However, Action Mailbox offers a tool in development mode to let you submit emails to your app via a form. So we’ll use that tool to help us as we modify our blog to let our authors submit draft articles via email. If at some point you decide to send actual email to your app, be sure to check out the Action Mailbox guide at https://edgeguides.rubyonrails.org/action_mailbox_basics.html.

Installation

To add Action Mailbox capabilities to our blog, we must begin by running Action Mailbox’s installation command:
> rails action_mailbox:install
Copying application_mailbox.rb to app/mailboxes
      create  app/mailboxes/application_mailbox.rb
Copied migration 20200314210550_create_action_mailbox_tables.action_mailbox.rb from action_mailbox
As we see from the command’s output, it did a couple of things. First, it created an ApplicationMailbox class. Then, it created a migration file; Action Mailbox keeps track of which messages it has received and processed in a database table. So let's run the migrations to add this new database table to our database:
> rails db:migrate
== 20200314210550 CreateActionMailboxTables: migrating ==================
-- create_table(:action_mailbox_inbound_emails)
   -> 0.0069s
== 20200314210550 CreateActionMailboxTables: migrated (0.0071s) ===========

That's it! Next, let's take a look at configuring Action Mailbox.

Configuration

Action Mailbox has a few configuration options, as shown in Table 12-4. We won’t deviate from the defaults in our blog, but it’s still helpful to take a look and see what’s possible. Per usual, these values can be changed by putting config.action_mailbox.[setting] = [value] in the appropriate config/environments/ files or in config/application.rb to set the value for all environments.
Table 12-4

Action Mailbox Configuration Options

Option

Description

ingress

Specifies which adapter to use to receive emails. Valid options include :relay, :mailgun, :mandrill, :postmark, and :sendgrid. Depending on the adapter chosen, additional credentials may be required.

logger

Specifies which logger Action Mailbox should use for its logging output. By default, it will use the standard Rails logger.

incinerate

Action Mailbox stores the emails it receives for a certain amount of time and then deletes them. If you wish to keep them forever, set this value to false.

incinerate_after

By default, when incinerate is true, Action Mailbox will destroy emails after storing them for 30 days. You can alter the storage policy by changing this value. (e.g., config.action_mailbox.incinerate_after = 60.days).

queues

Action Mailbox uses queues to schedule routing and incineration jobs. We haven’t talked about queues yet, but we will in the next chapter when we discuss Active Job. For now, just know this option gives you a chance to rename the queues which Action Mailbox uses by default.

Now that we’ve installed Action Mailbox and perused its options (though we didn’t need to change any yet), let’s get started on enhancing our blog by allowing authors to create draft articles via email.

Creating Draft Articles via Email

Before we jump in to adding this feature, let’s think about the various things we need to do in order to accomplish this.

First, we need to give each author a special email address, so that when we receive an email, we know what it’s for (creating a draft article) and we know whom it’s for. Ideally, we’ll make this a hard-to-guess email address to help prevent the public from being able to create draft articles for the author.

Then, we’ll need to process the email. Based on the email’s To: address, subject, and body, we’ll create a draft article associated with the right user, the right subject, and the draft body.

Finally, we’ll send an acknowledgement email to the author, so they know our app successfully processed their content. It will also have a convenient link to allow them to edit their draft article.

Assigning Authors a Special Email Address

First, let’s give each author a unique email address which they can use to create draft articles via email. To do this, we will add a secret token to each user record for this purpose and display it on their “Edit Password” page.

Let’s generate and run the necessary migration:
> rails g migration add_draft_article_token_to_users draft_article_token:token
      invoke  active_record
      create    db/migrate/20200314215947_add_draft_article_token_to_users.rb
> rails db:migrate
== 20200314215947 AddDraftArticleTokenToUsers: migrating ================
-- add_column(:users, :draft_article_token, :string)
   -> 0.0034s
-- add_index(:users, :draft_article_token, {:unique=>true})
   -> 0.0016s
== 20200314215947 AddDraftArticleTokenToUsers: migrated (0.0053s) =========

Notice we gave a hint to the migration generator that our new field—draft_article_token—is a token. Rails handles that specially. We can see in the output that it created our field as a string in the database, but also added a unique index to the column, ensuring that each user has a different token. (If two users had the same draft_articles_token, we might end up creating a draft article associated with the wrong user!)

Next, we’ll modify our User class so that it will treat the draft_article_token like a token, as shown in Listing 12-19. By doing so, it will automatically set a random, unique token when a user is being created. It also gives the User class a method called regenerate_draft_article_token we could use if we needed to.

We’ll also add a method to return the full special email address for convenience, so we don’t end up repeating it throughout the blog’s code base.
require 'digest'
class User < ApplicationRecord
  attr_accessor :password
  validates :email, uniqueness: true
  validates :email, length: { in: 5..50 }
  validates :email, format: { with:  /\A[^@][\w.-]+@[\w.-]+[.][a-z]{2,4}\z/i }
  validates :password, confirmation: true, if: :password_required?
  validates :password, length: { in: 4..20 }, if: :password_required?
  validates :password, presence: true, if: :password_required?
  has_one :profile
  has_many :articles, -> { order 'published_at DESC, title ASC' },
           dependent: :nullify
  has_many :replies, through: :articles, source: :comments
  has_secure_token :draft_article_token
  before_save :encrypt_new_password
  def self.authenticate(email, password)
    user = find_by email: email
    return user if user && user.authenticated?(password)
  end
  def authenticated?(password)
    self.hashed_password == encrypt(password)
  end
  def draft_article_email
    "#{draft_article_token}@drafts.example.com"
  end
  protected
  def encrypt_new_password
    return if password.blank?
    self.hashed_password = encrypt(password)
  end
  def password_required?
    hashed_password.blank? || password.present?
  end
  def encrypt(string)
    Digest::SHA1.hexdigest(string)
  end
end
Listing 12-19

Adding draft_article_token to User Model https://gist.github.com/nicedawg/6848c1a2348a1afa28406f93ced19e97

After this code change, new users will automatically receive a unique token. And we can easily generate the user’s special draft article email address. But what about our existing users? We can use this code in the rails console to generate draft_article_token values for them and then verify it worked:
> rails c
irb(main):001:0> User.find_each { |u| u.regenerate_draft_article_token }
    .... SQL output ...
irb(main):002:0> User.first.draft_article_token
  User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> "qzi2k2g9ULwZhVqFQTKUet5M"

Now that we have secure draft article tokens for each user (and will automatically create tokens for new users), let's show our authors their unique, secure email address they can use to send draft articles via email.

Perhaps near the top of the “new article” form would be a good place to show them this email address. We could also explain how the process works. Let’s add this to our app/views/articles/new.html.erb, as Listing 12-20 shows.
<h1>New Article</h1>
<p>
  <em>Did you know that you can submit draft articles via email?</em>
  Send an e-mail to <%= mail_to current_user.draft_article_email %> with the title in your subject, and your draft content in the body.
</p>
<%= render 'form', article: @article %>
<%= link_to 'Back', articles_path %>
Listing 12-20

Showing the Draft Article Email at the Top of app/views/articles/new.html.erb https://gist.github.com/nicedawg/6a6dbb6049f28c9873ed0d963488e477

After making these changes, go to the New Article page in your browser. It should resemble Figure 12-7.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig7_HTML.jpg
Figure 12-7

New article form with special draft article email address

With these changes, authors are aware of their special draft article email address and can start sending email to our blog to create draft articles. But what will our application do with those emails when it receives them? We clearly have more work to do. Next, we’ll add code to process these emails and create draft articles from them.

Processing the Email

We need a place to put code that can handle incoming emails which are meant to create draft articles. It needs to be able to find the right author record and then create an article with the right title and body. If we wanted to take this action in response to an incoming HTTP request, we’d do that in a controller. But when receiving email input, we’ll do that in a mailbox.

In an Action Mailbox context, a mailbox receives emails and decides what to do with them. We can have many mailboxes, each one for a particular purpose, similar to controllers. Since we’re going to be creating draft articles from emails, we should name our new mailbox accordingly.

To create our new mailbox, run the following Rails command:
> rails g mailbox draft_articles
      create  app/mailboxes/draft_articles_mailbox.rb
      invoke  test_unit
      create    test/mailboxes/draft_articles_mailbox_test.rb

As we can see, the generator created our DraftArticlesMailbox class in the app/mailboxes directory and also added a placeholder test file for us (which we won’t use yet.)

If you view the DraftArticlesMailbox class, you’ll see it’s a blank state. It has an empty process method, and that’s it. We’ll need to update the DraftArticlesMailbox class in app/mailboxes/draft_articles_mailbox.rb to match Listing 12-21.
class DraftArticlesMailbox < ApplicationMailbox
  before_processing :require_author
  def process
    author.articles.create(
      title: mail.subject,
      body: mail.body,
    )
  end
  private
  def require_author
    bounce_with DraftArticlesMailer.no_author(mail.from) unless author
  end
  def author
    @author ||= User.find_by(draft_article_token: token)
  end
  def token
    mail.to.first.split('@').first
  end
end
Listing 12-21

Processing Emails in app/mailboxes/draft_articles_mailbox.rb https://gist.github.com/nicedawg/fdb5245cb9a8235d6f0894f0d4dc31f5

This isn’t a ton of code, but there’s a bit to unpack here. First, we see that our DraftArticlesMailbox class inherits from ApplicationMailbox. Any mailboxes we create to work with Action Mailbox should inherit from this class.

Next, we see before_processing :require_author. Similar to how Action Controller allows you to define filters to run before, after, or around your controller action, Action Mailbox allows you to define filters to run before, after, or around your process method. Here, we reference a method we added called require_author which we’ll explain in a minute. We could have put the code for require_author at the top of our process method, but extracting it to a separate method keeps our process method tidy and encourages reuse. So now we understand the general idea—before we process the email, we make sure we could locate the right user object.

Before we dig into the methods we added, let’s talk about some magic that Action Mailbox added to our class. First, notice how we referenced something called mail a few times in our class. Where did that come from? Action Mailbox provides that to our class—and it returns the object representing the email we’re processing. So we can use it to access the email’s To, Subject, and Body fields and more.

We also used a method called bounce_with; this method marks the inbound email record as “bounced” (so the system knows it was processed but failed), and the method takes an ActionMailer::MessageDelivery instance as an argument. Action Mailbox will then deliver this message in response to the failure. This is an elegant way to let you do both things—mark as bounced and deliver an error message—with very little code.

Now we can understand the code we added more clearly. In require_author, we bounce a message back to the sender unless we can find a valid author.

In author, we search for the user record with the right token. You may not have seen the ||= operator yet, often referred to as the “or equals” operator. Using this in conjunction with an instance variable, as we did in the author method, is a common pattern called memoization —which attempts to prevent redundant, expensive queries. We refer to author multiple times in this class, but don’t need to actually execute an SQL query more than once. So the author method returns the value of @author if it has a value. Otherwise, it executes the User.find_by query, assigns the value to @author, and then returns the value.

In token, we access the object representing the email and get its to value…which rightly returns an array of email addresses (since an email’s To field may have multiple recipients). For our purposes, we assume the first address is the one we want, and then we split the address based on the @ symbol and take the first piece of the resulting array to return the token portion of our email address.

Finally, to the meat of the class. The process method creates an article associated with the author found via the token in the email address and sets its title and body based on the email’s subject and body, respectively.

You might notice the explanation of this class is longer than the code itself, a tribute to the concise readability of well-written Ruby code. The code is rather self-documenting. Even if you don’t know what’s happening behind the scenes, a developer could read the code and have a good sense of the class’s intentions.

There’s one more matter to take care of. We referenced a mailer and mailer action which don’t yet exist. Let’s take care of that now:
> rails g mailer draft_articles no_author
      create  app/mailers/draft_articles_mailer.rb
      invoke  erb
      create    app/views/draft_articles_mailer
      create    app/views/draft_articles_mailer/no_author.text.erb
      create    app/views/draft_articles_mailer/no_author.html.erb
      invoke  test_unit
      create    test/mailers/draft_articles_mailer_test.rb
      create    test/mailers/previews/draft_articles_mailer_preview.rb
We need to implement the no_author mailer action to send a message back to the sender, informing them we couldn’t process their email. Edit your DraftArticlesMailer to match Listing 12-22.
class DraftArticlesMailer < ApplicationMailer
  def no_author(to)
    mail to: to, subject: 'Your email could not be processed' do |format|
      content = 'Please check your draft articles email address and try again.'
      format.html { render plain: content }
      format.text { render plain: content }
    end
  end
end
Listing 12-22

Sending Notifications That the Draft Article Couldn’t Be Created https://gist.github.com/nicedawg/653f0fefb94f8afda2e1021a0b6287e4

Notice how in addition to specifying the “To:” address and subject of the message, this time we passed a block which takes format as an argument. This lets us provide the content inline for our HTML and text formats. Since we just want to send a plain, simple message for now, we’ll do this instead of creating separate mailer view template files.

Before we finish with our DraftArticlesMailer, we should update its preview. Let’s edit test/mailers/previews/draft_articles_mailer_preview.rb so DraftArticlesMailerPreview matches Listing 12-23.
class DraftArticlesMailerPreview < ActionMailer::Preview
  def no_author
    DraftArticlesMailer.no_author('test@example.com')
  end
end
Listing 12-23

Update DraftArticlesMailerPreview https://gist.github.com/nicedawg/d24dcc1464e41ef468589ee4ad2e8755

Now we can preview our new mailer via http://localhost:3000/rails/mailers if we want to.

Finally, our mailbox is prepared to turn emails into draft articles when it can and send an email back to the sender when it can’t find the right user. That sounds good, but how will our app know to send emails to our new DraftArticlesMailbox? We have a little more work to do.

Next, we need to update our ApplicationMailbox to send the right emails to our DraftArticlesMailbox. We know we only need to handle emails addressed to sometoken@drafts.example.com, so let’s edit ApplicationMailbox to look like Listing 12-24.
class ApplicationMailbox < ActionMailbox::Base
  routing /@drafts\./i => :draft_articles
end
Listing 12-24

Routing Emails to Our DraftArticlesMailbox in ApplicationMailbox https://gist.github.com/nicedawg/1ac117138347346a67bf7a82f9117023

We added a routing line which says “if the To: address of the email has ‘@drafts.’ in it, then send it to the DraftArticlesMailbox.” If you have multiple mailboxes in your app, you will likely need multiple routing calls to send emails to the right mailboxes. Note that the routes will be processed in top-down order, executing the first match (similar to Action Pack’s router). Action Mailbox’s router also allows the symbol :all instead of a regular expression to say “send all emails to this mailbox.”

Now that we’ve added the mailbox and routed the right types of email to it, let’s see if it works! As mentioned in the beginning of the chapter, Action Mailbox provides a helpful tool in development to “send” emails to your app. Visit http://localhost:3000/rails/conductor/action_mailbox/inbound_emails/ in your browser, and you should see a page that says “All Inbound Emails” and has an empty table. This page lists all of your ActionMailbox::InboundEmail records in your database.

Click the “Deliver new inbound email” link, and you should see a form with fields for creating an ActionMailbox::InboundEmail record. Populate the “From” field with your email address and the “To” field with the draft article email address shown on your New Article page, provide a title and body, and click “Deliver inbound email.” Before submitting, your screen will look something like Figure 12-8.
../images/314622_4_En_12_Chapter/314622_4_En_12_Fig8_HTML.jpg
Figure 12-8

Testing our new mailbox with a new inbound email

Once you submit the email, your app should receive the email, route it to the DraftArticlesMailbox, find your user record based on the token from the To: address, and create an article with the subject of your email as its title and the body of your email as its body.

Did it work? Go back to your blog’s root URL, http://localhost:3000. If the email was able to be processed, you will see the unpublished article there. (Hmm, we ought to change that! We won’t take time to do it now, but a live blog wouldn’t want to serve unpublished articles to the public.)

If it didn’t work, make sure you addressed your email correctly. The dev tool we used to send an email redirected to a details page for the email we sent. It shows the full source of the email we generated and gives us a chance to send it again.

If the email was addressed correctly, then check over our recent code changes closely. You may also look in your server’s output for clues, though sending/processing an email generates quite a bit of logging.

Now that we can create draft articles by processing an email, we’ve almost finished adding our feature. However, there’s one last thing that will make it more polished—letting the author know their email has been processed.

Responding to the Author

As our feature currently stands, the author is left wondering if the email they sent to create a draft article actually created a draft article. Rather than leaving them worried or in doubt, we can send them an email to acknowledge their submission and even to give them a helpful link to edit the draft article.

First, let’s add a mailer action to the DraftArticleMailer called created, as shown in Listing 12-25.
class DraftArticlesMailer < ApplicationMailer
  def created(to, article)
    @article = article
    mail to: to, subject: 'Your Draft Article has been created.'
  end
  def no_author(to)
    mail to: to, subject: 'Your email could not be processed' do |format|
      content = 'Please check your draft articles email address and try again.'
      format.html { render plain: content }
      format.text { render plain: content }
    end
  end
end
Listing 12-25

Adding DraftArticleMailer#created Action https://gist.github.com/nicedawg/760e8a533498503ab8f7133318830fe6

And let’s add the HTML and text mailer view templates for this new mailer action as shown in Listings 12-26 and 12-27, respectively.
<p>
  Your draft article has been successfully created.
</p>
<p>
  You may edit your article here:
  <%= link_to @article.title, edit_article_url(@article) %>
</p>
Listing 12-26

app/views/draft_articles_mailer/created.html.erb https://gist.github.com/nicedawg/9ba9ca657cd66e34a6b0cbaf82f0edf9

Your draft article has been successfully created.
You may edit your article here:
  <%= edit_article_url(@article) %>
Listing 12-27

app/views/draft_articles_mailer/created.text.erb https://gist.github.com/nicedawg/b5ed9f8d972b90cdf0baeac23ca5f668

Next, let’s update our DraftArticlesMailerPreview class in test/mailers/previews/draft_articles_mailer_preview.rb, as shown in Listing 12-28, so we can preview our new created mailer action.
class DraftArticlesMailerPreview < ActionMailer::Preview
  def created
    DraftArticlesMailer.created('test@example.com', Article.first)
  end
  def no_author
    DraftArticlesMailer.no_author('test@example.com')
  end
end
Listing 12-28

test/mailers/previews/draft_articles_mailer_preview.rb https://gist.github.com/nicedawg/adeaf5ae0325a1c6c1a5b460861f2099

Now that we’re all set up to preview and send the DraftArticlesMailer.created message, the last thing we need to do is to have our DraftArticlesMailbox send this message when it’s appropriate. Let’s update our DraftArticlesMailbox so it looks like Listing 12-29.
class DraftArticlesMailbox < ApplicationMailbox
  before_processing :require_author
  def process
    article = author.articles.create!(
      title: mail.subject,
      body: mail.body,
    )
    DraftArticlesMailer.created(mail.from, article).deliver
  end
  private
  def require_author
    bounce_with DraftArticlesMailer.no_author(mail.from) unless author
  end
  def author
    @author ||= User.find_by(draft_article_token: token)
  end
  def token
    mail.to.first.split('@').first
  end
end
Listing 12-29

Updating DraftArticlesMailbox to Send “Created” Email https://gist.github.com/nicedawg/b6bdb5d3289079cd7f91c6e6657658d2

In this last code change, we changed author.articles.create to say author.articles.create! instead. This will ensure that if the article can’t be created, an exception will be raised and the process method will stop before sending the created message. If create! succeeds, then we’ll construct our created message using the “From” address of the sender, and the resulting draft article, and then deliver it.

Try it out! Visit http://localhost:3000/rails/conductor/action_mailbox/inbound_emails/ to resend an email or to send a new email. If your Action Mailer delivery works correctly and you use your email address as the “From” message, then in response to your email to create a draft article, you will receive an acknowledgement email with a link to the newly created draft article. It did take a bit of work to make it happen, but in retrospect, that was a really cool feature to add with not that much work.

Summary

In this chapter, we learned how to send email from our web application using Action Mailer. We configured Action Mailer to talk to our mail server and learned the most common configuration parameters we can use to fine-tune how Action Mailer works with our application.

We learned that Action Mailer allows us to send email messages based on view templates and how to use implicit parts for text and HTML messages, as well as how to use the attachments helper to add attachments to our messages. We also learned how to preview messages so we don’t even have to deliver them while developing.

We also learned how to receive and process email using Action Mailbox. This chapter only scratched the surface, but serves as a good starting point. Should your application ever need to perform this task, you know where to look when you need more information.

Now that our app is sending (and even receiving) emails, it’s time to talk about another component of Rails—Active Job. In the next chapter, we’ll discuss how it can be used to improve performance of your application when performing tasks that have the potential to bog it down.