Sending Emails in Rails with Action Mailer and Gmail
Tags: rails, ruby, beginners
Published: 2019-05-19 07:07:13

In this post, I will be setting up a mailer in Rails that will send out an email to the website owner when a customer submits an order. (I was using Rails 6 but this should also work in Rails 5).

The steps:

1. Set up a mailer with `rails generate mailer`
2. Create email templates (views)
3. Tell the appropriate controller action to send the email
4. Set up an email previewer
5. Configure the mail settings for Gmail.
6. Set up tests

Let's assume we already have a basic Order Model and Orders Controller setup, which simply shows a confirmation to the customer via a flash message when the order is successful:

```ruby
# app/controllers/orders_controller.rb

class OrdersController < ApplicationController
  def new
    @order = Order.new
  end

  def create
    @order = Order.new(order_params)

    if @order.save
      flash[:success] = t('flash.order.success')
      redirect_to root_path
    else
      flash.now[:error] = t('flash.order.error_html')
      render :new
    end
  end

  private

  def order_params
    params.require(:order).permit(:name, :email, :address, :phone, :message)
  end
end

```

We also want to send the email to the website owner when the order is received, otherwise, how would the know? 


To get started, we will first need to create a mailer which is easy to do with Rails' `generate mailer` command. In this case, the mailer will be used in the `OrdersController` so we'll name it `OrderMailer`:

```
$ rails generate mailer OrderMailer
```

This will create the following files and output:

```
create  app/mailers/order_mailer.rb
invoke  erb
create    app/views/order_mailer
invoke  test_unit
create    test/mailers/order_mailer_test.rb
create    test/mailers/previews/order_mailer_preview.rb
```

Let's out the `app/mailers` folder. We'll find the `application_mailer.rb` and the newly created `order_mailer.rb` files.

```ruby
# app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com' # Replace this email address with your own
  layout 'mailer'
end

```

```ruby
# app/mailers/order_mailer.rb

class OrderMailer < ApplicationMailer
end

```

The new `OrderMailer` works very similarly to a regular controller. A controller prepares content like HTML and shows it to the user through views. A mailer prepares content in the form of an email and delivers it. We can create an email by adding a method to the mailer, as you would add an action to a controller.

Let's add a method for the order email to the `OrderMailer`:

```ruby
# app/mailers/order_mailer.rb

class OrderMailer < ApplicationMailer
  def new_order_email
    @order = params[:order]

    mail(to: <ADMIN_EMAIL>, subject: "You got a new order!")
  end
end
```

*Replace `<ADMIN_EMAIL>` with the email you want to use, preferably hidden away as an environment variable.

