概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: 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件ずつチェックする
- レジュメをダウンロードする
- レジュメをクラウドにアップロードして、レジュメのテーブルにエントリを作成する
ダルそうな作業ですね。
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も同時に作成されます。
それではUserとProductとFeedbackの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テーブルのスキーマにはstatusやmessage_idやmessage_checksumなどのカラムが含まれます。statusはpending、processing、finished、bouncedのいずれかになります。message_idとmessage_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.comやfeedback-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.fromでuserを取れるようになりました。しかし製品へのフィードバックを保存するためにproduct_idが欲しくなります。
product_idを取得するには、返信メールの正規表現を次のようにproduct_idを含む形で指定できます。
RECIPIENT_FORMAT = /feedback\-(.+)@example.com/i
返信メールがfeedback-1234@example.comの場合、上の正規表現によって1234をproduct_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を指定する必要があります。
ingressをpostmarkにした場合で考えます。
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
動かせるサンプル
たとえば以下のサービスが使えます。
いずれも無料です。
セットアップ
SendGrid、Freenom、ngrokでアカウントを作ります。ngrokのインストールについてはngrokのガイドに記載の手順をご覧ください。
次はfreenomのドメイン登録リンクで無料ドメインを登録します。検索ボックスにactionmailboxなどのドメイン名を入力して「Check Availability」ボタンを登録します。オプション機能は無料なので好きな機能をオンにします。ナビゲーションバーの「Services > My Domain」セクションにドメインが表示されます。
それが終わったら、このドメインをSendGridで認証する必要があります。認証手順は以下のとおりです。
- SendGridの sender_authフォームをクリックする。
- 「DNS host」の「Other Host (Not Listed)」を選択し、「DNS Host」に
freenomと入力する。 - 「Next」をクリック。
- 「From Domain」ボックスにドメイン名を入力する。
- 「Next」をクリック。
- 「CNAME」レコードのフィールドが3つ表示される(「Freenom」ドメイン管理セクションでこれらを追加する必要あり)。
- 「My Domains」セクションの新しいタブで「Freenom」を開く。
- 「Manage Freenom DNS」タブをクリック。
- ここにCNAMEレコードを3つ入力する必要がある。
MXレコードも作成する(「name」は空欄でもよい)。「type」をMX、「target」をmx.sendgrid.net、「priority」を10にそれぞれ設定。- 「SendGrid」タブに戻ってチェックボックスを確認し、「Verify」をクリック。
- 失敗した場合は15〜20分ほど待ってから「Verify」を再度クリック。
- 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のルーティングは設定済みなので、ユーザーがメールに返信するとFeedbackMailboxのprocessメソッドが呼び出されてメールが処理されます。
まとめ
本記事ではAction Mailboxの基礎、インストール方法、実装、設定方法について学びました。また、production環境に近いセットアップ例についても解説いたしました。