Our great sponsors
-
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.
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 modeAdding 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 calledorder_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 yourorder_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 modeEverything 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 modeNow with this data added, spin up the server with
rails s
in terminal. Next you can go tolocalhost:3000/rails/mailers
and you will see our Order Mailer with ourorder_email
method. Click onorder_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
andorders.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 modeNow 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 modeLets 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 theorder_email
method from ourOrderMailer
. Next we make sure that only one email is sent with the lineassert_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 fileorders_helper.rb
intest/helpers
directory. Put this inorders_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 modeHere 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 modeLet'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 inActionMailer::TestHelper
to use theassert_enqueued_email_with
method, and of course we include ourOrdersHelper
. Next is the actual test, we create an order with ourorder_attributes
which was defined in our moduleOrdersHelper
from the last step. Then we checked to see if an email was enqueued with ourOrderMailer
using theorder_email
method defined in our mailer. We then pass it the created order. Runningrails test
in terminal, all tests should pass and be green. Starting our local serverrails s
we can create an order and see that we get an email sent that opens in another tab, thanks to ourletter_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 modeChange 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 :)