Email Confirmation in Rails

Project Mode

It's project time for the students here at the Flatiron School, which means absolute madness has ensued. This is the time where you get to solidify some of the concepts you have been exposed to during the course of the semester, and also explore new concepts, because you want your application to be REALLY cool, and your ambition almost always exceeds your skill set.

One of the ideas I wanted to explore is concept of using email confirmation. Whether that be validating the users email does in fact belong to them, or sending a request to an admin to unlock a feature of the application. Before you say it, yes, there are gems that can do this sort of thing for you. I chose not to use a gem because this is the first time I have had to implement this type of feature in my coding journey. And also, my wonderful instructor, Sophie, politely advised me not to.

So here we go.

ActionMailer

Because I want to make this post as comprehensive as possible, I am going to briefly walk through getting Rails ActionMailer set up so we can send emails to our users.

Part I: Setup Action Mailer

The first thing you want to do is make sure you generate a mailer in your Rails project. You can do this using the command line by doing the following:

rails generate mailer UserMailer

If done correctly, the command line will look something like this:

$ bin/rails generate mailer UserMailer
create  app/mailers/user_mailer.rb
create  app/mailers/application_mailer.rb
invoke  erb
create  app/views/user_mailer
create  app/views/layouts/mailer.text.erb
create  app/views/layouts/mailer.html.erb
invoke  test_unit
create  test/mailers/user_mailer_test.rb
create test/mailers/previews/user_mailer_preview.rb

If you navigate to the ApplicationMailer, you will see:

class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
   layout 'mailer'
end

As you can see, you can generate mailers just like you use other generators with Rails. Mailers are conceptually similar to controllers, and so we get a mailer, a directory for views, and a test.

Because Mailers are similar to controllers, they have methods called "actions", which are used to generate HTML or text to send to a client.

Step 2: Email Action

Next we want to create a method that will be responsible for sending the email.

In our UserMailer controller we will create an action called registration_confirmation, which will look like this:

class UserMailer < ActionMailer::Base
 default :from => "application_name@domain.com"

def registration_confirmation(user)
    @user = user
    mail(:to => "#{user.name} <#{user.email}, :subject => "Registration Confirmation for Awesome App)
 end
end

There are a number of various options that you can pass into this controller and action, but this is all we need for the what we are trying to accomplish. Just like any other controller, any instance method we set in this method will become available in the view that we will be sending out.

sidebar
I would highly recommend setting up an email specific to your application to send out emails. I used figaro to store my email and password. I used gmail for my applications email and ran into an issue where I could not send out emails and had no idea why. After a ton of frustration, I realized that I just needed to change a preference in my email to allow less secure apps use my email. I'm sure there are other ways to do this, but I hope this can save someone time, but I digress.

Step 3: Email View

Next we need to set up the view, which in the case for Mailers, is the template that will be generated by referencing the corresponding action. In app/views/user_mailer/, create a file called registration_confirmation.html.erb. My view looks like this:

 Hi <%= @user.name %>,

Thanks for registering! To confirm your registration click the URL below.

<%=  link_to "confirm", confirm_email_url(@user.confirm_token, host: "http://lvh.me:3000")%>

Don't worry too much about what @user.confirm_token means right now because we will get to that later. However, some things to note about the link that we are generating here:

  • If you are going to use Rails helper methods for links, always use *url extension instead of *path. If you do not use *url helper, the links will not work in your email.
  • You can change the host, which is the default host for the url that you generate, in config/application.rb, but for the sake of transparency we are setting it directly in the generation of the link.

Part II: Setup User Model & Logic

Now that our ActionMailer is setup, lets start setting up our User model.

Step 1: Email Confirmed & Confirm Token

Assuming you have a User model set up, let's create a migration to add two new attributes to our User table.

rails generate migration   AddEmailConfirmedAndConfirmTokenToUser email_confirmed:boolean confirm_token:string 

In the migration file generated by this line of code, make sure to set the email_confirmed boolean to false.

add_column :users, :email_confirmed, :boolean,   :default => false
Step 2: User Helper Methods

We need a helper method that will let us update the users email_confirmed boolean to true once the token has been validated. There are, like anything, a number of ways to do this. Make sure you are in app/models/user.rb and use this code:

private
  def validate_email
    self.email_confirmed = true
    self.confirm_token = nil
  end

We will call this method once we have successfully verified the confirmation token.

The confirmation token will be the randomly generated string that we will store in the database for the user signing up and will also be stored in the url sent to the users email.

Lets navigate to our users model by going to app/models/user.rb. Here we are going to create a private method that sets the users confirmation token.

private
  def set_confirmation_token
     if self.confirm_token.blank?
         self.confirm_token = SecureRandom.urlsafe_base64.to_s
   end
  end

We will call this in our create action for User before we send the email out. Depending on how you use this functionality will determine when and where you call it. My create action looks like this:

def create
   @user = User.new(user_params)
   if @user.save 
      @user.set_confirmation_token
      @user.save(validate: false)            UserMailer.registration_confirmation(@user).deliver_now 
     flash[:success] = "Please confirm your email address to continue"
  else 
     flash[:error] = "Invalid, please try again"
     render :new
  end
end

You could do a lot with where you take the user after this happens depending on what you plan on implementing this functionality for, so I won't go into the logic that prevents a user from seeing what in your application, but we will move on to what happens when the user clicks the registration url in their email.

Step 3: Update Routes

We will need to update our routes to be able to take the user and the token to the right place after the link has been clicked. Remember that the helper method that we used in our registration_confirmation.html.erb view was called confirm_email_url. In order for this to be valid we need to update our routes. A few things to keep in mind here:

  • We need to have a route that will let us dynamically set and get the token from the url.
  • Naming matters, so the route must correspond with the action and url helper that is being called.

The route in config/routes.rb should look something like this

  get '/:token/confirm_email/', :to => "users#confirm_email", as: 'confirm_email'

This will give us what we need to direct the url to the right controller action and allow us to dynamically grab the token.

Step 4: Confirm Email Action

Next we need to set up our confirm_email action in a way that will allow us to check the confirmation_token and set the email_confirmed boolean to true.

def confirm_email
  user = User.find_by_confirm_token(params[:token])
   if user
     user.validate_email
     user.save(validate: false)
     redirect_to user
   else
     flash[:error] = "Sorry. User does not exist"
     redirect_to root_url
 end
end

So, here we are checking if a user exists with the token found in the parameters, which is a unique token set by our set_confirmation_token method preceding the email being sent to the user. If the user is found we run our validate_email method to set the boolean value stored for the user to true. Boom, much confirmation.

validate: false

If you have been thoroughly reading this blog post, you may have noticed user.save(validate: false) called twice. Depending on how you set up your application, you may have some validations for updating attributes for users. After spending about an hour with one my instructors, Josh, trying to figure out why the tokens were not being persisted to the database, we came up with this solution. Which means, for this action, we are not requiring the user to input their password, because most of the logic is happening behind the scenes. I am confident that there are other ways to do this and would love to hear how some of you solved this issue.

Conclusion

Using Rails ActionMailer is a very nice and simple way to send emails, which makes setting up an email client rather simple. By generating a random token, adding a boolean value to the user table, and implementing some logic, we were able to confirm a users email address upon signup. I know there are other ways to do this but this is how I chose to do it, and I hope someone can find some value in this post.

Also, special thanks to Sophie and Josh for helping me debug my issues with this feature!

Sources

Rails Guides on ActionMailer
Stackoverflow Post on Email Confirmation
Rails Doc on SecureRandom Module

Show Comments
comments powered by Disqus