更多内容请访问 rubyonrails.org:

Action Mailbox 基础

本指南提供开始接收应用程序邮件所需的一切信息。

阅读本指南后,您将了解

  • 如何在 Rails 应用程序中接收电子邮件。
  • 如何配置 Action Mailbox。
  • 如何生成和路由邮件到邮箱。
  • 如何测试传入邮件。

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

正则表达式匹配传入邮件的 toccbcc 字段。例如,上面的代码将匹配发送到 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.tomail.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: 被特定邮箱拒绝处理并退回给发件人。

如果邮件被标记为 deliveredfailedbounced,则它被认为是 "已处理",并被标记为 销毁

5 例子

以下是一个 Action Mailbox 的例子,它处理邮件以创建用户的项目 "转发"。

before_processing 回调用于确保在调用 process 方法之前满足某些条件。在本例中,before_processing 检查用户是否至少有一个项目。其他支持的 Action Mailbox 回调after_processingaround_processing

如果 "转发者" 没有项目,可以使用 bounced_with 将邮件退回。 "转发者" 是一个与 mail.from 电子邮件相同的 User

如果 "转发者" 确实至少有一个项目,则 record_forward 方法使用邮件数据 mail.subjectmail.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 的状态变为 deliveredfailedbounced 时,它被认为是已处理。

实际销毁是通过 IncinerationJob 完成的,该工作计划在 config.action_mailbox.incinerate_after 时间后运行。此值默认设置为 30.days,但您可以在 production.rb 配置中更改它。(注意,这种未来很长时间的销毁调度依赖于您的作业队列能够保存这么长时间的作业)。

默认数据销毁确保您不会在用户可能已取消账户或删除其内容后不必要地保留他们的数据。

Action Mailbox 处理的目的是,当您处理邮件时,您应该从邮件中提取所需的所有数据,并将其持久化到应用程序中的域模型中。InboundEmail 会保留在系统中,以便您在配置的时间内进行调试和取证,然后会被删除。



返回顶部