1 什么是 Action Mailbox?
Action Mailbox 将传入邮件路由到类似控制器的邮箱,以便在您的 Rails 应用程序中进行处理。Action Mailbox 用于接收邮件,而 Action Mailer 用于发送邮件。
传入邮件使用 Active Job 异步路由到一个或多个专用邮箱。这些邮件使用 Active Record 转换为 InboundEmail
记录,这些记录能够直接与您域模型的其余部分交互。
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://127.0.0.1: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
提供您的 Mailgun 签名密钥(您可以在 Mailgun 的“设置”->“安全和用户”->“API 安全”中找到它)给 Action Mailbox,以便它可以验证对 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
提供您的 Mandrill API 密钥给 Action Mailbox,以便它可以验证对 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:[email protected]/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:[email protected]/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: [email protected]>, <To: [email protected]>, <Message-ID: <[email protected]>>, <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
=> ["[email protected]"]
irb> mail.from
=> ["[email protected]"]
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
将邮件退回。 "转发者" 是一个与 mail.from
电子邮件相同的 User
。
如果 "转发者" 确实至少有一个项目,则 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
的 conductor 控制器,它提供所有系统中 InboundEmails 的索引,它们的处理状态,以及创建新 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: "[email protected]",
from: people(:david).email_address,
subject: "Fwd: Status update?",
body: <<~BODY
--- Begin forwarded message ---
From: Frank Holland <[email protected]>
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 天后被销毁。当 InboundEmail
的状态变为 delivered
、failed
或 bounced
时,它被认为是已处理。
实际销毁是通过 IncinerationJob
完成的,该工作计划在 config.action_mailbox.incinerate_after
时间后运行。此值默认设置为 30.days
,但您可以在 production.rb 配置中更改它。(注意,这种未来很长时间的销毁调度依赖于您的作业队列能够保存这么长时间的作业)。
默认数据销毁确保您不会在用户可能已取消账户或删除其内容后不必要地保留他们的数据。
Action Mailbox 处理的目的是,当您处理邮件时,您应该从邮件中提取所需的所有数据,并将其持久化到应用程序中的域模型中。InboundEmail
会保留在系统中,以便您在配置的时间内进行调试和取证,然后会被删除。