Action Mailbox の基礎

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

このガイドの内容:

  • メールをRailsアプリケーションで受信する方法
  • Action Mailboxの設定方法
  • メールボックスの生成方法とメールをメールボックスにルーティングする方法
  • 受信メールをテストする方法

1 はじめに

Action Mailboxは、受信したメールをコントローラに似たメールボックスにルーティングし、Railsで処理できるようにします。なお、Action Mailerメール送信のための機能です

受信したメールはActive Jobによって非同期で1個以上の専用メールボックスにルーティングされます。それらのメールは、さらにActive Recordを用いてInboundEmailレコードに変換されます。InboundEmailは、ドメインモデルの他の部分と直接やりとりできるようになります。

InboundEmailは、ライフサイクルのトラッキング機能や、Active Storageを介したオリジナルメールの保存機能、およびデフォルトでデータの焼却(incineration)を行う機能も提供します。

Action Mailboxは、Mailgun、Mandrill、Postmark、SendGridなどの外部メールプロバイダ用の入り口(ingress)を備えています。受信メールを組み込みのEximやPostfixやQmail用のingressで直接扱うことも可能です。

2 セットアップ

Action Mailboxにはいくつかの可動部分があります。 最初に、インストーラを実行します。 次に、受信メールを処理するingressを選択して設定します。 これでAction Mailboxのルーティング追加やメールボックス作成を行って、受信メールの処理を開始する準備が整います。

最初に、以下を実行してAction Mailboxをインストールします。

$ bin/rails action_mailbox:install

これで、Action MailboxのマイグレーションとActive Storageのマイグレーションが実行されます。

Action Mailboxのaction_mailbox_inbound_emailsテーブルには、受信メッセージと処理のステータスが保存されます。

この時点で、Railsサーバを起動してhttp://localhost:3000/rails/conductor/action_mailbox/inbound_emailsをチェックできるようになります。詳しくはローカル環境での開発とテストを参照してください。

次のステップは、Railsアプリケーションでのメール受信方法を指定するために、受信メールを処理するingressを選択して設定します。

3 ingressの設定

ingressの設定作業には、選択したメールサービスのcredentialやエンドポイント情報のセットアップ作業が関連しています。サポートされているingressごとにステップを以下に示します。

3.1 Exim

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

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

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

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

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署名キー(Signing key: MailgunのSettings -> Security & Users -> API securityにあります)を渡して、Mailgun ingressへのリクエストを認証できるようにします。

bin/rails credentials:editを実行して署名キーを追加します。署名キーはアプリケーションの暗号化済みcredentialのaction_mailbox.mailgun_signing_keyの下に追加されます(Action Mailboxはこのcredentialを自動的に見つけます)。

action_mailbox:
  mailgun_api_key: ...

または、MAILGUN_INGRESS_SIGNING_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キーを渡して、Mandrillのingressへのリクエストを認証できるようにします。

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

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へのリクエストを認証するのに使える強力なパスワードを生成します。

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

action_mailbox:
  ingress_password: ...

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

受信メールをbin/rails action_mailbox:ingress:postfixにルーティングするようPostfixを設定し、Postfix ingressの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

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

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

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

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

action_mailbox:
  ingress_password: ...

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

受信メールを/rails/action_mailbox/postmark/inbound_emailsに転送するようPostmarkのinbound webhookを設定し、ユーザー名actionmailboxと上で生成したパスワードを指定します。アプリケーションが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へのリクエストを認証するのに使える強力なパスワードを生成します。

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

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へのリクエストを認証するのに使える強力なパスワードを生成します。

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

action_mailbox:
  ingress_password: ...

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

受信メールを/rails/action_mailbox/sendgrid/inbound_emailsに転送するようSendGridのInbound Parseを設定し、ユーザー名actionmailboxと上で生成したパスワードを指定します。アプリケーションが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 受信メールを処理する

Railsアプリケーションで受信メールを処理するには、通常、メールのコンテンツを用いてモデルを作成し、ビューを更新し、バックグラウンド作業をエンキュー(enqueue: キューに入れる)する必要があります。

受信メールの処理を開始する前に、Action Mailboxのルーティングを設定し、メールボックスを作成しておく必要があります。

4.1 ルーティングを設定する

