带有取消订阅链接的 Rails 电子邮件

Posted

技术标签:

【中文标题】带有取消订阅链接的 Rails 电子邮件【英文标题】:Rails Email with Unsubscribe link 【发布时间】:2016-03-28 06:51:20 【问题描述】:

我正在开发一个 Rails 4.2 应用程序,该应用程序具有人们注册的每周重复活动。他们将在每次活动之前(每周一次)收到一封提醒电子邮件。我想要电子邮件上的一键退订链接。这似乎是一个常见的任务,但我还没有找到一个好的当前解决方案。我看到的一些方向是使用 Rails 4.1 新增的 MessageVerifier,并且不需要保存令牌字符串以在数据库中进行比较。完成此操作的步骤是什么。我有一个用户模型和一个事件模型。电子邮件将发送给注册定期事件的注册用户。

【问题讨论】:

【参考方案1】:

这是我为发送给订阅者的常规电子邮件提供的取消订阅链接的解决方案。它使用 MessageVerifier。

1。生成 user.id 的加密哈希

在我的事件控制器中,我有一个发送电子邮件的 send_notice 操作。我将为取消订阅链接创建一个变量并将其传递给邮件程序。

# 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

这将获取用户 ID 并生成一个签名和编码的字符串变量@unsubscribe。消息验证程序通过将用户 ID 与您的 secret_key_base(在文件 config/secrets.yml 中找到)和您提供消息的名称(在这种情况下我称之为:unsubscribe 但它可以是任何名称)作为他们所说的盐,并通过称为 SHA1 的算法运行它。确保您的 secret_key_base 保持安全。使用环境变量将值存储在生产中。 在最后一行中,我们将@unsubscribe 变量连同@event 和@user 变量一起传递给邮件程序。

2。将@unsubscribe 哈希变量添加到邮件程序

# 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。将退订链接放在电子邮件中

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

注意添加的 (id: @unsubscribe) 参数。这会将编码的用户 ID 附加到以 ? 开头的 URL 的末尾。在所谓的查询参数中。

4。添加路线

添加将用户带到取消订阅页面的路由。然后是他们提交退订时的另一条路线。

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

5。向 User 模型添加订阅字段

我选择向用户模型添加订阅布尔 (true/false) 字段,但您可以通过多种不同方式进行设置。 rails g migration AddSubscriptionToUsers subscription:boolean.

6。控制器 - 从 URL 解码 user_id

我选择添加一个带有取消订阅和更新操作的设置控制器。生成控制器rails g controller Settings unsubscribe。 当用户单击电子邮件中的取消订阅链接时,它将转到 URL 并将编码的用户 ID 附加到 URL 后的问号作为查询参数。以下是链接的示例:http://localhost:3000/settings/unsubscribe?id=BAhpEg%3D%3D--be9f8b9e64e13317bb0901d8725ce746b156b152。 取消订阅操作将使用 MessageVerifier 取消签名和解码 id 参数以获取实际的用户 id 并使用它来查找用户并将其分配给 @user 变量。

# 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。观看次数

添加包含取消订阅表单的取消订阅页面。

# 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 %>

您可以通过多种方式进行设置。在这里,我使用了路由到 settings_update_path 的 form_for 助手,并添加了 id 参数:@user.id。在将表单作为查询参数发送时,这会将用户 ID 附加到 URL。 Update 操作将读取它以查找用户并将 Subscription 字段更新为 false。

【讨论】:

这个答案是正确的!谢谢史蒂夫·凯里。你的贡献将作为我最喜欢的之一载入史册^__^ @SteveCarey 这个解决方案抛出NoMethodError - undefined method 'id' for nil:NilClass 有什么遗漏吗?我该如何解决这个问题? @SteveCarey 有什么特别的原因为什么您添加了一个新的设置控制器而不是将这些方法添加到您的 users_controller 中?顺便说一句,谢谢你的解释,真的很好! @Ben Smith - 我创建了一个单独的控制器只是为了让事情更加隔离。我没有尝试过,但我相信您可以将这些操作添加到 users_controller。 @Sunny,是的,我认为你是对的。要解决此问题,您可以跳过更新表单步骤并在取消订阅操作中取消订阅它们。或者不要在取消订阅操作中解码 id 验证器,只需将编码的 id 从取消订阅传递到更新操作并在那里解码。【参考方案2】:

一个非常好的选择可能是使用 Michael Hartl 的 Rails 教程应用程序。在chapter 12 中,他实现了一个系统,用户可以在该系统中关注其他用户的微博(基本上是推特,推特风格)。您只需要设置模型验证(用户和事件模型)并实现关系模型和控制器。关系模型和控制器处理整个以下机制。本质上,控制器只有创建和删除操作,创建操作将用于创建关系,而销毁用于删除它。要处理取消订阅的情况,您可以简单地让 rails 在 ActionMailer 模板中呈现删除路径。上面的链接为您提供了分步说明,但如果您迷路了,请随时向我提问。

【讨论】:

