Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 6のAction Mailboxを使ってみよう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

  • 英語記事: Rails 6 - Action Mailbox tryout – Saeloun Blog
  • 原文公開日: 2019/11/11
  • 著者: Romil Mehta
  • サイト: Saeloun -- Ruby on Railsのコンサルティング会社で、Rails + React開発のほかに、React Nativeによるモバイルアプリ開発も手がけています。

Rails 6のAction Mailboxを使ってみよう(翻訳)

アプリケーションで大量のメールを受信するはめになることがままあります。そういうメールを扱わなければならなくなった場合、操作のためにメールをひとつひとつ処理しなければなりません。

人事部門のユースケースで考えてみましょう。ある組織から、オープニングの参加候補者たちに「レジュメを送って欲しい」というメールを1件送信したとします。参加候補者たち全員がレジュメを添付してメールに返信したら、届いたレジュメをクラウドにアップロードして、レジュメ1件ごとにデータベースのエントリーを作成する必要があります。

以下はそのためのステップです。

  1. メールを1件ずつチェックする
  2. レジュメをダウンロードする
  3. レジュメをクラウドにアップロードして、レジュメのテーブルにエントリを作成する

ダルそうな作業ですね。

Action Mailboxを導入する

Rails 6では、受信メールを処理するためのAction Mailboxが導入されました。Action Mailboxは、受信メールをコントローラによく似たメールボックスにルーティングしてRailsで処理できるようにします。Mailgun、Mandrill、Postmark、SendGridといった主要なプラットフォームはひととおりサポートされています。

インストールと実装

以下のコマンドを実行して、feedback_collectorという新しいアプリケーションを作ります。

> rails new feedback_collector

rails action_mailbox:installを実行してAction Mailboxをインストールします。このとき、受信メール保存用にActive Storageも同時にインストールされます。メールはここに保存され、処理済みかどうかがトラッキングされます。

続いてActive Jobが読み込まれてメールを処理し、処理が終わったメールは削除されます。削除されたメールについてもidやチェックサムをトラッキングするので、同じメールがまたやってきたときに処理を回避できます。

> cd feedback_collector
> rails action_mailbox:install
#=> Copying application_mailbox.rb to app/mailboxes
#=>      create  app/mailboxes/application_mailbox.rb
#=> Copied migration 20191021075823_create_active_storage_tables.active_storage.rb from active_storage
#=> Copied migration 20191021075824_create_action_mailbox_tables.action_mailbox.rb from action_mailbox

上のコマンドで生成されるマイグレーションは、action_mailbox用とactive_storgage用です。application_mailboxも同時に作成されます。

それではUserProductFeedbackのscaffoldを生成しましょう。

> rails g scaffold User name email
> rails g scaffold Product title
> rails g scaffold Feedback user:references product:references content:text

以下のマイグレーションを実行します。

> rails db:migrate

action_mailboxテーブルのスキーマにはstatusmessage_idmessage_checksumなどのカラムが含まれます。statuspendingprocessingfinishedbouncedのいずれかになります。message_idmessage_checksumは重複防止用です。

ApplicationMailboxクラスは以下のような感じになります。

class ApplicationMailbox < ActionMailbox::Base
  # routing /something/i => :somewhere
end

メールのルーティングは以下のように定義します。

class ApplicationMailbox < ActionMailbox::Base
  # routing /something/i => :somewhere
  routing  :all => :feedbacks
end

すべてのメールをFeedbacksMailboxにリダイレクトするルーティングを1つ指定しました。ルーティングは以下のように正規表現でも指定できます。

class ApplicationMailbox < ActionMailbox::Base
  # routing /something/i => :somewhere
  routing  /feedback\-.+@example.com/i => :feedbacks
end

上の正規表現はfeedback-Ahdhc12@example.comfeedback-5264yYxjg@example.comのようなメールアドレスにマッチします。

次はFeedbacksMailboxを作成します。

> rails g mailbox Feedbacks
#=> Running via Spring preloader in process 623
#=>       create  app/mailboxes/feedbacks_mailbox.rb
#=>       invoke  test_unit
#=>       create    test/mailboxes/feedbacks_mailbox_test.rb