設定したingress経由で受信したメールをアプリケーションで実際に処理するには、メールボックスに転送する必要があります。Action Mailboxのルーティングは、RailsのルーターがURLをコントローラーにディスパッチするのと同様に、どのメールがどのメールボックスに送信されて処理されるかを定義します。このルーティングは、正規表現を用いて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"というメールボックスに転送します。メールをルーティングする方法はいくつもあります。詳しくはAPIドキュメントActionMailbox::Baseを参照してください。

続いて、forwards"というメールボックスを作成する必要があります。

5 メールボックスを設定する

# 新しいメールボックスを生成する
$ bin/rails generate mailbox forwards

上を実行するとapp/mailboxes/forwards_mailbox.rbファイルが作成され、そこにForwardsMailboxクラスとprocessメソッドも同時に作成されます。

5.1 メールを処理する

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: 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">

5.2 受信メールのステータス

メールにマッチするメールボックスにメールがルーティングされて処理されている間、Action Mailboxは action_mailbox_inbound_emailsテーブルに保存されているメールのステータスを次のいずれかの値で更新します。

  • pending: ingressコントローラの1つがメールを受信完了して、ルーティングがスケジュールされている状態。
  • processing: アクティブな処理内で、特定のメールボックスがそのprocessメソッドを実行中の状態。
  • delivered: メールが特定のメールボックスによって正常に処理完了した状態。
  • failed: 特定のメールボックスのprocessメソッドの実行中に例外が発生したことを表す。
  • bounced: 特定のメールボックスでメールの処理が拒否され、送信者にバウンス(bounce: )された状態。

メールのステータスがdeliveredfailedbouncedのいずれかになった場合、そのメールは「処理完了」とみなされ、焼却とマーキングされます。

6 例

Action Mailboxでメールを処理してプロジェクトの"forwards"を作成するアクションの例を次に示します。。

before_processingコールバックは、processメソッドが呼び出される前に特定の条件が確実に満たされるようにする目的で使います。before_processingは、ユーザーに少なくとも1個のプロジェクトが存在することをチェックします。Action Mailboxのコールバック では、この他にafter_processingaround_processingもサポートされています。

"forwarder"にプロジェクトが1個もない場合は、bounced_withでメールをバウンスできます。 この"forwarder"は、mail.fromと同じメールアドレスを持つUserです。

"forwarder"にプロジェクトが1個以上ある場合は、record_forwardメソッドでアプリケーションのActive Recordモデルを作成し、そのモデルにはメールのmail.subjectmail.decodedデータが含まれます。それ以外の場合はAction Mailerでメールを送信して、何らかのプロジェクトを"forwarder"で選択するようリクエストします。

# app/mailboxes/forwards_mailbox.rb
class ForwardsMailbox < ApplicationMailbox
  # 処理に必要な条件をコールバックで指定する
  before_processing :require_projects

  def process
    # 転送を1個のプロジェクトに記録する、または…
    if forwarder.projects.one?
      record_forward
    else
      # …2番目のAction Mailerに転送先プロジェクトを問い合わせてもらう
      request_forwarding_project
    end
  end

  private
    def require_projects
      if forwarder.projects.none?
        # Action Mailersを用いて受信メールを送信者に送り返す(bounce back)
        # ここで処理が停止する
        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

7 ローカル環境での開発とテスト

development環境では、実際にメールを送受信せずにメールの受信をテストできると便利です。このために、/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: '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

テストヘルパーメソッドについて詳しくは、APIドキュメントのActionMailbox::TestHelperを参照してください。

8 InboundEmailsの焼却

デフォルトでは、処理が成功したInboundEmailは30日後にincinerate(焼却)されます。これにより、アカウントをキャンセルまたはコンテンツを削除したユーザーのデータをむやみに保持せずに済みます。この設計では、メールを処理した後に必要なメールをすべて切り出して、アプリケーションの業務ドメインモデルやコンテンツに取り込む必要があることが前提となります。InboundEmailがシステムに余分に保持される期間は、単にデバッグや事後調査のためのものです。

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

デフォルトのデータincinationにより、ユーザーがアカウントをキャンセルしたりコンテンツを削除したりした後でも、不要なユーザーデータを保持しないようになります。

Action Mailboxにおけるこの処理の目的は、メールを処理するときに、必要なすべてのデータを電子メールから抽出し、アプリケーションのドメインモデルに保持することです。InboundEmailは、デバッグやフォレンジック調査のために設定された期間だけシステム内に残り、その後削除されます。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加