设计不通过 ActionMailer 发送电子邮件

Devise not sending emails through ActionMailer

我已经在线阅读了如此多的教程和答案,但仍未找到解决办法。我已将 ActionMailer 设置为通过 Mailgun 的服务发送电子邮件。我可以验证在生产中它确实通过我的在线联系表发送电子邮件没有任何问题。但是,当我尝试使用 Devise 的恢复模板之一(即:忘记密码)时,没有发送电子邮件。

代码

--> config/environments/production.rb

# ActionMailer Config
config.action_mailer.perform_caching = false

config.action_mailer.raise_delivery_errors = true

config.action_mailer.default_url_options = { :host => 'thechristianchain.org' }

config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true

config.action_mailer.default :charset => "utf-8"

config.action_mailer.smtp_settings = {
  :authentication => :plain,
  :address => "smtp.mailgun.org",
  :port => '587',
  :domain => "mx.tccf.co",
  :user_name => ENV["MAILGUN_USERNAME"],
  :password => ENV["MAILGUN_PASSWORD"]
}

为了尝试调试,我将 raise_deliever_errors 设置为 true,如上面的代码所示。

当我查看日志时,我看到了这个(我删除了日志时间戳以便于阅读):

I, [2018-02-23T20:17:28.511441 #4]  INFO -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Sent mail to info@thechristianchain.org (1028.0ms)

D, [2018-02-23T20:17:28.511562 #4] DEBUG -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Date: Fri, 23 Feb 2018 20:17:27 +0000

From: registrations@thechristianchain.org
Reply-To: registrations@thechristianchain.org
To: info@thechristianchain.org
Message-ID: <5a9076d776379_4233fb2465758@2694484e-c3cb-44d0-8ece-832cd70adf2c.mail>
Subject: Reset password instructions
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<p>Hello info@thechristianchain.org!</p>

<p>Someone has requested a link to change your password. You can do this through the link below.</p>

<p><a href="http://thechristianchain.org/password/edit?reset_password_token=1gtx9XHmTzUjf97YhJdG">Change my password</a></p>

<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

I, [2018-02-23T20:17:28.511950 #4] INFO -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Completed 401 Unauthorized in 1044ms (ActiveRecord: 5.7ms)
F, [2018-02-23T20:17:28.513684 #4] FATAL -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba]   
F, [2018-02-23T20:17:28.513764 #4] FATAL -- : [fb02bfdf-a5c3-4918-bae6-5c5d32ef6fba] Net::SMTPAuthenticationError (535 5.7.0 Mailgun is not loving your login or password
):

关键在最后一行,因为 401 UnauthorizedNet::SMTPAuthenticationError 并且基本上说明 Mailgun 不喜欢我的登录凭据。

问题

这是我的问题。 Devise 在哪里提取信息?当我通过联系表发送邮件时,Mailgun 会接受我提交的登录凭据;但是当 Devise 发送时,它们不被接受。这让我觉得某处有一个设置没有从 production.rb.

中提取信息

在处理这个问题 4 天后,我找到了解决方案。不确定这是否是最好或最 eloquent 的方法,但以防万一将来其他人遇到同样的问题,这就是我所做的。

首先,一些解释。我不确定为什么用我的 config.action_mailer.smtp_settings 更新 development.rbproduction.rb 文件不起作用。从所有文档来看,Devise 似乎应该可以与这些设置无缝配合。现在,我的联系表起作用而 Devise 不起作用的原因是因为我的联系表使用了 contact_us_email_mailer.rb,它具有通过 Mailgun 的 API 发送电子邮件的代码。 (谢谢@kuwantum。您的评论帮助我建立了联系,即我发送电子邮件的方式不同。)

所以,我的想法是弄清楚 Devise 是如何发送电子邮件的,它在哪里调用 SMTP 或 API 来发送电子邮件,然后在那里输入我的信息。我去了 Devise Wiki,找到了 setting up a custom mailer file 的 "How To"。按照这些步骤,我在 app/mailers/ 下设置 membership_mailer.rb 文件,如下所示:

class MembershipMailer < Devise::Mailer   

  helper :application
  include Devise::Controllers::UrlHelpers

end

然后通过更改以下行更改我的 config/initializers/devise.rb 文件以指向这个新邮件程序:

# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
  config.mailer = 'MembershipMailer'

然后我需要获取原始的 Devise 邮件程序代码并将其输入到本文档中。我明白了 code here.

devise/mailer.rb

def confirmation_instructions(record, token, opts={})
  @token = token
  devise_mail(record, :confirmation_instructions, opts)
end

def reset_password_instructions(record, token, opts={})
  @token = token
  devise_mail(record, :reset_password_instructions, opts)
end

def unlock_instructions(record, token, opts={})
  @token = token
  devise_mail(record, :unlock_instructions, opts)
end

def email_changed(record, opts={})
  devise_mail(record, :email_changed, opts)
end

def password_change(record, opts={})
  devise_mail(record, :password_change, opts)
end

查看这段代码,我发现 devise_mail 经常被调用,所以我需要找出那段代码是什么。我达到了我需要的 this page. Searching this site gave me the code

def devise_mail(record, action, opts = {}, &block)
  initialize_from_record(record)
  mail headers_for(action, opts), &block
end

此代码显示了我在上述网站上搜索的更多函数调用,我需要了解的代码是 headers_for 部分。这是代码:

def headers_for(action, opts)
  headers = {
    subject: subject_for(action),
    to: resource.email,
    from: mailer_sender(devise_mapping),
    reply_to: mailer_reply_to(devise_mapping),
    template_path: template_paths,
    template_name: action
  }.merge(opts)

  @email = headers[:to]
  headers
end

运行 byebug,我能够调用此函数并获得响应的哈希值,调用每个响应都为我提供了它的值。所以,输入 headers_for(action, opts)[:from] 给了我在 devise.rb 文件中设置的默认电子邮件。所以,那行得通。

现在,对于经过反复试验的部分。我在使用 Mailgun 的 SMTP 设置时遇到了困难,但我知道 API 可以正常工作,因为它正在处理我的联系表。所以,我需要让变量起作用。这是适用于我的联系表单的代码(请注意,隐藏密钥是从使用“dotenv-rails' gem and the require statement utilized the 'rest-client”gem 创建的 .env 文件中调用的):

contact_us_email_mailer.rb

require 'rest-client'

def send_email(from, to, subject, text)
  RestClient.post ENV.fetch("MAILGUN_MX_URL"),
    :from => from,
    :to => to,
    :subject => subject,
    :text => text
end

现在,将它与 devise_mail 函数结合起来,我得到了这个代码:

def mail(record, action, opts = {}, block)
  initialize_from_record(record)

  RestClient.post ENV.fetch("MAILGUN_MX_URL"),
    :from => headers_for(action, opts)[:from],
    :to => headers_for(action, opts)[:to],  
    :subject => headers_for(action, opts)[:subject],
end

然后我需要弄清楚如何将电子邮件模板创建到电子邮件中。那是当我遇到这个 Whosebug answer 时,它为我指明了正确的方向,我想出了这个代码:

html_output = render_to_string(:action => action, :layout => false)
:html => html_output.to_str

将其添加到之前的代码中,我得到了这个:

def mail(record, action, opts = {}, block)
  initialize_from_record(record)
  html_output = render_to_string(:action => action, :layout => false)

  RestClient.post ENV.fetch("MAILGUN_MX_URL"),
    :from => headers_for(action, opts)[:from],
    :to => headers_for(action, opts)[:to],  
    :subject => headers_for(action, opts)[:subject],
    :html => html_output.to_str
end

我要做的下一个问题是将 'action' 的输入从符号更改为字符串。例如 :confirmation_instructions 变成了 'confirmation_instructions'

最后一个问题是系统试图在 membership_mailer 文件夹而不是 devise/mailers/ 文件夹中查找所有邮件程序模板。所以,我只是将设计模板移动到新文件夹中并解决了这个问题。

就是这样。正在调用新邮件程序,正确发送到 Mailgun API,从新文件夹调用设计邮件程序模板并将电子邮件发送给用户。希望这个详细的解释能对将来的人有所帮助。

这是最终的邮件程序代码:

class MembershipMailer < Devise::Mailer   

  helper :application
  include Devise::Controllers::UrlHelpers
  require 'rest-client'

  def mail(record, action, opts = {}, block)
    initialize_from_record(record)
    html_output = render_to_string(:action => action, :layout => false)

    RestClient.post ENV.fetch("MAILGUN_MX_URL"),
      :from => headers_for(action, opts)[:from],
      :to => headers_for(action, opts)[:to],  
      :subject => headers_for(action, opts)[:subject],
      :html => html_output.to_str
  end

  def confirmation_instructions(record, token, opts={})
    @token = token
    mail(record, 'confirmation_instructions', opts)
  end

  def reset_password_instructions(record, token, opts={})
    @token = token
    mail(record, 'reset_password_instructions', opts)
  end

  def unlock_instructions(record, token, opts={})
    @token = token
    mail(record, 'unlock_instructions', opts)
  end

  def email_changed(record, opts={})
    mail(record, 'email_changed', opts)
  end

  def password_change(record, opts={})
    mail(record, 'password_change', opts)
  end
end