Any instance variables in `new_order_email` can be used in the mailer views. The `params[:order]` will be provided when we tell the `OrderController` to send the email (which I'll go over below).

Let's create an email view file now, making sure to name the file the same as the method. Make a `new_order_email.html.erb` in the `app/views/order_mailer/` folder:


```erb
# app/views/order_mailer/new_order_email.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
  </head>
  <body>
    <p>You got a new order from <%= @order.name %>!</p>
    <p>
    Order details<br>
    --------------------------
    </p>
    <p>Name: <%= @order.name %></p>
    <p>Email: <%= @order.email %></p>
    <p>Address: <%= @order.address %></p>
    <p>Phone: <%= @order.phone %></p>
    <p>
    Message:<br>
    ----------
    </p>
    <p><%= @order.message %></p>
  </body>
</html>
```

And as a best practice, let's also create a text version of the email in case the receiver doesn't use HTML email. This goes in the same folder and has the same file name but uses the `text.erb` extension instead of `html.erb`.

```erb
# app/views/order_mailer/new_order_email.text.erb

You got a new order from <%= @order.name %>!
===============================================

Order Details:
--------------------------

Name: <%= @order.name %>
Email: <%= @order.email %>
Address: <%= @order.address %>
Phone: <%= @order.phone %>
 
Message:
<%= @order.message %>
```

Now that we have the emails set up, next we'll tell the `OrdersController` to send an email when an order is made, that is, after an order is saved in the `create` action.

```ruby
class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)

    if @order.save
      OrderMailer.with(order: @order).new_order_email.deliver_later
			
      flash[:success] = "Thank you for your order! We'll get contact you soon!"
      redirect_to root_path
    else
      flash.now[:error] = "Your order form had some errors. Please check the form and resubmit."
      render :new
    end
  end

  ...
end
```

Here, we added the following line of code after the order was saved:

```
OrderMailer.with(order: @order).new_order_email.deliver_later
```

Notice the `with(order: @order)` code. This is what gives the `OrderMailer` access to the order info as a param. Remember setting the instance variable with `@order = params[:order]` in the `OrderMailer`? That's where the param is coming from!

So, how can we preview this email before sending it? Well, when we generated our mailer, a preview file was created in the `test/mailers/previews/` folder. It contains a file called `order_mailer_preview.rb` that has an empty `OrderMailerPreview` class in it:

```ruby
# test/mailers/previews/order_mailer_preview.rb

# Preview all emails at http://localhost:3000/rails/mailers/order_mailer
class OrderMailerPreview < ActionMailer::Preview

end
```

If we try to go to the `http://localhost:3000/rails/mailers/order_mailer` URL, it just shows us a white page with the text "Order Mailer". 

To set up a preview for our new order email, simply add a method with the same name as the mailer method you want to preview (in this case `new_order_email` and set up the mailer:

```ruby
# Preview all emails at http://localhost:3000/rails/mailers/order_mailer
class OrderMailerPreview < ActionMailer::Preview
  def new_order_email
    # Set up a temporary order for the preview
    order = Order.new(name: "Joe Smith", email: "joe@gmail.com", address: "1-2-3 Chuo, Tokyo, 333-0000", phone: "090-7777-8888", message: "I want to place an order!")

    OrderMailer.with(order: order).new_order_email
  end
end
```

Restart the Rails server and navigate to `http://localhost:3000/rails/mailers/order_mailer/new_order_email` to see the email. You can even see both the HTML and the text versions. Awesome! Tweak the look of the email until you like it. (Additionally, `http://localhost:3000/rails/mailers/order_mailer` now shows a list of available previews.)

Lastly, we need to configure our Rails app to send emails via Gmail. To do so, we'll add the following settings to our `config/environments/production.rb`:

```ruby
# config/environments/production.rb

config.action_mailer.delivery_method = :smtp
host = 'example.com' #replace with your own url
config.action_mailer.default_url_options = { host: host }

# SMTP settings for gmail
config.action_mailer.smtp_settings = {
  :address              => "smtp.gmail.com",
  :port                 => 587,
  :user_name            => <gmail_username>,
  :password             => <gmail_password>,
  :authentication       => "plain",
  :enable_starttls_auto => true
}
```

Replace `<gmail_username>` and `<gmail_password>` with your own username and password, which would preferably be hidden away as environment variables. Note on the password: I *highly* recommend enabling 2-Step Verification and registering an "app password" to use in the app or you're likely to run into problems with Gmail blocking the emails. See below in the troubleshooting section.

For local use only or development use, use

```ruby
host = 'localhost:3000'
config.action_mailer.default_url_options = { :host => 'localhost:3000', protocol: 'http' }
``` 

instead of the above settings for `host` and `config.action_mailer.default_url_options`.

To test if the mail *really* gets sent in development, too, add the same settings to `config/environments/development.rb`. You can later change the line `config.action_mailer.delivery_method = :smtp` to `config.action_mailer.delivery_method = :test` to prevent sending real emails during development.

# Troubleshooting Email Sending Errors

The above configuration actually didn't work for me at first, due to Google's security features, and I was getting `Net::SMTPAuthenticationError` errors. Here's how I go everything to work.

### Account with 2-step Verification
If your Gmail account uses 2-step verification, you will need to get an app password and use that instead of your regular password. If you don't use 2-step verification, I recommend turning it on to avoid getting the emails blocked by Google.

To create an app password, go to your Google account settings and navigate to the "Security" tab. Under "Signing in to Google", click on the "App passwords" menu item (this will only be available if you have 2-step verification turned on). Next, select `Other (Custom name)` under the "Select app" dropdown and enter the name of your app or something else useful. Click "Generate" and your new app password will appear on the screen. Make sure you copy it before closing the window, or you won't be able to see the password again.

Now, in your Rails app mailer settings, replace the `<gmail_password>` with the new app password instead.

### Account without 2-step verification
If you don't use 2-step verification, you will have to allow your account to be accessed by "less secure apps". In your Google settings under the "Security" tab, look for the "Less secure app access" section and click "Turn on access".

After pushing to production, I still had problems sending mail because Google was blocking access from unknown locations, in this case, the app in production. I was able to solve the problem by going through the "Display Unlock Captcha" process. If you still have problems after doing the above, this will grant access to the account for a few minutes, allowing you to register the new app.

Activate this option by going to [http://www.google.com/accounts/DisplayUnlockCaptcha](http://www.google.com/accounts/DisplayUnlockCaptcha). After that, have the app send an email again. This should register the app so that you will be allowed to send emails from then on!

UPDATE: After a few months, for some reason, emails sent through the app were getting blocked again and security warnings would get sent to the email account. To avoid headaches, I would go with the 2-step verification + app password method.

### Rails credentials issue with Heroku
One last problem I had in production with Heroku was forgetting to register my Rails app's master key in the Heroku app settings and configuring Rails to require the master key. This will be a problem if you are using Rails' credential file (`credentials.yml.enc`) to keep track of your secret keys (in this case, your email and password). 

In development, Rails can access the secret credentials because the master key is directly available on the system, but in production with Heroku, the app cannot access secret keys without first registering your master key with Heroku.

To fix this problem, in the `config/environments/production.rb` file, uncomment or add the following line:

```
config.require_master_key = true
```

Then, in Heroku app settings, register a key called `RAILS_MASTER_KEY`. Enter the value found inside the `config/master.key` file. This allows Heroku to access secret keys registered inside the `credentials.yml.enc` file.

# Testing

Lastly, let's make sure we have some tests set up!

If there isn't already an `orders.yml` file under `test_fixtures`, create one and add: 

```yaml
# test/fixtures/orders.yml

one:
  name: "Joe Smith"
  email: "joe@gmail.com"
  address: "1-2-3 Chuo, Tokyo, 333-0000"
  phone: "090-7777-8888"
  message: "I want to place an order!"
```

This is an easy way to pull in a sample order for use in the mailer tester.

Next, in `test/mailers/order_mailer_test.rb` (create this file if it is not already there), we can add a simple test that asserts that the email is getting sent and that the content is correct.

```ruby
require 'test_helper'

class OrderMailerTest < ActionMailer::TestCase
  test "new order email" do
    # Set up an order based on the fixture
    order = orders(:one)
    
    # Set up an email using the order contents
    email = OrderMailer.with(order: order).new_order_email

    # Check if the email is sent
    assert_emails 1 do
      email.deliver_now
    end

    # Check the contents are correct
    assert_equal [<ADMIN_EMAIL>], email.from
    assert_equal [<ADMIN_EMAIL>], email.to
    assert_equal "You got a new order!", email.subject
    assert_match order.name, email.html_part.body.encoded
    assert_match order.name, email.text_part.body.encoded
    assert_match order.email, email.html_part.body.encoded
    assert_match order.email, email.text_part.body.encoded
    assert_match order.address, email.html_part.body.encoded
    assert_match order.address, email.text_part.body.encoded
    assert_match order.phone, email.html_part.body.encoded
    assert_match order.phone, email.text_part.body.encoded
    assert_match order.message, email.html_part.body.encoded
    assert_match order.message, email.text_part.body.encoded
  end
end
```

The `<ADMIN_EMAIL>` should be replaced with the email you are using.


`email.html_part.body.encoded` checks the content in the HTML email while `email.text_part.body.encoded` checks the text email.

# Conclusion

We should now have a functional mailer that notifies us (or the website owner) of newly incoming orders!

Once the Rails mailer and Gmail settings are properly configured, we can easily send other emails from other controller actions by generating and setting up new mailers in much the same way. :)