其实我比你领先一步。事实上,我确实做了类似的事情。因此,取消订阅被设置为事件控制器中的销毁操作。它由事件页面上的按钮激活。因此,在电子邮件中,我有一个返回事件页面的链接,用户可以点击该链接,登录,然后按事件页面上的取消订阅按钮。不过,我想补充的是,只需按下链接即可完成此操作,而无需登录并按下按钮。 我不认为在事件控制器中具有销毁操作是您想要的......看起来这会破坏实际事件,而不是用户与该事件“遵循关系”。此外,关于电子邮件中的链接……您需要包含某种类型的令牌系统来验证用户身份,然后将用户的电子邮件和哈希令牌作为查询字符串参数插入 url。让我举几个例子: 是的,我实际上比我提到的要复杂一些。结构是你有一个重复的事件,你有用户。我创建了一个名为参与的连接表,它与用户和事件具有多对多关系。用户单击重复事件的显示页面上的按钮以成为重复事件的参与者。这会在 Participations 表上创建一行,其中包含 user_id 和 event_id。取消订阅按钮会破坏参与行。 好的,那么本质上模型和控制器操作是否正常运行?然后听起来你只需要令牌函数来创建令牌,然后修改控制器上的删除操作以验证用户电子邮件和令牌,然后通过查询字符串参数简单地将用户电子邮件和令牌插入电子邮件.正确的?如果是这样,我也知道如何做到这一点 是的,模型和控制器操作都按预期工作。我确实需要做一些你建议的事情。我发现了一个叫做 Mailkick 的 gem 可以做到这一点,我正在试验它。它确实使用了 Rails 4.1 MessageVerifier,并不太复杂。如果我可以自己直接编码,我通常会避免使用 gem。【参考方案3】:

我是这样做的:

class User < ApplicationRecord
  has_many :unsubscribes

  scope :unsubscribed, -> (list)  joins(:unsubscribes).where(unsubscribes:  list: list ) 
  scope :subscribed, -> (list)  where.not(id: User.unsubscribed(list)) 

  def subscribed?(list)
    unsubscribes.where(list: list).none?
  end

  def unsubscribed?(list)
    !subscribed?(list)
  end
end

class Unsubscribe < ApplicationRecord
  LISTS = %w[
    message_mailer-messages_email
    user_mailer-reset_password_email
    event_mailer-summary_email
  ].freeze

  belongs_to :user

  validates :list,
            presence: true,
            uniqueness: 
              scope: :user_id
            ,
            inclusion: 
              in: LISTS
            
end

class CreateUnsubscribes < ActiveRecord::Migration[6.1]
  def change
    create_table :unsubscribes do |t|
      t.string :list, null: false
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end

    add_index :unsubscribes, %i[list user_id], unique: true
  end
end

class UnsubscribesController < ApplicationController
  before_action :load_user

  helper_method :unsubscribe_url
  helper_method :unsubscribes_url

  def show
    authorize :unsubscribe
    @list = params[:id]
    @unsubscribe = Unsubscribe.find_by(user: @user, list: params[:id])
  end

  def create
    @unsubscribe = Unsubscribe.new(unsubscribe_params)
    @unsubscribe.user = @user
    authorize @unsubscribe

    if @unsubscribe.save
      redirect_to unsubscribe_url
    else
      redirect_to unsubscribe_url,
                  alert: @unsubscribe.errors.full_messages.to_sentence
    end
  end

  def destroy
    authorize :unsubscribe
    @unsubscribe = Unsubscribe.find_by!(user: @user, list: params[:id])
    @unsubscribe.destroy!
    redirect_to unsubscribe_url
  end

  private

  def load_user
    @user = User.find_signed!(params[:user_id])
  end

  def unsubscribe_params
    params.require(:unsubscribe).permit(:list)
  end

  def unsubscribe_url
    if @unsubscribe
      user_unsubscribe_url(user_id: @user.signed_id, id: @unsubscribe.list)
    else
      user_unsubscribe_url(user_id: @user.signed_id, id: @list)
    end
  end

  def unsubscribes_url
    user_unsubscribes_url(user_id: @user.signed_id)
  end
end

# app/views/unsubscribes/show.html.slim
- title t(".lists.#@list")

- if @unsubscribe
  p.text-center.text-green-600= t(".you_are_unsubscribed")

  .text-center.mb-4
    = button_to t(".subscribe"), unsubscribe_url, method: :delete
- else
  p.text-center.text-green-600= t(".you_are_subscribed")

  = form_for Unsubscribe.new, url: unsubscribes_url do |f|
    = f.hidden_field :list, value: @list
    p.text-center
      = f.submit t(".unsubscribe")

# config/routes.rb

    resources :users do
      resources :unsubscribes
    end

【讨论】:

以上是关于带有取消订阅链接的 Rails 电子邮件的主要内容,如果未能解决你的问题,请参考以下文章

通过 sendgrid/mail 发送时,如何向我的电子邮件添加取消订阅链接

Django,使用 Mailchimp 列表发送取消订阅的电子邮件

Rails - 电子邮件确认链接给出“未知操作”错误

在 ruby​​ on rails 3 中通过电子邮件创建订阅

取消订阅后将用户重新订阅到 MailChimp 列表

使用 Ruby(在 Rails 上)确认链接创建