Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Email with Unsubscribe link

I am working on a Rails 4.2 app that has recurring weekly events that people register for. They will get a reminder email before each event (so weekly). I want a one click unsubscribe link on the email. This seems like a common task but I haven't found a good current solution. Some directions I have seen are to use MessageVerifier which was new to Rails 4.1 and doesn't require saving a token string to compare to in the database. What are the steps to accomplish this. I have a user model and an event model. Emails are sent to registered users who signed up for the recurring event.

like image 280
Steve Carey Avatar asked Dec 22 '15 04:12

Steve Carey


2 Answers

Here is the solution I came up with for an unsubscribe link for regular emails sent to subscribers. It uses MessageVerifier.

1. Generate a cryptographic hash of the user.id

In my Events controller I have a send_notice action that sends the email. I'll create a variable for the unsubscribe link and pass it to the mailer.

# app/controller/events_controller.rb
def send_notice
  ...
  @unsubscribe = Rails.application.message_verifier(:unsubscribe).generate(@user.id)
  EventMailer.send_notice(@event, @user, @unsubscribe).deliver_later
end

This takes the user id and generates a signed and encoded string variable @unsubscribe. Message verifier turns the user id into an indecipherable string of characters by mixing it with your secret_key_base (found in the file config/secrets.yml), and the name that you give the message (in this case I'm calling it :unsubscribe but it could be any name) as what they call a salt, and running it through an algorithm called SHA1. Make sure your secret_key_base stays secure. Use environmental variables to store the value in production. In the last line we pass the @unsubscribe variable to the mailer along with @event and @user variables.

2. Add the @unsubscribe hash variable to the mailer

# app/mailers/event_mailer.rb
def send_notice(event, user, unsubscribe)
  @event = event
  @user = user
  @unsubscribe = unsubscribe
  mail(to: user.email, subject: "Event Info")
end 

3. Put the unsubscribe link in the email

# app/views/events_mailer/send_notice.html.erb
...
<%= link_to "Unsubscribe", settings_unsubscribe_url(id: @unsubscribe) %>.

Notice the (id: @unsubscribe) argument added. This will append the encoded user id to the end of the URL beginning with a ? in what is known as a query param.

4. Add routes

Add a route that takes the user to an unsubscribe page. Then another route for when they submit the unsubscribe.

# config/routes.rb
get 'settings/unsubscribe'
patch 'settings/update'

5. Add a subscription field to the User model

I opted to add a subscription boolean (true/false) field to the user model but you can set it up many different ways. rails g migration AddSubscriptionToUsers subscription:boolean.

6. Controller - Decode the user_id from the URL

I opted to add a settings controller with an unsubscribe and an update action. Generate the controller rails g controller Settings unsubscribe. When the user clicks on the unsubscribe link in the email it will go to the url and append the encoded User ID to the URL after the question mark as a query param. Here's an example of what the link would look like: http://localhost:3000/settings/unsubscribe?id=BAhpEg%3D%3D--be9f8b9e64e13317bb0901d8725ce746b156b152. The unsubscribe action will use MessageVerifier to unsign and decode the id param to get the actual user id and use that to find the user and assign it to the @user variable.

# app/controllers/settings_controller.rb
def unsubscribe
  user = Rails.application.message_verifier(:unsubscribe).verify(params[:id])
  @user = User.find(user)
end

def update
  @user = User.find(params[:id])
  if @user.update(user_params)
    flash[:notice] = 'Subscription Cancelled' 
    redirect_to root_url
  else
    flash[:alert] = 'There was a problem'
    render :unsubscribe
  end
end

private
  def user_params
    params.require(:user).permit(:subscription)
  end

7. Views

Add the unsubscribe page that includes a form to cancel your subscription.

# app/views/settings/unsubscribe.html.erb
<h4>Unsubscribe from Mysite Emails</h4>
<p>By unsubscribing, you will no longer receive email...</p>
<%= form_for(@user, url: settings_update_path(id: @user.id)) do |f| %>
  <%= f.hidden_field(:subscription, value: false) %>
  <%= f.submit 'Unsubscribe' %>
  <%= link_to 'Cancel', root_url %>
<% end %>

There are a number of ways you can set this up. Here I use the form_for helper routed to the settings_update_path with an added argument of id: @user.id. This will append the user id to the URL when sending the form as a query param. The Update action will read it to find the user and update the Subscription field to false.

like image 196
Steve Carey Avatar answered Oct 29 '22 06:10

Steve Carey


A really good option might be using Michael Hartl's Rails tutorial app. In chapter 12 he implements a system where users can follow other users microposts (basically tweets, twitter style). You would just need to set up the model validations (user and event models) and implement a Relationship model & controller. The relationship model and controller handle the whole following mechanism. Essentially the controller only has create and delete actions, and the create action would serve to create the relationship and the destroy to delete it. To handle the unsubscribe scenario you could simply have rails render the delete path in the ActionMailer template. The link above gives you step by step instructions, but if you get lost feel free to ask me questions.

like image 22
Jarrel09 Avatar answered Oct 29 '22 07:10

Jarrel09