Implement one-click-unsubscribe (#192)

* Implement one-click-unsubscribe feature WIP

* Adjust email subscription page's css

* Add mobile scss

* Apply subscription logic to other email type

* Add back default email footer

* Create EmailSubscriptions request spec
This commit is contained in:
Mac Siri 2018-04-09 17:05:45 -04:00 committed by Ben Halpern
parent d67c2437bc
commit 0ea3f38dbf
12 changed files with 112 additions and 9 deletions

View file

@ -0,0 +1,20 @@
.unsubscribed {
background: #66e2d5;
color: white;
margin: 100px auto;
padding: 40px 10px;
@media screen and (min-width: 550px) {
margin: 100px auto;
width: 450px;
padding: 40px;
}
}
.unsubscribed__title {
font-weight: 600;
}
.unsubscribed__message {
font-weight: 300;
}

View file

@ -29,3 +29,5 @@
@import 'chat';
@import 'livechat/chat';
@import 'email_subscriptions';

View file

@ -0,0 +1,14 @@
class EmailSubscriptionsController < ApplicationController
def unsubscribe
verified_params = Rails.application.message_verifier(:unsubscribe).verify(params[:ut])
if verified_params[:expires_at] > Time.now
user = User.find(verified_params[:user_id])
user.update(verified_params[:email_type] => false)
else
not_found
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
not_found
end
end

View file

@ -17,9 +17,11 @@ class EmailDigest
user_email_heuristic = EmailLogic.new(user).analyze
next unless user_email_heuristic.should_receive_email?
articles = user_email_heuristic.articles_to_send
DigestMailer.digest_email(user, articles).deliver
rescue
puts "Email issue"
begin
DigestMailer.digest_email(user, articles).deliver
rescue StandardError
puts "Email issue"
end
end
end

View file

@ -1,4 +1,12 @@
class ApplicationMailer < ActionMailer::Base
default from: "The DEV Community <yo@dev.to>"
layout 'mailer'
layout "mailer"
def generate_unsubscribe_token(id, email_type)
Rails.application.message_verifier(:unsubscribe).generate(
user_id: id,
email_type: email_type.to_sym,
expires_at: Time.now + 2.days,
)
end
end

View file

@ -6,6 +6,7 @@ class DigestMailer < ApplicationMailer
user
end
@articles = articles.first(6)
@unsubscribe = generate_unsubscribe_token(@user.id, :email_digest_periodic)
@digest_email = true
mail(from: "DEV Digest <yo@dev.to>", to: @user.email, subject: "#{adjusted_title(@articles.first)} + #{@articles.size - 1} #{email_end_phrase} #{random_emoji}") do |format|
format.html { render "layouts/mailer" }
@ -14,12 +15,11 @@ class DigestMailer < ApplicationMailer
def adjusted_title(article)
title = article.title.strip
title = "\"#{title}\"" unless title.start_with? '"'
"\"#{title}\"" unless title.start_with? '"'
end
def random_emoji
["🤓","🎉","🙈","🔥","💬","👋","👏","🐶","🦁","🐙","🦄","❤️","😇"].shuffle.take(3).join
["🤓", "🎉", "🙈", "🔥", "💬", "👋", "👏", "🐶", "🦁", "🐙", "🦄", "❤️", "😇"].shuffle.take(3).join
end
def email_end_phrase

View file

@ -7,6 +7,7 @@ class NotifyMailer < ApplicationMailer
comment.parent_user
end
return if RateLimitChecker.new.limit_by_email_recipient_address(@user.email)
@unsubscribe = generate_unsubscribe_token(@user.id, :email_comment_notifications)
@comment = comment
mail(to: @user.email, subject: "#{@comment.user.name} replied to your #{@comment.parent_type}") do |format|
format.html { render "layouts/mailer" }
@ -22,6 +23,7 @@ class NotifyMailer < ApplicationMailer
end
return if RateLimitChecker.new.limit_by_email_recipient_address(@user.email)
@follower = follow.follower
@unsubscribe = generate_unsubscribe_token(@user.id, :email_follower_notifications)
mail(to: @user.email, subject: "#{@follower.name} just followed you on dev.to") do |format|
format.html { render 'layouts/mailer' }
@ -35,6 +37,7 @@ class NotifyMailer < ApplicationMailer
@mentioner = User.find(mention.mentionable.user_id)
@mentionable = mention.mentionable
@mention = mention
@unsubscribe = generate_unsubscribe_token(@user.id, :email_mention_notifications)
mail(to: @user.email, subject: "#{@mentioner.name} just mentioned you!") do |format|
format.html { render 'layouts/mailer' }
@ -50,6 +53,7 @@ class NotifyMailer < ApplicationMailer
end
return if RateLimitChecker.new.limit_by_email_recipient_address(@user.email)
@unread_notifications_count = NotificationCounter.new(@user).unread_notification_count
@unsubscribe = generate_unsubscribe_token(@user.id, :email_unread_notifications)
mail(to: @user.email, subject: "🔥 You have #{@unread_notifications_count} unread notifications on dev.to") do |format|
format.html { render 'layouts/mailer' }
format.text { render plain: "Visit https://dev.to/notifications to read all of your notifications" }

View file

@ -0,0 +1,10 @@
<div class="unsubscribed">
<div class="unsubscribed__title">
You have been successfully unsubscribed. 😔
</div>
<br/>
<div class="unsubscribed__message">
If that was a mistake or you would like to update other email preferences,
<a href="https://dev.to/settings/notifications">click here</a>.
</div>
</div>

View file

@ -219,5 +219,4 @@
<% end %>
</em>
</center>
<% end %>

View file

@ -8,7 +8,11 @@
</tr>
<tr>
<td style="padding:3% 12% 2% 4%;font-size:16px;line-height:22px;color:#7d7d7d">
Don't want to get emails like this? Adjust your email settings at <a href="https://dev.to/settings/notifications">dev.to/settings/notifications</a>
<%if @unsubscribe %>
<%= link_to "Unsubscribe", email_subscriptions_unsubscribe_url(ut: @unsubscribe) %> | <a href="https://dev.to/settings/notifications">Adjust your email settings</a>
<% else %>
Don't want to get emails like this? Adjust your email settings at <a href="https://dev.to/settings/notifications">dev.to/settings/notifications</a>
<% end %>
<br/><br/>
<% if @user.twitter_username.blank? %>
Reminder: You used <b>GitHub</b> to authenticate your account, so use that to sign in if prompted.

View file

@ -91,6 +91,7 @@ Rails.application.routes.draw do
resources :additional_content_boxes, only: [:index]
get "/notifications/:username" => "notifications#index"
patch "/onboarding_update" => "users#onboarding_update"
get "email_subscriptions/unsubscribe"
# resources :users
### Subscription vanity url

View file

@ -0,0 +1,39 @@
require "rails_helper"
RSpec.describe "EmailSubscriptions", type: :request do
let(:user) { create(:user) }
def generate_token(user_id)
Rails.application.message_verifier(:unsubscribe).generate(
user_id: user_id,
email_type: :email_mention_notifications,
expires_at: Time.now + 2.days,
)
end
describe "GET /email_subscriptions/unsubscribe" do
it "returns 200 if valid" do
get email_subscriptions_unsubscribe_url(ut: generate_token(user.id))
expect(response.status).to be(200)
end
it "does unsubscribe the user" do
get email_subscriptions_unsubscribe_url(ut: generate_token(user.id))
user.reload
expect(user.email_mention_notifications).to be(false)
end
it "handles error properly" do
expect { get email_subscriptions_unsubscribe_url }.
to raise_error(ActionController::RoutingError)
end
it "won't work if it's past expireation date" do
token = generate_token(user.id)
Timecop.freeze(Date.today + 3) do
expect { get email_subscriptions_unsubscribe_url(ut: token) }.
to raise_error(ActionController::RoutingError)
end
end
end
end