Using Rails Action Mailer

This page summarizes the projects mentioned and recommended in the original post on dev.to

Our great sponsors
  • WorkOS - The modern identity platform for B2B SaaS
  • InfluxDB - Power Real-Time Data Analytics at Scale
  • SaaSHub - Software Alternatives and Reviews
  • Devise

    Flexible authentication solution for Rails with Warden.

  • We are using tags because rails mailer views because they do not support external CSS. You could also do in-line styling. Next up the text.erb file

    
    
    Thank you for your order, <%= @user.name %>!
    
    ===============================================
    
    Here are the details of your order
    
    Item: <%= @order.item %>
    
    Price: <%= @order.price %>
    
    Description: <%= @order.description %>
    
    Enter fullscreen mode Exit fullscreen mode

    Adding previews for our mailer

    At this point, our mailer should work. Before trying it out, we will make a preview for it first. The generator we ran earlier to make our mailer already generated this file for us. It should be in test/mailers/previews/order_mailer_preview.rb. In this file we will create a method called order_email. It will pull the first user out of the database and the first order just so it has the data to fill the preview. put this in your order_mailer_preview.rb file.

    # test/mailers/previews/order_mailer_preview.rb
    
    class OrderMailerPreview < ActionMailer::Preview
      def order_email
        OrderMailer.with(user: User.first).order_email
      end
    end
    
    Enter fullscreen mode Exit fullscreen mode

    Everything should be good to go now! However, the preview won't work until we add some data. It can't render the templates with no User or Orders in the database, so lets add a User and an Order! We could spin up the server and do it through the site, but I will do it in console here. You can do it through the site if you'd like. If not, start up rails console by typing in rails c in terminal

    irb(main):001:0>User.create(email: "[email protected]", name: "Johnny")
    irb(main):001:0>Order.create(item: "NBA Jersey", price: 110, description: "NBA Jersey for Joel Embiid")
    irb(main):001:0>exit
    
    Enter fullscreen mode Exit fullscreen mode

    Now with this data added, spin up the server with rails s in terminal. Next you can go to localhost:3000/rails/mailers and you will see our Order Mailer with our order_email method. Click on order_email and you should see the preview for our email. You can switch between HTML and plain text previews.

    Adding tests to our mailer

    Now we will add tests to make sure that 1. our mailer is enqueued when it should be(after an order is placed) and 2. that the email contains the content we are expecting. Since the preview works, we should be able to write a passing test. If you spin up the server and make a new order, you should get the email that opens up in a new tab. Everything should work, but we will write tests to back that up, and so we don't have to test the mailer by hand everytime we make a change to the mailer system. Testing the mailer by hand to see if it still works everytime you make a change to the mailer system, gets slow and tedious, fast. That's where tests come in. We could have written the tests first and developed our mailer until they pass(known as TDD, Test Driven Development), but I prefer to do tests after. Our first test is going to see if the email contains the content we expect it to. First, we need to add fixtures, aka dummy data, for the tests to use. Because we don't actually want to write to the database or make actual queries to the DB. Add this to the users.yml and orders.yml fixtures. These files were auto generated when we ran the scaffold generator for both Models.

    # test/fixtures/users.yml
    
    one:
      email: [email protected]
      name: Example
      id: 1
    
    Enter fullscreen mode Exit fullscreen mode
    # test/fixtures/orders.yml
    
    one:
      item: Item
      price: 20
      description: Description
      user_id: 1
    
    Enter fullscreen mode Exit fullscreen mode

    Now with our fixtures setup, we can begin writing our tests. First test we will write we see if the email has the contents we expect it to have.

    # test/mailers/order_mailer_test.rb
    
    require "test_helper"
    
    class OrderMailerTest < ActionMailer::TestCase
    
        setup do
          @order = orders(:one)
          @user = users(:one)
        end
    
        test "send order details email to customer" do
          email = OrderMailer.with(order: @order).order_email
    
          assert_emails 1 do
            email.deliver_now
          end
    
          assert_equal [@user.email], email.to
          assert_match(/Below are the details of your order/, email.body.encoded)
        end
    end
    
    Enter fullscreen mode Exit fullscreen mode

    Lets break down this first test. So first we setup the test to use our fixtures created in the previous step. We make an instance variable that uses our Users and Orders fixtures. In the test block, we create an email with our OrderMailer with the data from our Orders fixture, then we call the order_email method from our OrderMailer . Next we make sure that only one email is sent with the line assert_emails 1 do and we send the email. The last two lines check to see that the email was sent to the right user, and that part of the body also matches. We are not concerned with if it matches the content of the entire body, it would make the test too brittle. Next we will write a test to make sure the email is enqueued when it's supposed to be. First, we need a helper for our test. You are going to need to make the file orders_helper.rb in test/helpers directory. Put this in orders_helper.rb

    # test/helpers/orders_helper.rb
    
    module OrdersHelper
      def order_attributes
        {
    
        user: users(:one),
        item: "Item",
        price: 30,
        description: "Description"
    
        }
      end
    end
    
    Enter fullscreen mode Exit fullscreen mode

    Here we use a helper instead of our yaml file because when assigning attributes, you must pass a hash as an argument. If you try to pass our orders fixture, the test will fail with an error. With our helper, we can now write our test to see if an email is enqueued when there is a new Order.

    # test/models/order_test.rb
    
    require "test_helper"
    require "helpers/orders_helper"
    
    class OrderTest < ActiveSupport::TestCase
        include ActionMailer::TestHelper
        include OrdersHelper
    
        test "sends email when order is created" do
          order = Order.create!(order_attributes)
          assert_enqueued_email_with OrderMailer, :order_email, args: {order: order}
        end
    end
    
    Enter fullscreen mode Exit fullscreen mode

    Let's go over the code. We have our test_helper as usual. Then we pull in our helper we just made in the last step. In our class we bring in ActionMailer::TestHelper to use the assert_enqueued_email_with method, and of course we include our OrdersHelper. Next is the actual test, we create an order with our order_attributes which was defined in our module OrdersHelper from the last step. Then we checked to see if an email was enqueued with our OrderMailer using the order_email method defined in our mailer. We then pass it the created order. Running rails test in terminal, all tests should pass and be green. Starting our local server rails s we can create an order and see that we get an email sent that opens in another tab, thanks to our letter_opener gem we added at the start of this tutorial. Our mailer is complete! Next we will get our mailer working on a production server. If you don't know how to deploy a Rails app, feel free to skip the next section.

    Sending mail in production

    If you don't know how to deploy to Heroku, you can use whatever you want. If you don't know how to deploy a Rails app into production, you can skip this section.

    There are a ton of ways to send emails in production. Send Grid, MailGun, among many others. The easiest(and free way), is to use gmail. In order to send emails from our app with gmail, we need to make an app password, a setting in our Google account. Here we will create an app password. It is a special password that can be used for our app to send emails, without actually using our gmail account password. In order to do this, you need to set up 2FA on your account. So do that if you haven't done that yet. Under Signing in to Google you should see App passwords. Click that, then you will see a few drop downs. First one, Select app, pick Mail. Under Select device, pick Other. It will ask for a name, you can name it whatever you want. I used mailer-example. If you are on Heroku, put this password in your Config Vars. On the Heroku dashboard click settings and look for Config Vars. Click Reveal Config Vars and add your env variable(I used GMAIL_USER_PASSWORD) with the password generated by Google. I linked at the end of this post the docs from Heroku on how to use Config Vars if you get stuck.

    Next step

    We need to setup our production config to use gmail in production. We need to edit our production.rb and add the following:

    # config/environments/production.rb
    
    config.action_mailer.delivery_method = :smtp
    
    config.action_mailer.smtp_settings =
    
      {
        address: "smtp.gmail.com",
        port: 587,
        domain: "example.com",
        user_name: "[email protected]",
        password: ENV["GMAIL_USER_PASSWORD"],
        authentication: "plain",
        enable_starttls_auto: true,
        open_timeout: 5,
        read_timeout: 5
      }
    
    Enter fullscreen mode Exit fullscreen mode

    Change your user_name to your gmail email. Our password is protected in an environment variable, which we saved on Heroku, which was the app password generated by Google in our last step. Change the domain to whatever your Heroku domain is for the site. After pushing everything to Heroku, everything should work. Your app should send emails in both production and development environements. Awesome!

    Wrapping things up

    So there you have it, a basic overview of the mailer and how it works. This is a very basic app, but it's just to demo the mailer in Rails. Feel free to add to it. Add devise gem for a real user authentication system. Add some styling cause the app currently is ugly LOL. Build on Orders and create an Items model where you can add items to order. Build it into a fully functional ecommerce site. The sky is the limit, go forth and code!

    If you don't know how to deploy Rails to Heroku here is a great place to start. How to use Heroku Config Vars. If you don't know how to use git/github, start here. Git docs are also a good place for information. The Rails documentation for the mailer here and pretty much everything else you could need for Rails is on Rails Guides. Hope you all learned something :)

  • WorkOS

    The modern identity platform for B2B SaaS. The APIs are flexible and easy-to-use, supporting authentication, user identity, and complex enterprise features like SSO and SCIM provisioning.

    WorkOS logo
NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts