Rails

【Rails】受信したメールを処理することができる機能「ActionMailbox」を使ってみる

Rails

 

今回はRails6から導入された受信メールを処理することができる機能「ActionMailbox」を使ってみたので簡単にまとめてみました。メール送信処理にはActionMailerという機能がありますがメール受信処理に関する機能はなく、Rails6から入った機能がこのActionMailboxになります。

Rails
【Rails】ActionMailerを用いたメール送信機能の実装手順を初心者向けにまとめてみたAction Mailerを用いたメール送信機能の実装手順についてまとめています。Action MailerはRailsに標準で組み込まれており、アプリケーションのメイラークラスやビューでメールを送信することが可能な便利機能です。Railsでのメール送信機能の実装に役立てば幸いです。...

ActionMailboxとは

ActionMailboxとは、受信したメールをRailsアプリケーション側で処理することができる機能です。この機能はRails6から導入されました。

メール送信に関する処理はActionMailerを用いて実装できていましたが、メール受信に関する処理は持ち合わせていませんでした。そこでRails6から導入されたのがこのActionMailboxになります。

ActionMailboxは、以下のような主要メール配信サービスと連携することが可能です。

ActionMailboxを使ってみる

実際に開発環境でActionMailboxを実装してみます。

今回はメールを返信することでコメントを返すことができる機能を実装してみます。(このアプリケーションの元ネタは以下になります。)

参照: How to use Action Mailbox in Rails 6

新規アプリケーションの作成

以下のコマンドで新規アプリケーションを作成します。

% rails new action-mailbox-app -d mysql
% cd action-mailbox-app
% rails db:create
  • Ruby: 3.1.2
  • Rails: 7.0.4
  • DB: MySQL

ActionMailboxのインストール

% rails action_mailbox:installコマンドを打ち込むことで、設定ファイルであるapplication_mailbox.rbとマイグレーションファイルが生成されます。

% rails action_mailbox:install
Copying application_mailbox.rb to app/mailboxes
      create  app/mailboxes/application_mailbox.rb
       rails  railties:install:migrations FROM=active_storage,action_mailbox

Scaffoldで雛形を作成

Scaffoldの機能を用いて、User・Discussion・Commentに関する雛形を作成します。同時にマイグレートも行います。

% rails g scaffold User name email
% rails g scaffold Discussion title
% rails g scaffold Comment user:references discussion:references body:text
% rails db:migrate

rails db:migrate時に以下のようなエラーが発生した場合、database.ymlのencodingをutf8に編集することでエラーは発生しなくなります。

ActiveRecord::StatementInvalid: Mysql2::Error: Specified key was too long; max key length is 767 bytes
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

↓ 以下のように変更

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock
Rails
【Rails】秒速でアプリケーション開発できるscaffoldの使い方を簡単にまとめてみた手っ取り早くアプリケーションを開発することができるscaffold(スキャフォールド)の使い方について簡単に解説しています。新しい機能実装をテストしてみたい、すぐにでもアプリケーションを立ち上げたいといった場合に便利な機能なので、ぜひ覚えておきましょう。...

 

マイグレート後のUser・Discussion・Commentに関するスキーマ(schema.rb)は以下のようになっています。

create_table "comments", charset: "utf8", force: :cascade do |t|
  t.bigint "user_id", null: false
  t.bigint "discussion_id", null: false
  t.text "body"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["discussion_id"], name: "index_comments_on_discussion_id"
  t.index ["user_id"], name: "index_comments_on_user_id"
end

create_table "discussions", charset: "utf8", force: :cascade do |t|
  t.string "title"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

create_table "users", charset: "utf8", force: :cascade do |t|
  t.string "name"
  t.string "email"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

ルーティングの設定

application_mailbox.rbを以下のように編集します。

class ApplicationMailbox < ActionMailbox::Base
  routing /reply-(.+)@reply.example.com/i => :replies
end

 