FeedbacksMailboxクラスには、メールを処理するprocessメソッドがあります。このクラスでmailオブジェクトにアクセスできます。

メールを処理する前に何か操作を加えたい場合は、次のようにbefore_processingコールバックででやれます。

class FeedbacksMailbox < ApplicationMailbox
  before_processing :user

  def process
  end

  def user
    @user ||= User.find_by(email: mail.from)
  end
end

mail.fromuserを取れるようになりました。しかし製品へのフィードバックを保存するためにproduct_idが欲しくなります。

product_idを取得するには、返信メールの正規表現を次のようにproduct_idを含む形で指定できます。

  RECIPIENT_FORMAT = /feedback\-(.+)@example.com/i

返信メールがfeedback-1234@example.comの場合、上の正規表現によって1234product_idとして取得できます。

このメールを処理してユーザーからのフィードバックを保存しましょう。

class FeedbacksMailbox < ApplicationMailbox
  RECIPIENT_FORMAT = /feedback\-(.+)@example.com/i

  before_processing :user

  def process
    # フィードバックを作成する
    # mail.decodedはメールがマルチパートでない場合はbodyを返す
    # マルチパートの場合はmail.parts[0].body.decodedを使う
    # ここでは後者がフィードバックを含む
    if mail.parts.present?
      Feedback.create user_id: @user.id, product_id: product_id, content: mail.parts[0].body.decoded
    else
      Feedback.create user_id: @user.id, product_id: product_id, content: mail.decoded
    end
  end

  def user
    @user ||= User.find_by(email: mail.from)
  end

  def product_id
    # recipientsは複数の可能性があるので
    # RECEIPIENT_FORMATにマッチするものを検索

    recipient = mail.recipients.find { |r| RECIPIENT_FORMAT.match?(r) }

    # first_match(つまりproduct_id)を返す
    # 例: recipient = "feedback-1234@example.com"
    # これで1234を返す
    recipient[RECIPIENT_FORMAT, 1]
  end
end

mailオブジェクトにアクセスできるので、マルチパートのメールや添付ファイルありのメールも読み取れます。

development環境でテストする

development環境でテストするなら、http://localhost:3000/rails/conductor/action_mailbox/inbound_emails/newをブラウザで開いて受信メールを配信するだけでやれます。toのメールアドレスに基づいてメールボックスにルーティングされ、メールが処理されます。

production向けの設定

Action Mailboxをproduction環境向けに設定するために、私たちの場合はconfig/environment/production.rbでingressを指定する必要があります。

ingresspostmarkにした場合で考えます。

config.action_mailbox.ingress = :postmark

さらに、Action Mailboxでpostmarkのingressへの認証リクエストに使う強力なパスワードの生成も必要です。私たちの場合、強力なパスワードはingress_passwordに暗号化済みcredentialとして保存する必要があります。

action_mailbox:
  ingress_password: PASSWORD

credentialに保存する代わりに、RAILS_INBOUND_EMAIL_PASSWORD環境変数に保存したパスワードを提供する手もあります。

今度は受信フックを設定して、受信したメールをactionmailboxというユーザー名と先ほど生成したパスワードを用いて/rails/action_mailbox/postmark/inbound_emailsに転送する必要があります。以下は私たちの場合のWebhook URLです。

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

動かせるサンプル

たとえば以下のサービスが使えます。

  1. Sendgrid(メールサービス)
  2. Freenom(ドメイン登録サービス)
  3. ngrok(ローカルWebサーバーを公開できるパブリックURLを提供する)

いずれも無料です。

セットアップ

SendGrid、Freenom、ngrokでアカウントを作ります。ngrokのインストールについてはngrokのガイドに記載の手順をご覧ください。

次はfreenomのドメイン登録リンクで無料ドメインを登録します。検索ボックスにactionmailboxなどのドメイン名を入力して「Check Availability」ボタンを登録します。オプション機能は無料なので好きな機能をオンにします。ナビゲーションバーの「Services > My Domain」セクションにドメインが表示されます。

