1. 什么是 Action Mailbox?
Action Mailbox 将收到的电子邮件路由到类似控制器的邮箱中,以便在您的 Rails 应用程序中进行处理。Action Mailbox 用于接收电子邮件,而 Action Mailer 用于**发送**电子邮件。
收到的电子邮件使用 Active Job 异步路由到一个或多个专用邮箱。这些电子邮件被转换为 InboundEmail 记录,使用 Active Record,能够直接与您的其余领域模型交互。
InboundEmail 记录还提供生命周期跟踪、通过 Active Storage 存储原始电子邮件,以及默认启用的 焚烧 的负责任数据处理。
Action Mailbox 附带了入口,使您的应用程序能够接收来自外部电子邮件提供商(如 Mailgun、Mandrill、Postmark 和 SendGrid)的电子邮件。您还可以通过内置的 Exim、Postfix 和 Qmail 入口直接处理收到的电子邮件。
2. 设置
Action Mailbox 有一些活动部件。首先,您将运行安装程序。接下来,您将选择并配置一个入口来处理收到的电子邮件。然后,您就可以添加 Action Mailbox 路由、创建邮箱并开始处理收到的电子邮件了。
首先,让我们安装 Action Mailbox
$ bin/rails action_mailbox:install
这将创建一个 application_mailbox.rb 文件并复制迁移。
$ bin/rails db:migrate
这将运行 Action Mailbox 和 Active Storage 迁移。
Action Mailbox 表 action_mailbox_inbound_emails 存储收到的邮件及其处理状态。
此时,您可以启动 Rails 服务器并查看 https://:3000/rails/conductor/action_mailbox/inbound_emails。有关更多信息,请参阅 本地开发和测试。
下一步是在 Rails 应用程序中配置一个入口,以指定应如何接收收到的电子邮件。
3. 入口配置
配置入口涉及为所选电子邮件服务设置凭据和端点信息。以下是每个受支持的入口的步骤。
3.1. Exim
告诉 Action Mailbox 接受来自 SMTP 中继的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :relay
生成一个强密码,Action Mailbox 可以使用它来验证对中继入口的请求。
使用 bin/rails credentials:edit 将密码添加到应用程序加密凭据中的 action_mailbox.ingress_password 下,Action Mailbox 将自动找到它
action_mailbox:
ingress_password: ...
或者,在 RAILS_INBOUND_EMAIL_PASSWORD 环境变量中提供密码。
配置 Exim 将收到的电子邮件通过管道传输到 bin/rails action_mailbox:ingress:exim,提供中继入口的 URL 和您之前生成的 INGRESS_PASSWORD。如果您的应用程序位于 https://example.com,则完整命令将如下所示
$ bin/rails action_mailbox:ingress:exim URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
3.2. Mailgun
向 Action Mailbox 提供您的 Mailgun 签名密钥(您可以在 Mailgun 的“设置”->“安全与用户”->“API 安全”下找到),以便它可以验证对 Mailgun 入口的请求。
使用 bin/rails credentials:edit 将您的签名密钥添加到应用程序加密凭据中的 action_mailbox.mailgun_signing_key 下,Action Mailbox 将自动找到它
action_mailbox:
mailgun_signing_key: ...
或者,在 MAILGUN_INGRESS_SIGNING_KEY 环境变量中提供您的签名密钥。
告诉 Action Mailbox 接受来自 Mailgun 的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :mailgun
配置 Mailgun 将收到的电子邮件转发到 /rails/action_mailbox/mailgun/inbound_emails/mime。如果您的应用程序位于 https://example.com,您将指定完全限定的 URL https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime。
3.3. Mandrill
向 Action Mailbox 提供您的 Mandrill API 密钥,以便它可以验证对 Mandrill 入口的请求。
使用 bin/rails credentials:edit 将您的 API 密钥添加到应用程序加密凭据中的 action_mailbox.mandrill_api_key 下,Action Mailbox 将自动找到它
action_mailbox:
mandrill_api_key: ...
或者,在 MANDRILL_INGRESS_API_KEY 环境变量中提供您的 API 密钥。
告诉 Action Mailbox 接受来自 Mandrill 的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :mandrill
配置 Mandrill 将收到的电子邮件路由到 /rails/action_mailbox/mandrill/inbound_emails。如果您的应用程序位于 https://example.com,您将指定完全限定的 URL https://example.com/rails/action_mailbox/mandrill/inbound_emails。
3.4. Postfix
告诉 Action Mailbox 接受来自 SMTP 中继的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :relay
生成一个强密码,Action Mailbox 可以使用它来验证对中继入口的请求。
使用 bin/rails credentials:edit 将密码添加到应用程序加密凭据中的 action_mailbox.ingress_password 下,Action Mailbox 将自动找到它
action_mailbox:
ingress_password: ...
或者,在 RAILS_INBOUND_EMAIL_PASSWORD 环境变量中提供密码。
配置 Postfix 将收到的电子邮件通过管道传输到 bin/rails action_mailbox:ingress:postfix,提供 Postfix 入口的 URL 和您之前生成的 INGRESS_PASSWORD。如果您的应用程序位于 https://example.com,则完整命令将如下所示
$ bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
3.5. Postmark
告诉 Action Mailbox 接受来自 Postmark 的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :postmark
生成一个强密码,Action Mailbox 可以使用它来验证对 Postmark 入口的请求。
使用 bin/rails credentials:edit 将密码添加到应用程序加密凭据中的 action_mailbox.ingress_password 下,Action Mailbox 将自动找到它
action_mailbox:
ingress_password: ...
或者,在 RAILS_INBOUND_EMAIL_PASSWORD 环境变量中提供密码。
配置 Postmark 入站 Webhook 将收到的电子邮件转发到 /rails/action_mailbox/postmark/inbound_emails,用户名为 actionmailbox,密码为您之前生成的密码。如果您的应用程序位于 https://example.com,您将使用以下完全限定的 URL 配置 Postmark
https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails
配置 Postmark 入站 Webhook 时,请务必选中标有 **“在 JSON 有效负载中包含原始电子邮件内容”** 的复选框。Action Mailbox 需要原始电子邮件内容才能工作。
3.6. Qmail
告诉 Action Mailbox 接受来自 SMTP 中继的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :relay
生成一个强密码,Action Mailbox 可以使用它来验证对中继入口的请求。
使用 bin/rails credentials:edit 将密码添加到应用程序加密凭据中的 action_mailbox.ingress_password 下,Action Mailbox 将自动找到它
action_mailbox:
ingress_password: ...
或者,在 RAILS_INBOUND_EMAIL_PASSWORD 环境变量中提供密码。
配置 Qmail 将收到的电子邮件通过管道传输到 bin/rails action_mailbox:ingress:qmail,提供中继入口的 URL 和您之前生成的 INGRESS_PASSWORD。如果您的应用程序位于 https://example.com,则完整命令将如下所示
$ bin/rails action_mailbox:ingress:qmail URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...
3.7. SendGrid
告诉 Action Mailbox 接受来自 SendGrid 的电子邮件
# config/environments/production.rb
config.action_mailbox.ingress = :sendgrid
生成一个强密码,Action Mailbox 可以使用它来验证对 SendGrid 入口的请求。
使用 bin/rails credentials:edit 将密码添加到应用程序加密凭据中的 action_mailbox.ingress_password 下,Action Mailbox 将自动找到它
action_mailbox:
ingress_password: ...
或者,在 RAILS_INBOUND_EMAIL_PASSWORD 环境变量中提供密码。
配置 SendGrid 入站解析 将收到的电子邮件转发到 /rails/action_mailbox/sendgrid/inbound_emails,用户名为 actionmailbox,密码为您之前生成的密码。如果您的应用程序位于 https://example.com,您将使用以下 URL 配置 SendGrid
https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails
配置 SendGrid 入站解析 Webhook 时,请务必选中标有 **“发布原始、完整的 MIME 消息。”** 的复选框。Action Mailbox 需要原始 MIME 消息才能工作。
4. 处理收到的电子邮件
处理收到的电子邮件通常需要在您的 Rails 应用程序中使用电子邮件内容来创建模型、更新视图、排队后台工作等。
在开始处理收到的电子邮件之前,您需要设置 Action Mailbox 路由并创建邮箱。
4.1. 配置路由
通过配置的入口收到入站电子邮件后,需要将其转发到邮箱以供应用程序实际处理。与将 URL 分派给控制器的 Rails 路由器 非常相似,Action Mailbox 中的路由定义了哪些电子邮件发送到哪些邮箱进行处理。路由使用正则表达式添加到 application_mailbox.rb 文件中
# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
routing(/^save@/i => :forwards)
routing(/@replies\./i => :replies)
end
正则表达式匹配入站电子邮件的 to、cc 或 bcc 字段。例如,上面将匹配发送到 save@ 的任何电子邮件到“forwards”邮箱。还有其他路由电子邮件的方法,请参阅 ActionMailbox::Base 了解更多信息。
我们接下来需要创建那个“forwards”邮箱。
4.2. 创建邮箱
# Generate new mailbox
$ bin/rails generate mailbox forwards
这将创建 app/mailboxes/forwards_mailbox.rb,其中包含 ForwardsMailbox 类和 process 方法。
4.3. 处理电子邮件
处理 InboundEmail 时,您可以使用 InboundEmail#mail 将解析后的电子邮件作为 Mail 对象获取。您还可以使用 #source 方法直接获取原始源。使用 Mail 对象,您可以访问相关字段,例如 mail.to、mail.body.decoded 等。
irb> mail
=> #<Mail::Message:33780, Multipart: false, Headers: <Date: Wed, 31 Jan 2024 22:18:40 -0600>, <From: someone@hey.com>, <To: save@example.com>, <Message-ID: <65bb1ba066830_50303a70397e@Bhumis-MacBook-Pro.local.mail>>, <In-Reply-To: >, <Subject: Hello Action Mailbox>, <Mime-Version: 1.0>, <Content-Type: text/plain; charset=UTF-8>, <Content-Transfer-Encoding: 7bit>, <x-original-to: >>
irb> mail.to
=> ["save@example.com"]
irb> mail.from
=> ["someone@hey.com"]
irb> mail.date
=> Wed, 31 Jan 2024 22:18:40 -0600
irb> mail.subject
=> "Hello Action Mailbox"
irb> mail.body.decoded
=> "This is the body of the email message."
# mail.decoded, a shorthand for mail.body.decoded, also works
irb> mail.decoded
=> "This is the body of the email message."
irb> mail.body
=> <Mail::Body:0x00007fc74cbf46c0 @boundary=nil, @preamble=nil, @epilogue=nil, @charset="US-ASCII", @part_sort_order=["text/plain", "text/enriched", "text/html", "multipart/alternative"], @parts=[], @raw_source="This is the body of the email message.", @ascii_only=true, @encoding="7bit">
4.4. 收件状态
当电子邮件被路由到匹配的邮箱并进行处理时,Action Mailbox 会使用以下值之一更新存储在 action_mailbox_inbound_emails 表中的电子邮件状态
pending:由其中一个入口控制器接收并计划路由。processing:在活动处理期间,当特定邮箱正在运行其process方法时。delivered:由特定邮箱成功处理。failed:在特定邮箱执行process方法期间引发异常。bounced:被特定邮箱拒绝处理并退回给发件人。
如果电子邮件被标记为 delivered、failed 或 bounced,则认为它已“处理”并标记为 焚烧。
5. 示例
这是一个 Action Mailbox 的示例,它处理电子邮件以为用户的项目创建“转发”。
before_processing 回调用于确保在调用 process 方法之前满足某些条件。在这种情况下,before_processing 检查用户是否至少有一个项目。其他受支持的 Action Mailbox 回调 是 after_processing 和 around_processing。
如果“转发者”没有项目,可以使用 bounced_with 退回电子邮件。“转发者”是一个 User,其电子邮件与 mail.from 相同。
如果“转发者”确实至少有一个项目,则 record_forward 方法使用电子邮件数据 mail.subject 和 mail.decoded 在应用程序中创建一个 Active Record 模型。否则,它会使用 Action Mailer 发送一封电子邮件,请求“转发者”选择一个项目。
# app/mailboxes/forwards_mailbox.rb
class ForwardsMailbox < ApplicationMailbox
# Callbacks specify prerequisites to processing
before_processing :require_projects
def process
# Record the forward on the one project, or…
if forwarder.projects.one?
record_forward
else
# …involve a second Action Mailer to ask which project to forward into.
request_forwarding_project
end
end
private
def require_projects
if forwarder.projects.none?
# Use Action Mailers to bounce incoming emails back to sender – this halts processing
bounce_with Forwards::BounceMailer.no_projects(inbound_email, forwarder: forwarder)
end
end
def record_forward
forwarder.forwards.create subject: mail.subject, content: mail.decoded
end
def request_forwarding_project
Forwards::RoutingMailer.choose_project(inbound_email, forwarder: forwarder).deliver_now
end
def forwarder
@forwarder ||= User.find_by(email_address: mail.from)
end
end
6. 本地开发和测试
在开发中测试收到的电子邮件而无需实际发送和接收真实电子邮件是很有帮助的。为此,有一个控制器安装在 /rails/conductor/action_mailbox/inbound_emails,它为您提供了系统中所有 InboundEmail 的索引、它们的处理状态以及创建新 InboundEmail 的表单。
以下是使用 Action Mailbox TestHelpers 测试入站电子邮件的示例。
class ForwardsMailboxTest < ActionMailbox::TestCase
test "directly recording a client forward for a forwarder and forwardee corresponding to one project" do
assert_difference -> { people(:david).buckets.first.recordings.count } do
receive_inbound_email_from_mail \
to: "save@example.com",
from: people(:david).email_address,
subject: "Fwd: Status update?",
body: <<~BODY
--- Begin forwarded message ---
From: Frank Holland <frank@microsoft.com>
What's the status?
BODY
end
recording = people(:david).buckets.first.recordings.last
assert_equal people(:david), recording.creator
assert_equal "Status update?", recording.forward.subject
assert_match "What's the status?", recording.forward.content.to_s
end
end
有关更多测试帮助方法,请参阅 ActionMailbox::TestHelper API。
7. 收件焚烧
默认情况下,已处理的 InboundEmail 将在 30 天后被焚烧。当其状态变为 delivered、failed 或 bounced 时,InboundEmail 被视为已处理。
实际的焚烧是通过 IncinerationJob 完成的,该作业计划在 config.action_mailbox.incinerate_after 时间后运行。此值默认设置为 30.days,但您可以在 production.rb 配置中更改它。(请注意,这种遥远的焚烧调度依赖于您的作业队列能够长时间保留作业。)
默认数据焚烧可确保在人们可能已取消其帐户或删除其内容后,您不会不必要地保留人们的数据。
Action Mailbox 处理的目的是,当您处理电子邮件时,您应该从电子邮件中提取所需的所有数据并将其持久化到应用程序的领域模型中。InboundEmail 会在系统中保留配置的时间,以便进行调试和取证,然后将被删除。