发布于 2026-01-05 21 阅读
0

使用 Rails 中的 Action Mailer 和 Gmail 发送电子邮件:排查电子邮件发送错误、测试结论

在 Rails 中使用 Action Mailer 和 Gmail 发送电子邮件

排查邮件发送错误

测试

结论

在这篇文章中,我将介绍如何在 Rails 中设置一个邮件发送器,当客户提交订单时,该发送器会自动向网站所有者发送电子邮件。(我使用的是 Rails 6,但此方法也应该适用于 Rails 5)。

步骤如下:

  1. 设置邮件发送程序rails generate mailer
  2. 创建电子邮件模板(视图)
  3. 指示相应的控制器执行发送电子邮件的操作。
  4. 设置电子邮件预览器
  5. 配置 Gmail 的邮件设置。
  6. 设置测试

假设我们已经设置了基本的订单模型和订单控制器,当订单成功时,它会通过闪信向客户显示确认信息:

# app/controllers/orders_controller.rb

class OrdersController < ApplicationController
  def new
    @order = Order.new
  end

  def create
    @order = Order.new(order_params)

    if @order.save
      flash[:success] = t('flash.order.success')
      redirect_to root_path
    else
      flash.now[:error] = t('flash.order.error_html')
      render :new
    end
  end

  private

  def order_params
    params.require(:order).permit(:name, :email, :address, :phone, :message)
  end
end

Enter fullscreen mode Exit fullscreen mode

我们还希望在收到订单时向网站所有者发送电子邮件,否则他们怎么知道呢?

首先,我们需要创建一个邮件发送器,这使用 Railsgenerate mailer命令很容易实现。在本例中,邮件发送器将在 `<mailer_name>` 中使用,OrdersController因此我们将其命名为OrderMailer

$ rails generate mailer OrderMailer
Enter fullscreen mode Exit fullscreen mode

这将创建以下文件并输出:

create  app/mailers/order_mailer.rb
invoke  erb
create    app/views/order_mailer
invoke  test_unit
create    test/mailers/order_mailer_test.rb
create    test/mailers/previews/order_mailer_preview.rb
Enter fullscreen mode Exit fullscreen mode

我们打开app/mailers文件夹。我们会找到application_mailer.rb那些新创建的order_mailer.rb文件。

# app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com' # Replace this email address with your own
  layout 'mailer'
end

Enter fullscreen mode Exit fullscreen mode
# app/mailers/order_mailer.rb

class OrderMailer < ApplicationMailer
end

Enter fullscreen mode Exit fullscreen mode

新的邮件OrderMailer发送器的工作方式与常规控制器非常相似。控制器准备 HTML 等内容,并通过视图将其显示给用户。邮件发送器则准备电子邮件格式的内容并将其发送出去。我们可以像向控制器添加操作一样,通过向邮件发送器添加方法来创建电子邮件。

让我们为订单电子邮件添加一个方法OrderMailer

# app/mailers/order_mailer.rb

class OrderMailer < ApplicationMailer
  def new_order_email
    @order = params[:order]

    mail(to: <ADMIN_EMAIL>, subject: "You got a new order!")
  end
end
Enter fullscreen mode Exit fullscreen mode

*请替换<ADMIN_EMAIL>为您想要使用的电子邮件地址,最好将其隐藏为环境变量。

new_order_email邮件视图中可以使用任何实例变量。这些变量会在我们指示邮件程序发送电子邮件params[:order]时提供(我将在下文详细介绍)。OrderController

现在我们来创建一个邮件视图文件,请确保文件名与方法名称相同。new_order_email.html.erbapp/views/order_mailer/文件夹中创建一个文件:

# app/views/order_mailer/new_order_email.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
  </head>
  <body>
    <p>You got a new order from <%= @order.name %>!</p>
    <p>
    Order details<br>
    --------------------------
    </p>
    <p>Name: <%= @order.name %></p>
    <p>Email: <%= @order.email %></p>
    <p>Address: <%= @order.address %></p>
    <p>Phone: <%= @order.phone %></p>
    <p>
    Message:<br>
    ----------
    </p>
    <p><%= @order.message %></p>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

作为最佳实践,我们还要创建一个纯文本版本的电子邮件,以防收件人不使用 HTML 电子邮件。该纯文本版本应放在同一文件夹中,文件名相同,但使用 .txttext.erb扩展名而不是 .html html.erb

# app/views/order_mailer/new_order_email.text.erb

You got a new order from <%= @order.name %>!
===============================================

Order Details:
--------------------------

Name: <%= @order.name %>
Email: <%= @order.email %>
Address: <%= @order.address %>
Phone: <%= @order.phone %>

Message:
<%= @order.message %>
Enter fullscreen mode Exit fullscreen mode

