Heroku+Rails+Mandrillでメールを受信してデータを作成してみた

### はじめに
メール送信によりデータを投稿してみたいことあると思います。

mandrillを利用すれば簡単に実現できたので今回はサンプルプロジェクトを作りつつ覚書がてら綴っておこうと思います。

### サンプルプロジェクトの概要

- ユーザー登録が必要なコメントが出来る掲示板アプリ
- 特定のメールに送信すると新しい掲示板を作成できる
- 自分の作成した掲示板にコメント通知メールが届く
- コメント通知メールに返信すると該当の掲示板にコメントできる

### サンプルプロジェクトのER図
![スクリーンショット 2014-12-21 16.52.57.png](https://qiita-image-store.s3.amazonaws.com/0/4450/d73892e9-7ab2-35a7-1b6c-da8b6c437e1b.png)
boardにもったtokenはコメント通知返信時にどのboardか特定するのに利用する感じ。

### まずはメール関連以外を作成
deviseとbootstrapを利用してサクっと作成。
この時点のコードは[こちら](https://github.com/LuckOfWise/mail-board/tree/7a07e1159ceb1ce8fc1208bb1342efd9231105d8)。

### メールを受信して掲示板を作成できるようにする

- mandrillのgemを追加

```rb:Gemfile
gem 'mandrill-rails'
```

- [evendis/mandrill-rails](https://github.com/evendis/mandrill-rails)を参考にinbound用の処理を追加


```rb:models/board.rb
class Board < ActiveRecord::Base
  belongs_to :user
  has_many :comments, dependent: :destroy

  validates :title, presence: true
  validates :content, presence: true

  # 以下のメソッドを追加
  def self.save_inbound_mail!(event_payload)
    user = User.find_by!(email: event_payload.sender_email)
    user.boards.create!(title: event_payload.subject, content: event_payload.message_body(:text))
  end
end

```

```rb:controller/inbox_controller.rb
class InboxController < ApplicationController
  include Mandrill::Rails::WebHookProcessor

  skip_before_action :authenticate_user!

  def handle_inbound(event_payload)
    Board.save_inbound_mail!(event_payload)
  end
end

```

```rb:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resource :inbox, controller: :inbox, only: %i(show create) # 追加
  resources :boards, except: %i(edit update) do
    resources :comments, only: %i(create destroy)
  end
  root 'boards#index'
end

```

ここまで書いて一度herokuへdeploy。
mandrillで設定時に作成したアクションへアクセスできるかチェックするため。

- mandrillの設定

1. herokuからmandrillへアクセス
![mail-board_·_Resources___Heroku.png](https://qiita-image-store.s3.amazonaws.com/0/4450/71e185f7-1600-0c6e-3abc-e0de8f327803.png)

2. inboundの設定へ
![Transactional_Reports___Mandrill.png](https://qiita-image-store.s3.amazonaws.com/0/4450/f6d27e69-9aa6-5bd2-63f2-369b80705469.png)

3. 受信するメールのドメインを追加
![Inbound_Domains___Mandrill1.png](https://qiita-image-store.s3.amazonaws.com/0/4450/81711d15-0eb1-f995-0689-c5038929bd9c.png)

4. DNSの設定
![Inbound_Domains___Mandrill2.png](https://qiita-image-store.s3.amazonaws.com/0/4450/b6582bd3-c8c2-c1b5-280c-fa41d475a68b.png)![Inbound_Domains___Mandrill3.png](https://qiita-image-store.s3.amazonaws.com/0/4450/79d68123-e960-7846-42f7-83ab63fd1fb7.png)
Route53の場合はこんな感じでMXレコードを追加。
![Route_53_Management_Console.png](https://qiita-image-store.s3.amazonaws.com/0/4450/0a6fdaa4-c40b-a4c9-1706-9a91159bba9b.png)
value-dominで同様の設定をしようとしたら上手くいかなかった。
原因は深く調べてないけどルートのMXレコードがあるとサブドメインのMXレコードをうまく設定できないのかな。。。

5. 受信メールの設定へ
![Inbound_Domains___Mandrill4.png](https://qiita-image-store.s3.amazonaws.com/0/4450/68917d7b-a8c8-5944-1068-b86cbc236349.png)

6. Routeを追加
![Mailbox_Routes_for_mail-board_luckofwise_com___Mandrill5.png](https://qiita-image-store.s3.amazonaws.com/0/4450/38c2104d-fbf2-66c5-9177-156178e57e4a.png)![Mailbox_Route_for_mail-board_luckofwise_com___Mandrill7.png](https://qiita-image-store.s3.amazonaws.com/0/4450/2e2441af-cb2e-8193-7fe4-7644204ef106.png)
今回は board@mail-board.luckofwise.com にメールを送信すると掲示板を作成するように設定。
Post To URLへアクセス出来ないと設定できないので予めアプリをデプロイしておく必要があります。

これでMandrillの設定は完了。
board@mail-board.luckofwise.com にメールを送信すると掲示板を作成するようになりました。
この時点のコードは[こちら](https://github.com/LuckOfWise/mail-board/tree/67cab2a8797737b0b318caf160c5428b69eec70f)。

### コメント通知メールを送信するようにする
次に作成した掲示板にコメントが投稿されると作成者に通知メールが送信されるように改修します。

```rb:app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
  default from: 'no-reply@' + Rails.application.secrets.mail_domain_name

  def comment_created(comment)
    @comment = comment
    mail to: comment.board.user.email, subject: '新しいコメントがつきました'
  end
end
```

```haml:app/views/user_mailer/comment_created.html.haml
%p 「#{@comment.board.title}」にコメントがつきました。

%div{style: 'border: 1px dashed gray; padding: 0 10px; margin-bottom: 20px;'}
  %p #{@comment.user.name} #{l @comment.created_at}

  = simple_format(@comment.content)

= link_to board_url(@comment.board), board_url(@comment.board)
```

```rb:app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_board

  def create
    @comment = @board.comments.build(comment_params)
    @comment.user = current_user
    if @comment.save
      # 以下の行を追加
      UserMailer.comment_created(@comment).deliver! unless @comment.user == @comment.board.user
      redirect_to @board, notice: 'コメントしました。'
    else
      render 'boards/show'
    end
  end
  略
end
```
ここは特に変わったことしてないです。
この時点のコードは[こちら](https://github.com/LuckOfWise/mail-board/tree/202cddb4645b868428c70d00b28abebd023e1d2d)。


### コメント通知メールに返信でコメントできるようにする

- メールからboardを特定するためのtokenをセット

今までに作ったboardはよしなにセットするか作り直すかしてください。

```rb:Gemfile
gem 'uuidtools' #追加
```

```rb:app/models/board.rb
class Board < ActiveRecord::Base
  略
  # 以下を追加
  before_create :generate_token
  def generate_token
    self.token = UUIDTools::UUID.random_create.to_s
  end
  略
end
```

- コメント投稿用のinboundの処理を追加

```rb:app/models/comments.rb
class Comment < ActiveRecord::Base
  略
  # 以下を追加
  def self.save_inbound_mail!(event_payload)
    user = User.find_by!(email: event_payload.sender_email)
    board = Board.find_by!(token: event_payload.recipient_emails.first.split('@').first[/^.*\+(.*)$/, 1])
    board.comments.create!(user: user, content: event_payload.message_body(:text))
  end
end
```

```rb:app/controllers/inbox/comments_controller.rb
class Inbox::CommentsController < ApplicationController
  include Mandrill::Rails::WebHookProcessor

  skip_before_action :authenticate_user!

  def handle_inbound(event_payload)
    Comment.save_inbound_mail!(event_payload)
  end
end
```

```rb:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  namespace :inbox do
    resource :boards, only: %i(show create)
    resource :comments, only: %i(show create)
  end
  resources :boards, except: %i(edit update) do
    resources :comments, only: %i(create destroy)
  end
  root 'boards#index'
end
```

メール受信用のコントローラーが複数になるので構成を少し変更。

- コメント通知メールにreply_toを設定

```rb:app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
  default from: 'no-reply@' + Rails.application.secrets.mail_domain_name

  def comment_created(comment)
    @comment = comment
    mail to: comment.board.user.email, subject: '新しいコメントがつきました',
         reply_to: "comment+#{comment.board.token}@#{Rails.application.secrets.mail_domain_name}"
  end
end
```

- mandrillにコメント投稿用の設定を追加
![Mailbox_Routes_for_mail-board_luckofwise_com___Mandrill.png](https://qiita-image-store.s3.amazonaws.com/0/4450/850bdbe7-f762-18c0-41d2-153ac0ed2a08.png)
![Mailbox_Route_for_mail-board_luckofwise_com___Mandrill１.png](https://qiita-image-store.s3.amazonaws.com/0/4450/2906e79f-dcc1-438f-c5ed-0ffd87727cf6.png)

これでコメント通知メールに返信してコメントを投稿することができます。
これでサンプルアプリは完成です。
この時点のコードは[こちら](https://github.com/LuckOfWise/mail-board/tree/202cddb4645b868428c70d00b28abebd023e1d2d)。

### その他
HerokuにはMandrill以外にメール受信でpostしてくれるAddonesは存在します。
[CloudMailIn](https://addons.heroku.com/cloudmailin)を利用するとドメインをもっていなくてもCloudMailInが受信用のメールアドレスを用意してくれるので気軽に試せるかと思います。

### おわりに
比較的簡単にメールを送信することによりデータを登録することが出来たと思います。
Herokuのアドオンには便利なのがいっぱいありますねー。
今回作ったサンプルプロジェクトはこちらで動作の確認ができます。
https://mail-board.herokuapp.com/
（そのうち消すかもしれないですが。。。）
今回作ったコード
https://github.com/LuckOfWise/mail-board