How to Set Up Emails for Password Resets with Devise

The development team at Planet Argon recently helped one of our clients launch a redesigned application to their 50,000 customers. Before the existing users could get into the new app, they would need to reset their password. We use Devise for user authentication and wanted to leverage its existing functionality for our custom mailer.

This post walks through the process we used to create a custom mailer to welcome the users to the new platform and test that mailer before delivery.

At a high level, this entails:

  • Configuring the app to use a Mailtrap Inbox to test emails in a development environment
  • Configuring Devise to use a custom mailer
  • Generating the custom mailer to use with Devise
  • Updating our custom mailer to generate a Devise token for our welcome email
  • Writing a rake task to send the email
  • Testing the task using Mailtrap with a subset of our users

This tutorial assumes you already have Devise installed and configured on your application for a model named User. If you need to do this, I suggest consulting the Getting Started section in the gem’s documentation. It also assumes that you already have User model objects in your database that have email attributes.

Create a Mailtrap Inbox for your application

Log into your Mailtrap account and navigate to the Inboxes page. Write the name of your project in the “Inbox name” field in the top right corner of the window, and click “Create Inbox”.

Try Mailtrap for Free

After you’ve created your inbox, click on the link of the name of your newly created inbox. The credentials for the inbox should be displayed on that page, under the STMP Settings tab. Copy the content in the Integrations code box and paste it in your config/environments/development.rb file.

Replace your existing settings for action_mailer.delivery_method or action_mailer.stmp_settings with your Mailtrap STMP settings. It will look something like this:

# config/environments/development.rb
…
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    from: 'from@example.com'',
    user_name: 'XXXXXXXXXXXX',
    password: 'XXXXXXXXXXXX',
    address: 'smtp.mailtrap.io',
    domain: 'smtp.mailtrap.io',
    port: '2525',
    authentication: :cram_md5,
  }

Generate Devise mailer views and our custom mailer template

Devise has many view options, including pages for logging in and out, resetting passwords, and more. For our purposes, we’ll just generate the Devise default mailer templates.

$ rails generate devise:views users -v mailer

The devise:views generator can add them all to your application. By passing users, the generated views will be nested within the users view directory. The -v mailer command scopes the generated views to just those pertaining to the mailers. You should see a few new mailers in the app/views/users/mailer directory.

This creates some basic mailers for confirming your email, resetting your password, unlocking your account, and more. We’ll leave these templates as-is, and create a new mailer template to specifically welcome users to the new app and provide them a link to reset their passwords.

Let’s add that file to this directory, too:

$ touch app/views/users/mailer/welcome_reset_password_instructions.html.erb

And add some boilerplate HTML for the mailer:

<!doctype html>
<html>
  <head>
    <meta name="viewport" content="width=device-width" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Welcome to the new site</title>
  </head>
  <body>
    <div>
      <p>Hi,</p>
      <p>The new site is now live!</p>
      <p>To access the new site, <%=link_to "please first click this link to reset your password", edit_user_password_url(reset_password_token: @token), html_options = { style: "font-weight: bold" } %>.</p>
      <p>You have two weeks to reset your password. If you run into any issues, please email them to <a href="mailto:support@example.com">support@example.com</a>. Our Support Team will be happy to help you.</p>
      <p>Thank you!</p>
    </div>
  </body>
</html>


The key line in this template that provides a link to reset the user’s password is <%=link_to "please first click this link to reset your password", edit_user_password_url(reset_password_token: @token), html_options = { style: "font-weight: bold" } %>.

This link uses Devise’s built-in edit_user_password_url helper to reset the User’s password token, copied straight from their reset_password_instructions mailer template. This will do nothing right now, but later on we’ll connect it to our UserMailer to generate a token.

Configure Devise to use a Custom Mailer 

Devise configurations live in an initializer, config/initializers/devise.rb. If you’ve never set up mailers with Devise before, you may want to change a few settings:

  • config.mailer_sender: this should be the email address you’d like your devise messages to be sent from. For this tutorial, we’ll change the value to donotreply@example.com
  • config.mailer: this defaults to Devise::Mailer, which works in most cases. We’re going to change the value to our soon-to-be-created custom mailer, UserMailer.
  • config.reset_password_within: This is the length of time a reset_password_token is valid. The default is 6.hours, but we’d like to extend it to two weeks (2.weeks), so users seeing our email have extra time before the token expires. We can change it back to 6.hours after our new users have a chance to get acquainted with our new app.
# config/initializers/devise.rb

