Action Mailbox の基礎

本ガイドでは、アプリケーションでメールを受信するために必要なすべての情報を提供します。

このガイドの内容:

1 はじめに

Action Mailboxは、受信したメールをコントローラに似たメールボックスにルーティングし、Railsで処理できるようにします。Action Mailboxは、Mailgun、Mandrill、Postmark、SendGridへの入り口(ingress)を備えています。受信メールを組み込みのEximやPostfixやQmail用のingressで直接扱うこともできます。

受信メールはActive Recordを用いてInboundEmailレコードになり、Active Storageによってライフサイクルトラッキングや元のメールのクラウドストレージ保存を行い、データの扱いを「on-by-default incineration(焼却)」で扱います。

受信メールはActive Jobによって非同期的に1つまたは複数の専用メールボックスにルーティングされ、ドメインモデルの他の部分と直接やりとりできます。

2 セットアップ

InboundEmailで必要なマイグレーションをインストールし、Active Storageがセットアップ済みであることを確認します。

$ rails action_mailbox:install
$ rails db:migrate

3 設定

3.1 Exim

SMTPリレーからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :relay

Action Mailboxがrelay ingressへのリクエストを認証するのに使える強力なパスワードを生成します。

action_mailbox.ingress_passwordの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでパスワードを追加します。

action_mailbox:
  ingress_password: ...

または、RAILS_INBOUND_EMAIL_PASSWORD環境変数でパスワードを指定します。

Eximが受信メールをbin/rails action_mailbox:ingress:eximにパイプでつなぐよう設定し、relay ingressの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 API keyを渡して、Mailgunのingressへのリクエストを認証できるようにします。

action_mailbox.mailgun_api_keyの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでAPIキーを追加します。

action_mailbox:
  mailgun_api_key: ...

または、MAILGUN_INGRESS_API_KEY環境変数でパスワードを指定します。

Mailgunからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :mailgun

受信メールを/rails/action_mailbox/mailgun/inbound_emails/mimeに転送するようMailgunを設定します。アプリケーションがhttps://example.comにある場合、完全修飾済みURLをhttps://example.com/rails/action_mailbox/mailgun/inbound_emails/mimeのように指定します。

3.3 Mandrill

Action Mailboxに自分のMandrill API keyを渡して、Mandrillのingressへのリクエストを認証できるようにします。

action_mailbox.mandrill_api_keyの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでAPIキーを追加します。

action_mailbox:
  mandrill_api_key: ...

または、MANDRILL_INGRESS_API_KEY環境変数でパスワードを指定します。

Mandrillからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :mandrill

受信メールを/rails/action_mailbox/mandrill/inbound_emailsにルーティングするようMandrillを設定します。アプリケーションがhttps://example.comにある場合、完全修飾済みURLをhttps://example.com/rails/action_mailbox/mandrill/inbound_emailsのように指定します。

3.4 Postfix

SMTPリレーからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :relay

Action Mailboxがrelay ingressへのリクエストを認証するのに使える強力なパスワードを生成します。

action_mailbox.ingress_passwordの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでAPIキーを追加します。

action_mailbox:
  ingress_password: ...

または、RAILS_INBOUND_EMAIL_PASSWORD環境変数でパスワードを指定します。

受信メールをbin/rails action_mailbox:ingress:postfixにルーティングするようPostfixを設定します。アプリケーションがhttps://example.comにある場合、完全なコマンドは次のような感じになります。

$ bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=...

3.5 Postmark

Postmarkからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :postmark

Action MailboxがPostmarkのingressへのリクエストを認証するのに使える強力なパスワードを生成します。

action_mailbox.ingress_passwordの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでAPIキーを追加します。

action_mailbox:
  ingress_password: ...

または、RAILS_INBOUND_EMAIL_PASSWORD環境変数でパスワードを指定します。

受信メールを/rails/action_mailbox/postmark/inbound_emailsに転送するようPostmarkのinbound webhookを設定します。アプリケーションがhttps://example.comにある場合、完全なコマンドは次のような感じになります。

https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails

Postmarkのinbound webhookを設定するときには、必ず"Include raw email content in JSON payload"というチェックボックスをオンにしてください。Action Mailboxがrawメールを処理するのに必要です。

3.6 Qmail

SMTPリレーからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :relay

Action Mailboxがrelay ingressへのリクエストを認証するのに使える強力なパスワードを生成します。

