Automatically Unsubscribe from Emails in Rails (and Control Email Preferences)
In this tutorial, I’ll show you how to add a link to any Rails Mailer that will allow a user to automatically unsubscribe from that email. As an added bonus, we’ll build a page allowing a user to update their email preferences across all mailers.
Inspired by GoRails.
Step 1: Build Mailers
-
Generate mailers.
-
Update previews by passing a user into the mailer. This assumes your database has at least one user record.
Step 2: Build a Model to Save Email Preferences
-
Generate the model and migration.
-
Add a null constraint to the mailer column, and a unique index on the user_id and mailer columns. This will prevent duplicate records.
What’s Going On Here?
- We add
null: false
to themailer
column to prevent empty values from being saved, since this column is required. - We add a unique index on the
user_id
andmailer
columns to prevent a user from having multiple preferences for a mailer.
- We add
-
Run the migrations.
-
Build the MailerSubscription model.
What’s Going On Here
- We add a constant to store a list of mailers a user will be able to subscribe/unsubscribe from. The class value must match the name of a Mailer class.
- We use the values stored in the constant to constrain what values can be set on the
mailer
column. This prevents us from accidentally creating a record with an invalid mailer. - We add a uniqueness validator between the
user
andmailer
. This is made possible by the unique index we created in the migration. This will ensure a user cannot have multiple preferences for the same mailer. - We use the values stored in the constant to create a variety of helper methods that can be used in views.
-
Add method to check if a user is subscribed to a specific mailer.
What’s Going On Here?
- We add a method that checks if a user is subscribed to a particular mailer. If the method finds a matching record, then the user is subscribed. Otherwise, they are not. Note that this is an opt-in strategy. We’re deliberately looking for records where
subscribed
is set totrue
. This means that if there is no record in the database, they’ll be considered unsubscribed.- To make this an opt-out strategy, you could simply replace
subscribed: true
withsubscribed: false
.
Step 3: Allow a User to Automatically Unsubscribe from a Mailer
-
Generate a controller to handle automatic unsubscribes.
-
Build the endpoints.
-
Build the view.
You can test this be getting the Global ID of a user and going to the endpoint.
http://localhost:3000/mailer_subscription_unsubcribes/abc123…?mailer=MarketingMailer
What’s Going On Here?
- We create an endpoint that will automatically unsubscribe a user from a particular mailer. This is a little unconventional since we’re creating a record on a GET request (instead of a POST request). We’re forced to do this because a user will be clicking a link from an email to unsubscribe. If emails supported forms, we could create a POST request.
- We add a button on that page that will allow the user to resubscribe to the mailer. Note that we don’t redirect back to the
show
action because that would end up unsubscribing the user from the mailer again.- We find the user through their GlobalID in the URL which makes the URLs difficult to discover. Otherwise the URL would just accept the user’s ID which is much easier to guess. This will prevent a bad actor from from unsubscribing a user from a mailer.
Step 4: Build a Page for User to Update Their Email Preferences
-
Generate a controller for the MailerSubscription model.
-
Build the endpoints.
What’s Going On Here?
- We create a page allowing a user to subscribe/unsubscribe from all possible mailers that are defined in
MailerSubscription::MAILERS
. We can’t call@user.mailer_subscriptions
because they may not have any records. - We create a
handle_unauthorized
method to prevent a user from subscribing/unsubscribing another user from mailers. We need to do this because we’re passing in the ID of theMailerSubscription
through the params hash which can be altered via the browser.
- We create a page allowing a user to subscribe/unsubscribe from all possible mailers that are defined in
-
Build the views.
What’s Going On Here?
- We loop through each
MailerSubscription
instance. If it’s a new_record? we create aMailerSubscription
. Otherwise, it’s an existing record and we toggle! thesubscribed
value.- In either case we use a button_to to hit the correct endpoint. Note that when we’re creating a new
MailerSubscription
we passmailer_subscription.attributes
as params, but we’re only permitting themailer
value in our controller.
http://localhost:3000/mailer_subscriptions
Step 5: Add Unsubscribe Link to Mailer and Prevent Delivery if User Has Unsubscribed
-
Add shared logic to
ApplicationMailer
.What’s Going On Here?
- We add several action mailer callbacks to the
ApplicationMailer
in order for this logic to be shared across all mailers. - We call
prevent_delivery_if_recipient_opted_out
which will conditionally prevent the mailer from being sent if the user is not subscribed to that mailer. This is accomplished by settingmail.perform_deliveries
totrue
orfalse
based on the return value of@user.subscribed_to_mailer? self.class.to_s
. Note that callingself.class.to_s
will return the name of the mailer (i.e. MarketingMailer). - We call
@user.to_sgid.to_s
to ensure the the URL is unique and does not contain the user’s id. Otherwise a bad actor could unsubscribe any user from a mailer. - We conditionally call these callbacks with
should_unsubscribe?
to ensures we’ve passed a user to the mailer.
- We add several action mailer callbacks to the
-
Conditionally render unsubscribe links in mailer layouts.