それが終わったら、このドメインをSendGridで認証する必要があります。認証手順は以下のとおりです。

  1. SendGridの sender_authフォームをクリックする。
  2. 「DNS host」の「Other Host (Not Listed)」を選択し、「DNS Host」にfreenomと入力する。
  3. 「Next」をクリック。
  4. 「From Domain」ボックスにドメイン名を入力する。
  5. 「Next」をクリック。
  6. 「CNAME」レコードのフィールドが3つ表示される(「Freenom」ドメイン管理セクションでこれらを追加する必要あり)。
  7. 「My Domains」セクションの新しいタブで「Freenom」を開く。
  8. 「Manage Freenom DNS」タブをクリック。
  9. ここにCNAMEレコードを3つ入力する必要がある。
  10. MXレコードも作成する(「name」は空欄でもよい)。「type」をMX、「target」をmx.sendgrid.net、「priority」を10にそれぞれ設定。
  11. 「SendGrid」タブに戻ってチェックボックスを確認し、「Verify」をクリック。
  12. 失敗した場合は15〜20分ほど待ってから「Verify」を再度クリック。
  13. sender_auth/domainsリンクを開くと、設定したドメインのステータスがVerifiedになっている。

次は、development.rbに以下のSMTP設定を追加します。

config.action_mailer.smtp_settings = {
  :user_name => SENDGRID_USERANME,
  :password => SENDGRID_PASSWORD,
  :domain => OUR DOMAIN,
  :address => 'smtp.sendgrid.net',
  :port => 587,
  :authentication => :plain,
  :enable_starttls_auto => true
}
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false

rails sを実行してサーバーを起動します。

./ngrok http 3000を実行してブラウザの新しいタブで表示します。

> ./ngrok http 3000
ngrok by @inconshreveable

Session Status                online
Account                       ROMIL MEHTA (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://386e42cd.ngrok.io -> http://localhost:3000
Forwarding                    https://386e42cd.ngrok.io -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

上の「Forwarding」の値を見ると、サーバーのpublic URLが386e42cd.ngrok.ioになっています。この後の例で使いますので、URLをメモしておきます。

今度は、前述の「production向けの設定」で予告した手順を勧めます。

  • 「ingress」にsendgridを追加
config.action_mailbox.ingress = :sendgrid
  • ingressのパスワードを作成してcredentialに追加します。
action_mailbox:
  ingress_password: PASSWORD
  • SendGridの「Inbound Parse」セクションを開いて、「Add Host & URL」をクリックします。

ドメインを選択し、destination URLにhttps://actionmailbox:INGRESS_PASSWORD@SERVER_PUBLIC_URL/rails/action_mailbox/postmark/inbound_emailsを設定して、「POST the raw, full MIME message」チェックボックスをオンにします。

実例

以下を実行して、ユーザーに製品へのフィードバックを促すメイラーを作成します。

> rails g mailer Feedback
Running via Spring preloader in process 48769
      create  app/mailers/feedback_mailer.rb
      invoke  erb
      create    app/views/feedback_mailer
      invoke  test_unit
      create    test/mailers/feedback_mailer_test.rb
      create    test/mailers/previews/feedback_mailer_preview.rb

app/mailers/feedback_mailer.rbに以下のメール送信コードを追加します。

class FeedbackMailer < ApplicationMailer
  default from: FROM_MAIL_ADDRESS

  def send_email
    mail(to: ANY_USERS_EMAIL, reply_to: REPLY_TO_MAIL_ADDRESS, subject: 'Mailbox Test', body: 'Provide feedback for the product by replying to this mail')
  end
end

上のコード例のREPLY_TO_MAIL_ADDRESSにはfeedback-#{PRODUCT_ID}@#{SERVER_PUBLIC_URL}が、PRODUCT_IDには、フィードバックをお願いする製品がそれぞれ入ります。

以下のコマンドを実行すると、フィードバック依頼メールがトリガーされます。

> FeedbackMailer.send_email.deliver_now

上のコマンドを実行後、メールがユーザーに届きます。

feedback mailboxのルーティングは設定済みなので、ユーザーがメールに返信するとFeedbackMailboxprocessメソッドが呼び出されてメールが処理されます。

まとめ

本記事ではAction Mailboxの基礎、インストール方法、実装、設定方法について学びました。また、production環境に近いセットアップ例についても解説いたしました。

関連記事

銀座Rails#14で「出張Railsウォッチ」発表させていただきました


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。