现在我们已经设置好了电子邮件,接下来我们将告诉系统OrdersController在订单生成时发送电子邮件,也就是在create操作中保存订单之后发送电子邮件。

class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)

    if @order.save
      OrderMailer.with(order: @order).new_order_email.deliver_later

      flash[:success] = "Thank you for your order! We'll get contact you soon!"
      redirect_to root_path
    else
      flash.now[:error] = "Your order form had some errors. Please check the form and resubmit."
      render :new
    end
  end

  ...
end
Enter fullscreen mode Exit fullscreen mode

这里,我们在订单保存后添加了以下代码行:

OrderMailer.with(order: @order).new_order_email.deliver_later
Enter fullscreen mode Exit fullscreen mode

注意with(order: @order)代码。正是这段代码允许OrderMailer将订单信息作为参数传递。还记得@order = params[:order]在问号中设置实例变量吗OrderMailer?参数就是从那里来的!

那么,我们如何在发送邮件前预览它呢?其实,当我们生成邮件程序时,会在test/mailers/previews/文件夹中创建一个预览文件。该文件包含一个名为 `<prepression_name>` 的文件,其中order_mailer_preview.rb包含一个空OrderMailerPreview类:

# test/mailers/previews/order_mailer_preview.rb

# Preview all emails at http://localhost:3000/rails/mailers/order_mailer
class OrderMailerPreview < ActionMailer::Preview

end
Enter fullscreen mode Exit fullscreen mode

如果我们尝试访问该http://localhost:3000/rails/mailers/order_mailer网址,它只会显示一个空白页面,上面写着“订单邮件”。

要为我们的新订单电子邮件设置预览,只需添加一个与要预览的邮件发送方法同名的方法(在本例中为 ` new_order_emailmailor`),并设置邮件发送器:

# Preview all emails at http://localhost:3000/rails/mailers/order_mailer
class OrderMailerPreview < ActionMailer::Preview
  def new_order_email
    # Set up a temporary order for the preview
    order = Order.new(name: "Joe Smith", email: "joe@gmail.com", address: "1-2-3 Chuo, Tokyo, 333-0000", phone: "090-7777-8888", message: "I want to place an order!")

    OrderMailer.with(order: order).new_order_email
  end
end
Enter fullscreen mode Exit fullscreen mode

重启 Rails 服务器并访问该地址即可http://localhost:3000/rails/mailers/order_mailer/new_order_email查看邮件。您甚至可以同时看到 HTML 版本和纯文本版本。太棒了!您可以调整邮件的外观,直到满意为止。(此外,http://localhost:3000/rails/mailers/order_mailer现在还会显示可用预览列表。)

最后,我们需要配置 Rails 应用以通过 Gmail 发送电子邮件。为此,我们将以下设置添加到我们的配置中config/environments/production.rb

# config/environments/production.rb

config.action_mailer.delivery_method = :smtp
host = 'example.com' #replace with your own url
config.action_mailer.default_url_options = { host: host }

# SMTP settings for gmail
config.action_mailer.smtp_settings = {
  :address              => "smtp.gmail.com",
  :port                 => 587,
  :user_name            => <gmail_username>,
  :password             => <gmail_password>,
  :authentication       => "plain",
  :enable_starttls_auto => true
}
Enter fullscreen mode Exit fullscreen mode

请将 `<username>` 和 `<password>`替换<gmail_username><gmail_password>您自己的用户名和密码,最好将它们设置为环境变量。关于密码的注意事项:我强烈建议您启用两步验证并注册一个“应用密码”用于该应用,否则您可能会遇到 Gmail 拦截邮件的问题。请参阅下方的故障排除部分。

仅限本地使用或开发用途

host = 'localhost:3000'
config.action_mailer.default_url_options = { :host => 'localhost:3000', protocol: 'http' }
Enter fullscreen mode Exit fullscreen mode

而不是上述的设置hostconfig.action_mailer.default_url_options

为了测试邮件是否真的在开发环境中发送,请将相同的设置添加到配置文件中config/environments/development.rb。之后,您可以修改该行代码config.action_mailer.delivery_method = :smtpconfig.action_mailer.delivery_method = :test以防止在开发过程中发送真正的电子邮件。

排查邮件发送错误

由于谷歌的安全机制,上述配置一开始对我不起作用,我遇到了Net::SMTPAuthenticationError一些错误。以下是我最终成功配置的方法。

启用两步验证的账户

如果您的 Gmail 帐户启用了两步验证,您需要获取一个应用密码并使用该密码代替您的常规密码。如果您未启用两步验证,我建议您启用它,以避免电子邮件被 Google 拦截。

