在 Rails 中使用 Action Mailer 和 Gmail 发送电子邮件
排查邮件发送错误
测试
结论
在这篇文章中,我将介绍如何在 Rails 中设置一个邮件发送器,当客户提交订单时,该发送器会自动向网站所有者发送电子邮件。(我使用的是 Rails 6,但此方法也应该适用于 Rails 5)。
步骤如下:
- 设置邮件发送程序
rails generate mailer - 创建电子邮件模板(视图)
- 指示相应的控制器执行发送电子邮件的操作。
- 设置电子邮件预览器
- 配置 Gmail 的邮件设置。
- 设置测试
假设我们已经设置了基本的订单模型和订单控制器,当订单成功时,它会通过闪信向客户显示确认信息:
# 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
我们还希望在收到订单时向网站所有者发送电子邮件,否则他们怎么知道呢?
首先,我们需要创建一个邮件发送器,这使用 Railsgenerate mailer命令很容易实现。在本例中,邮件发送器将在 `<mailer_name>` 中使用,OrdersController因此我们将其命名为OrderMailer:
$ rails generate mailer OrderMailer
这将创建以下文件并输出:
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
我们打开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
# app/mailers/order_mailer.rb
class OrderMailer < ApplicationMailer
end
新的邮件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
*请替换<ADMIN_EMAIL>为您想要使用的电子邮件地址,最好将其隐藏为环境变量。
new_order_email邮件视图中可以使用任何实例变量。这些变量会在我们指示邮件程序发送电子邮件params[:order]时提供(我将在下文详细介绍)。OrderController
现在我们来创建一个邮件视图文件,请确保文件名与方法名称相同。new_order_email.html.erb在app/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>
作为最佳实践,我们还要创建一个纯文本版本的电子邮件,以防收件人不使用 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 %>
现在我们已经设置好了电子邮件,接下来我们将告诉系统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
这里,我们在订单保存后添加了以下代码行:
OrderMailer.with(order: @order).new_order_email.deliver_later
注意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
如果我们尝试访问该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
重启 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
}
请将 `<username>` 和 `<password>`替换<gmail_username>为<gmail_password>您自己的用户名和密码,最好将它们设置为环境变量。关于密码的注意事项:我强烈建议您启用两步验证并注册一个“应用密码”用于该应用,否则您可能会遇到 Gmail 拦截邮件的问题。请参阅下方的故障排除部分。
仅限本地使用或开发用途
host = 'localhost:3000'
config.action_mailer.default_url_options = { :host => 'localhost:3000', protocol: 'http' }
而不是上述的设置host。config.action_mailer.default_url_options
为了测试邮件是否真的在开发环境中发送,请将相同的设置添加到配置文件中config/environments/development.rb。之后,您可以修改该行代码config.action_mailer.delivery_method = :smtp,config.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
然后,在 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!"
这是获取样本订单以供邮件测试器使用的简便方法。
接下来,在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
请将此处<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