これにより、受信したメールアドレスURLがreply-ADJXWIDJ@reply.example.comのような形式だった場合にreplies_mailbox.rbに処理が移行するようになります。

application_mailbox.rbには受信メールのルーティングを記載します。

application_mailbox.rbが以下のような記述で、受信したメールアドレスURLが/somethingだった場合、somewhere_mailbox.rbへ処理が移行します。

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

 

application_mailbox.rbが以下のような記述だった場合、メールを受信するとall_mailbox.rbへ処理が移行します。

class ApplicationMailbox < ActionMailbox::Base
  routing :all => :all
end

 

application_mailbox.rbが以下のような記述で、受信したメールアドレスURLがreply+AsdsSDa@reply.github.comのような形式だった場合、replies_mailbox.rbへ処理が移行します。

class ApplicationMailbox < ActionMailbox::Base
  routing /reply\+.+@reply.github.com/i => :replies
end
正規表現チェッカー

参考: Rubular

RepliesMailboxの作成と編集

RepliesMailboxを以下のコマンドで作成します。

% rails g mailbox Replies
  create  app/mailboxes/replies_mailbox.rb
  invoke  test_unit
  create    test/mailboxes/replies_mailbox_test.rb

 

作成されたRepliesMailboxを以下のように編集します。

# replies_mailbox.rb

class RepliesMailbox < ApplicationMailbox
  def process
    return if user.nil?
  end

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

  def discussion
    @discussion = Discussion.find(discussion_id)
  end

  def discussion_id
  end
end

メールを受信するとRepliesMailboxのprocessメソッドが呼ばれ、この中でメール受信時の処理が行われます。

class RepliesMailbox < ApplicationMailbox
  def process
  end
end

 

processメソッドの中ではユーザーを検索し、ユーザーがいなければreturnしています。(mail.fromでメール送信者のメールアドレスを取得することができます。)

class RepliesMailbox < ApplicationMailbox
  def process
    return if user.nil?
  end

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

 

discussiondiscussion_idメソッドは後に使用します。

class RepliesMailbox < ApplicationMailbox
  def discussion
    @discussion = Discussion.find(discussion_id)
  end

  def discussion_id
  end
end

ルーティングを定数として定義

application_mailbox.rbに定義したルーティング(正規表現の部分)を、定数としてreplies_mailbox.rbに定義します。

class ApplicationMailbox < ActionMailbox::Base
  routing RepliesMailbox::MATCHER => :replies
end
class RepliesMailbox < ApplicationMailbox
  MATCHER = /reply-(.+)@reply.example.com/i

  def process
    return if user.nil?
  end

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

  def discussion
    @discussion = Discussion.find(discussion_id)
  end

  def discussion_id
  end
end

discussion_idを設定

replies_mailbox.rbを以下のように編集します。

class RepliesMailbox < ApplicationMailbox
  MATCHER = /reply-(.+)@reply.example.com/i

  def process
    return if user.nil?
  end

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

  def discussion
    @discussion = Discussion.find(discussion_id)
  end

  def discussion_id
    recipient = mail.recipients.find{ |r| MATCHER.match?(r) }
    recipient[MATCHER, 1]
  end
end

メール情報は以下のようなメソッドを使用して取得することができます。

mail = Mail.read('/path/to/message.eml')

mail.envelope_from   #=> 'mikel@test.lindsaar.net'
mail.from.addresses  #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
mail.sender.address  #=> 'mikel@test.lindsaar.net'
mail.to              #=> 'bob@test.lindsaar.net'
mail.cc              #=> 'sam@test.lindsaar.net'
mail.subject         #=> "This is the subject"
mail.date.to_s       #=> '21 Nov 1997 09:55:06 -0600'
mail.message_id      #=> '<4D6AA7EB.6490534@xxx.xxx>'
mail.decoded         #=> 'This is the body of the email...

参考: https://github.com/mikel/mail

アソシエーションの定義