要创建应用密码,请前往您的 Google 帐户设置,然后导航至“安全”标签页。在“登录 Google”下方,点击“应用密码”菜单项(仅当您启用两步验证时才会显示此选项)。接下来,Other (Custom name)在“选择应用”下拉菜单中选择您的应用,然后输入应用名称或其他易于记忆的内容。点击“生成”,您的新应用密码将显示在屏幕上。请务必在关闭窗口前复制密码,否则您将无法再次查看。

现在,在你的 Rails 应用邮件设置中,将原密码替换<gmail_password>为新的应用密码。

未进行两步验证的账户

如果您不使用两步验证,则需要允许“安全性较低的应用”访问您的帐户。在 Google 设置的“安全”标签页下,找到“安全性较低的应用访问权限”部分,然后点击“启用访问权限”。

推送到生产环境后,我仍然无法发送邮件,因为谷歌会阻止来自未知位置的访问,在本例中指的是生产环境中的应用。我通过“显示解锁验证码”流程解决了这个问题。如果您在完成上述操作后仍然遇到问题,此操作将暂时授予您的帐户访问权限,以便您注册新应用。

请访问http://www.google.com/accounts/DisplayUnlockCaptcha启用此选项。之后,让应用再次发送电子邮件。这样应该就能注册该应用,之后您就可以发送电子邮件了!

更新:几个月后,不知何故,通过应用发送的邮件再次被拦截,邮箱里也开始收到安全警告。为了避免麻烦,我建议使用两步验证+应用密码的方式。

Rails凭证与Heroku的问题

我在 Heroku 生产环境中遇到的最后一个问题是忘记在 Heroku 应用设置中注册 Rails 应用的主密钥,也没有配置 Rails 使其需要该主密钥。如果您使用 Rails 的凭据文件(例如 `.js` credentials.yml.enc)来管理您的私钥(在本例中是您的邮箱和密码),这将导致问题。

在开发过程中,Rails 可以访问密钥凭证,因为主密钥直接存在于系统中;但在 Heroku 的生产环境中,应用程序必须先向 Heroku 注册主密钥,才能访问密钥。

要解决此问题,请在config/environments/production.rb文件中取消注释或添加以下行:

config.require_master_key = true
Enter fullscreen mode Exit fullscreen mode

然后,在 Heroku 应用设置中,注册一个名为 `<key_name>` 的密钥RAILS_MASTER_KEY。输入文件中找到的值config/master.key。这样 Heroku 就可以访问文件中注册的密钥credentials.yml.enc

测试

最后,让我们确保设置了一些测试!

orders.yml如果该目录下尚不存在该文件test_fixtures,请创建一个并添加以下内容:

# test/fixtures/orders.yml

one:
  name: "Joe Smith"
  email: "joe@gmail.com"
  address: "1-2-3 Chuo, Tokyo, 333-0000"
  phone: "090-7777-8888"
  message: "I want to place an order!"
Enter fullscreen mode Exit fullscreen mode

这是获取样本订单以供邮件测试器使用的简便方法。

接下来,在test/mailers/order_mailer_test.rb(如果该文件尚不存在,请创建该文件)中,我们可以添加一个简单的测试,断言电子邮件正在发送且内容正确。

require 'test_helper'

class OrderMailerTest < ActionMailer::TestCase
  test "new order email" do
    # Set up an order based on the fixture
    order = orders(:one)

    # Set up an email using the order contents
    email = OrderMailer.with(order: order).new_order_email

    # Check if the email is sent
    assert_emails 1 do
      email.deliver_now
    end

    # Check the contents are correct
    assert_equal [<ADMIN_EMAIL>], email.from
    assert_equal [<ADMIN_EMAIL>], email.to
    assert_equal "You got a new order!", email.subject
    assert_match order.name, email.html_part.body.encoded
    assert_match order.name, email.text_part.body.encoded
    assert_match order.email, email.html_part.body.encoded
    assert_match order.email, email.text_part.body.encoded
    assert_match order.address, email.html_part.body.encoded
    assert_match order.address, email.text_part.body.encoded
    assert_match order.phone, email.html_part.body.encoded
    assert_match order.phone, email.text_part.body.encoded
    assert_match order.message, email.html_part.body.encoded
    assert_match order.message, email.text_part.body.encoded
  end
end
Enter fullscreen mode Exit fullscreen mode

请将此处<ADMIN_EMAIL>替换为您使用的电子邮件地址。

email.html_part.body.encoded检查 HTML 电子邮件中的内容,同时email.text_part.body.encoded检查纯文本电子邮件。

结论

我们现在应该有一个功能完善的邮件系统,可以通知我们(或网站所有者)新收到的订单!

Rails邮件程序和Gmail设置完成后,我们就可以通过类似的方式生成和设置新的邮件程序,轻松地从其他控制器操作发送其他邮件。:)

文章来源:https://dev.to/morinoko/sending-emails-in-rails-with-action-mailer-and-gmail-35g4