action_mailbox.ingress_passwordの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでAPIキーを追加します。

action_mailbox:
  ingress_password: ...

または、RAILS_INBOUND_EMAIL_PASSWORD環境変数でパスワードを指定します。

受信メールをbin/rails action_mailbox:ingress:qmailにパイプでつなぐようQmailを設定し、relay ingressの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

SendGridからのメールを受け取るようAction Mailboxに指示します。

# config/environments/production.rb
config.action_mailbox.ingress = :sendgrid

Action MailboxがSendGridのingressへのリクエストを認証するのに使える強力なパスワードを生成します。

action_mailbox.ingress_passwordの下にあるアプリケーションの暗号化済みcredential(Action Mailboxはこのcredentialを自動的に見つけます)にrails credentials:editでAPIキーを追加します。

action_mailbox:
  ingress_password: ...

または、RAILS_INBOUND_EMAIL_PASSWORD環境変数でパスワードを指定します。

受信メールを/rails/action_mailbox/sendgrid/inbound_emailsに転送するようSendGridのInbound Parseを設定します。アプリケーションがhttps://example.comにある場合、SendGridの設定に使うURLは次のような感じになります。

https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails

SendGridのInbound Parse webhookを設定するときには、必ず“Post the raw, full MIME message”というチェックボックスをオンにしてください。Action Mailboxがraw MIMEメッセージを処理するのに必要です。

4 例

基本的なルーティングを設定します。

# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
  routing /^save@/i     => :forwards
  routing /@replies\./i => :replies
end

続いてメールボックスを設定します。

# 新しいメールボックスを生成する
$ bin/rails generate mailbox forwards
# app/mailboxes/forwards_mailbox.rb
class ForwardsMailbox < ApplicationMailbox
  # 処理に必要な条件をコールバックで指定する
  before_processing :require_forward

  def process
    if forwarder.buckets.one?
      record_forward
    else
      stage_forward_and_request_more_details
    end
  end

  private
    def require_forward
      unless message.forward?
        # Action Mailersを用いて受信メールを送信者に送り返す(bounce back)
        # ここで処理が停止する
        bounce_with Forwards::BounceMailer.missing_forward(
          inbound_email, forwarder: forwarder
        )
      end
    end

    def forwarder
      @forwarder ||= Person.where(email_address: mail.from)
    end

    def record_forward
      forwarder.buckets.first.record \
        Forward.new forwarder: forwarder, subject: message.subject, content: mail.content
    end

    def stage_forward_and_request_more_details
      Forwards::RoutingMailer.choose_project(mail).deliver_now
    end
end

5 InboundEmailsの「焼却(incineration)」

デフォルトでは、処理が成功したInboundEmailは30日が経過すると焼却(incinerate)されます。これにより、アカウントをキャンセルまたはコンテンツを削除したユーザーのデータをぐずぐず保持せずに済みます。設計の意図は、メールを処理した後に必要なメールをすべて切り出してアプリケーションの業務ドメインモデルやコンテンツに取り込んでおくべきであるということです。InboundEmailは単に、デバッグや法医学的なオプションを提供する目的でシステムに余分な期間残されます。

実際のincinerationは、config.action_mailbox.incinerate_afterでスケジュールされた時刻の後、IncinerationJobで行われます。この値はデフォルトで30.daysに設定されますが、production.rbで設定を変更できます(incinerationを遠い未来にスケジューリングする場合、その間ジョブキューがジョブを保持できることが重要です)。

6 Action Mailboxをdevelopment環境で使う

実際にメールを送受信せずに、development環境でメールの受信をテストできると便利です。このために、/rails/conductor/action_mailbox/inbound_emailsに「コンダクター(conductor)」コントローラがマウントされます。これはシステム内にあるすべてのInboundEmailsのインデックスや処理の状態を提供し、新しいInboundEmailを作成できるフォームも提供します。

7 メールボックスをテストする

例:

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: ステータスは更新された?",
        body: <<~BODY
          --- Begin forwarded message ---
          From: Frank Holland <frank@microsoft.com>

          現在のステータスは?
        BODY
    end

    recording = people(:david).buckets.first.recordings.last
    assert_equal people(:david), recording.creator
    assert_equal "ステータスは更新された?", recording.forward.subject
    assert_match "現在のステータスは?", recording.forward.content.to_s
  end
end

支援・協賛

Railsガイドは下記のサポーターから継続的な支援を受けています。