DiscussionとCommentは1対多の関係になるため、アソシエーションを定義します。

class Discussion < ApplicationRecord
  has_many :comments
end

Discussionに紐づくCommentを作成

replies_mailbox.rbを以下のように編集します。

class RepliesMailbox < ApplicationMailbox
  MATCHER = /reply-(.+)@reply.example.com/i

  def process
    return if user.nil?

    discussion.comments.create(
      user: user,
      body: mail.decoded
    )
  end

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

  def discussion
    @discussion = Discussion.find(discussion_id)
  end

  def discussion_id
    recipient = mail.recipients.find{ |r| MATCHER.match?(r) }
    recipient[MATCHER, 1]
  end
end

mail.recipientsで受信メールのTO(宛先)を配列で受け取ることができます。

def discussion_id
  recipient = mail.recipients.find{ |r| MATCHER.match?(r) }
  recipient[MATCHER, 1]
end
mail.recipients
=> ["reply-1@reply.example.com"]

 

これにより、メールを受信し該当ユーザーが見つかった場合はコメントを返すことができるようになります。

テストデータの作成

サーバーを起動し、UserとDiscussionに関するテストデータを手動で作成します。

% rails s

 

http://localhost:3000/users/newhttp://localhost:3000/discussions/newにアクセスし、それぞれテストデータを1つずつ作成しましょう。

Userの作成
Image from Gyazo
Discussionの作成
Image from Gyazo

ビューファイルの修正

app/views/discussions/show.html.erbを以下のように編集しましょう。

<p style="color: green"><%= notice %></p>

<%= render @discussion %>

<div>
  <%= link_to "Edit this discussion", edit_discussion_path(@discussion) %> |
  <%= link_to "Back to discussions", discussions_path %>

  <%= button_to "Destroy this discussion", @discussion, method: :delete %>
</div>

<h4>Comments</h4>
<%= render @discussion.comments %>

 

app/views/comments/_comment.html.erbを以下のように編集します。

<div id="<%= dom_id comment %>">
  <p>
    <strong>User:</strong>
    <%= comment.user.name %>
  </p>

  <p>
    <strong>Body:</strong>
    <%= comment.body %>
  </p>
</div>

メール受信のテスト

開発環境ではメール受信が行われるとコンダクター(conductor)コントローラがマウントされるようになっています。

コンダクターコントローラは、システム内にあるすべてのInboundEmailsのインデックスや処理のステートを提供し、新しいInboundEmailを作成するときのフォームも提供します。

参考: Action Mailboxをdevelopment環境で使う

 

http://localhost:3000/rails/conductor/action_mailbox/inbound_emails/newにアクセスすることでメール入力フォームが表示され、メール受信時の挙動を確認することができるためアクセスしてみましょう。

メール受信テスト

 

From/To/Subject/Bodyに値を入力し、ページ下部にある送信ボタン(Deliver inbound email)をクリックします。

メール受信テスト
reply-(.+)@reply.example.com/i形式を設定したため、reply-1@reply.example.comのようなメールアドレスをToに打ち込みましょう。

 

メールが送信されると以下のようなページが表示されます。

メール受信テスト
Full email sourceには受信したメールの内容が記載されます。

 

http://localhost:3000/discussions/1にアクセスすることで、メール受信時のユーザー名とメール本文がコメントに表示されていることが確認できます。

受信メールの内容

まとめ

  • ActionMailboxとは、受信したメールをRailsアプリケーションで処理することができる機能でRails6から導入された
  • ActionMailerはメール送信処理、ActionMailboxはメール受信処理を担う
  • 開発環境ではメール受信が行われるとコンダクター(conductor)コントローラがマウントされる

参考

 

今回はRails6から導入された受信したメールを処理することができる機能「ActionMailbox」について簡単にまとめました。メール送信のActionMailer同様、簡単にメール受信に関する実装ができたのでメール周りの実装はこれで大丈夫そうですね。