Skip to content

Customizing for different Invite use cases (emails etc.)

Adrien Di Pasquale edited this page Jan 19, 2021 · 23 revisions

Sometimes you want to be able to send different invites for a variety of different reasons. Eg. Inviting a VIP user vs Inviting a regular user. Here is an example of doing it for a "friend" and for a "guest"

In your devise model, put:

attr_accessor :invitation_instructions

def self.invite_guest!(attributes={}, invited_by=nil)
 self.invite!(attributes, invited_by) do |invitable|
   invitable.invitation_instructions = :guest_invitation_instructions
 end
end

def self.invite_friend!(attributes={}, invited_by=nil)
 self.invite!(attributes, invited_by) do |invitable|
   invitable.invitation_instructions = :friend_invitation_instructions
 end
end

From here if you run User.invite_guest!(:email => 'email@domain.com') you will get the exact same email.

The way to fix this is to create your own mailer class that inherits from Devise::Mailer, and modify invitation_instructions method yourself. EG.

class InviteMailer < Devise::Mailer

  def invitation_instructions(record, token, opts={})
    @token = token
    devise_mail(record, record.invitation_instructions || :invitation_instructions, opts)
  end

end

To make InviteMailer your default class to send emails (wont override your other emails, which will still be sent by devise mailer) add this line to config/initializers/devise.rb:

config.mailer = "InviteMailer"

Lastly, create your views in views/invite_mailer/guest_invitation_instructions and you should be all set!

NOTE: When you create custom mailer (for example "InviteMailer") old devise mailer views should be moved to new mailer directory (just copy files from /app/views/devise/mailer/ to /app/views/invite_mailer/).

Customizing email headers

Sometimes you might want to add a dynamic email header instead of pulling something statically from config/locals. For example, a personalized custom subject header. Here's an easy way to achieve this in devise versions below 2.2.0.

⚠️ for Devise >= 2.2.0 this will not work, you will have to override the mailer class and customize it.

class User < ActiveRecord::Base
  #... regular implementation ...
  
  # This method is called internally during the Devise invitation process. We are
  # using it to allow for a custom email subject. These options get merged into the
  # internal devise_invitable options. Tread Carefully.
  #
 def headers_for(action)
    return {} unless invited_by && action == :invitation_instructions
    { subject: "#{invited_by.full_name} has given you access to their account" }
  end
end

Allowing users to create a custom invite message

Sometimes you may want to allow users to customize the message being sent, e.g. add a personal greeting to the standard message.

To accomplish this you can use your own mailer instead of having devise email things for you.

    class User < ActiveRecord::Base
      attr_reader :raw_invitation_token
    end

    class InvitationsController < Devise::InvitationsController
      def create
        @from    = params[:from]
        @subject = params[:invite_subject]
        @content = params[:invite_content]
        
        @user = User.invite!(params[:user], current_user) do |u|
          u.skip_invitation = true
        end

        NotificationMailer.invite_message(@user, @from, @subject, @content).deliver if @user.errors.empty?

        if @user.errors.empty?
          @user.update_column :invitation_sent_at, Time.now.utc # mark invitation as delivered
          flash[:notice] = "successfully sent invite to #{@user.email}"
          respond_with @user, :location => root_path
        else
          render :new
        end
      end
    end

    class NotificationMailer < ActionMailer::Base
      def invite_message(user, from, subject, content)
        @user = user
        @token = user.raw_invitation_token
        invitation_link = accept_user_invitation_url(:invitation_token => @token)

        mail(:from => from, :bcc => from, :to => @user.email, :subject => subject) do |format|
          content = content.gsub '{{first_name}}', user.first_name
          content = content.gsub '{{last_name}}', user.first_name
          content = content.gsub '{{full_name}}', user.full_name
          content = content.gsub('{{invitation_link}}', invitation_link)
          format.text do
            render :text => content
          end
        end
      end
    end

The raw_invitation_token only exists in newer versions of devise_invitable (compatible with devise >= 3.1).

Note that you must send the email with the user object instance that generated the token, as user.raw_invitation_token is available only to the instance and is not persisted in the database.

We are essentially skipping the devise invitation email process all together and using our own mailer. We take the content as a parameter and send it as the body of the email. We even substitute placeholders with their actual values.

This solution will give you a lot of flexibility to do whatever you want with emails. You can even add a tracking pixel to it if you would like.