...
  # ==> Mailer Configuration
  # Configure the e-mail address which will be shown in Devise::Mailer,
  # note that it will be overwritten if you use your own mailer class
  # with default "from" parameter.
  config.mailer_sender = 'donotreply@example.com'

  # Configure the class responsible to send e-mails.
  config.mailer = 'UserMailer'

…
  # ==> Configuration for :recoverable
  #
  # Defines which key will be used when recovering the password for an account
  # config.reset_password_keys = [:email]

  # Time interval you can reset your password with a reset password key.
  # Don't put a too small interval or your users won't have the time to
  # change their passwords.
  config.reset_password_within = 2.weeks

This creates a views directory for the mailer, app/views/users/mailer, a mailer file, app/mailers/user_mailer.rb, and a preview file to view the mailer using your Rails server. The location of this preview is dependent on your app’s test suite. If you’re using RSpec, it should be in spec/mailers/previews/user_mailer.rb. If you’re using MiniTest it will be nested within the test directory.

Let’s open app/mailers/user_mailer.rb and make a few changes to get it ready to send Devise emails, based on Devise’s Custom Mailer Wiki.

# app/mailers/user_mailer.rb

class UserMailer < Devise::Mailer
  include Devise::Controllers::UrlHelpers
  default template_path: 'users/mailer' 
end

First we change the UserMailer from extending ApplicationMailer to Devise::Mailer, to leverage Devise::Mailer‘s methods. To access Devise’s Url Helpers in our custom mailer, we include Devise::Controllers::UrlHelpers. Finally, we specify the default template_path to the location of our mailer templates within the views directory, users/mailer.

Create a new action within UserMailer

Now, we create a new action within the UserMailer for our welcome_reset_password_instructions mailer. Mailer actions are similar to controller actions. They default to looking for a view that has a name matching the mailer’s method name.

# app/mailers/user_mailer.rb 

… 

  def welcome_reset_password_instructions(user)
    mail(to: user.email, subject: 'Welcome to the New Site')
  end

In its most basic form, all it needs is a call to the #mail method, specifying who to send the email to using the to: argument, and the subject line using the subject: argument.

Since this mailer will be called through a rake task, we’ve added a user argument to the mailer action. This could also be passed via params when a mailer is called in other cases.

Set up the Mailer preview

Let’s visit the MailerPreview file that was generated alongside our UserMailer

# spec/mailers/previews/user_mailer_preview.rb 

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
end

This doesn’t have much to it right now. Just with this, any action in the UserMailer could be added to the file, and an ActionMailer::Preview object would be generated for the mailer when called in the rails console.

For our welcome_reset_password_instructions email to get a preview generated, all we need to do is add a call to that mailer action:

# spec/mailers/previews/user_mailer_preview.rb 

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer

class UserMailerPreview < ActionMailer::Preview
  def welcome_reset_password_instructions
    UserMailer.welcome_reset_password_instructions(User.first)
  end
end 

We name the method the same as the UserMailer’s corresponding action. Then we call the action on the UserMailer, and fulfill the required arguments by passing User.first. This will generate the mail preview with attributes from the first User in the database.

To preview the mailer, fire up a rails server and visit http://localhost:3000/rails/mailers/user_mailer/welcome_reset_password_instructions.

You should see the mailer template we created earlier in this tutorial. Click on the link to reset your password. You’ll be taken to the sign in page for the user. Our goal is to have this link go to the reset password page. What it gives?

By default, Devise adds attributes for its Recoverable module to the model you set it up with, in our cases, User. The reset_password_token attribute stores a string token that gets matched with a user and reset_password_sent_at, a datetime attribute that gets compared with Devise’s reset_password_within setting to note when the token should be invalidated. If you don’t have these attributes on the User model, you’ll need to add them before this will work.

The built-in reset password instructions email generates the token behind the scenes. The problem is that there is no token currently associated with the user that clicked on the email. We’ll need to create one and add it to this mailer in order for it to work.

To verify this theory, go back to your rails console and look at the reset_password_token attribute for your user:

> user.reset_password_token
# => nil

It’s nil because the mailer action does not currently generate a token to use in the mailer. Let’s fix that.

Update the welcome_reset_password_instructions email to use Devise::Controllers::UrlHelpers

This post from mokacoding helped connect the dots to generate a reset password link in a custom mailer.

The first thing we’ll do is create a new protected method, create_reset_password_token to generate the reset password token for the user. It accepts one argument, user, a User object to assign the specific token.

private


def create_reset_password_token(user)
    raw, hashed = Devise.token_generator.generate(User, :reset_password_token)
    @token = raw
    user.reset_password_token = hashed
    user.reset_password_sent_at = Time.now.utc
    user.save
 end

The Devise.token_generator.generate method takes two arguments, the class or model that the token is being generated for, and the column that the token will be used in. This token is for the User model’s reset_password_token.

This method returns two values, the raw token and a hashed copy.

Save the value of the raw token as an instance variable, @token, to use in the edit_user_password_url within our welcome_reset_password_instructions email.

Assign the hashed copy to the user’s reset_password_token attribute, set the reset_password_sent_at attribute to the current time, and save the user.

Next, we’ll call create_reset_password_token in our welcome_reset_password_instructions mailer action

def welcome_reset_password_instructions(user)
    create_reset_password_token(user)
    mail(to: user.email, subject: 'Welcome to the New Site')
  end

 The entire UserMailer file should now look like this:

class UserMailer < Devise::Mailer
  helper :application # gives access to all helpers defined within `application_helper`.
  include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
  default template_path: 'users/mailer' # to make sure that your mailer uses the devise views

  def welcome_reset_password_instructions(user)
    create_reset_password_token(user)
    mail(to: user.email, subject: 'Welcome to the New Site')
  end

  private

  def create_reset_password_token(user)
    raw, hashed = Devise.token_generator.generate(User, :reset_password_token)
    @token = raw
    user.reset_password_token = hashed
    user.reset_password_sent_at = Time.now.utc
    user.save
  end
end

Let’s re-rest our Mailer preview. Fire up a Rails server and visit http://localhost:3000/rails/mailers/user_mailer/welcome_reset_password_instructions.

Click on the reset password link within your mailer. You should now be redirected to Devise’s Change Password page instead of the log in page.
 

Join our newsletterOnly the best content, delivered once a month. Unsubscribe anytime.

Create a rake task to send the new mailer

Devise has a tutorial on creating a rake task to send a mass password reset notification. Let’s tweak it a bit to use our mailer.

First, create a rake file to house our devise task:

$ touch lib/tasks/devise.rake

Next, let’s create a task to send our mass password reset notification email:

# lib/tasks/devise.rake

namespace :devise do
  desc "Send welcome reset password instructions to all users.
  This will lockout all users until they reset their password."
  task send_welcome_reset_password_instructions_to_all_users: :environment do
    User.in_batches(of: 10).each_record do |user|
        # Send instructions so user can enter a new password:
        UserMailer.welcome_reset_password_instructions(user).deliver
        p user.id
      end
   end
end

Under the namespace :devise, we provide a description for the task to explain its intended use to future developers. Then we define the task, send_welcome_reset_password_instructions_to_all_users.

The task is taking the User object and operating on batches of 10 to send the UserMailer’s welcome_reset_password_instructions action to each passed_in user and deliver the mailer immediately. Finally, the user.id that was operated on is printed out to the console so the developers can track which users have received the email.

We decided to limit the batches to 10 emails at a time to better track where sent emails may have failed. Then, each_record is called to operate on the enumerator returned by in_batches

There may be better settings for your project. See the in_batches documentation for more configuration options and suggestions.

Word of Caution: Scope the task to only call valid users

One of the issues we ran into was that initially the rake task was run with _all_ users. However, some users were created in tests and didn’t have valid email addresses. Other users didn’t have any products, so they shouldn’t receive an alert to visit the new site.

Try filtering your users through a query before sending every one of them to your rake task. You’ll avoid bounced emails and make the process of sending many, many emails a little shorter. This will be unique to every app. This could be as simple as:

User.where.not(email: nil)

Test the task using Mailtrap

Testing the task on a large batch of users, but not _every_ user may also be helpful to get a sense of timing and the way your task behaves under a larger load. If you’re using Mailtrap’s free plan, you’ll have 500 free emails to send in a month. To test it out, let’s tweak the task to return only 100 users:

# lib/tasks/devise.rb 

… 

  task send_welcome_reset_password_instructions_to_all_users: :environment do
    User.take(100).in_batches(of: 10).each_record do |user|
        # Send instructions so user can enter a new password:
        UserMailer.welcome_reset_password_instructions(user).deliver
        p user.id
      end
   end

We’ve already configured our development environment to run Mailtrap, so all we should need to do to test out the rake task is run the following:

$ bin/rails devise:send_welcome_reset_password_instructions_to_all_users 

And visit Mailtrap to see your emails. There you have it! You’ve successfully learned how to set up emails for forgotten password resets using Devise and Mailtrap. This is a common challenge in all types of applications, and is a useful skill for any Rails developer to learn.

Add comment

E-mail is already registered on the site. Please use the Login form or enter another.

You entered an incorrect username or password

Sorry, you must be logged in to post a comment.