imapでメールを受信（Ruby）

# imapでメールを受信したい！
とあるシステムを作るためにRailsでgmailを素のimapで受信することになりました
gmail受信するならgmail api使えばいいじゃないかという意見はごもっともですが、今回それをしない理由が二つあります
1. google api経由だと制限がかかったときに大変
2. gmail以外のメールを使うとなったときにgmail取得のためのロジックになっているため移行できない 

## メール受信に関する記事がなかなか見当たらない
imapでメールを受信するにあたって多くの情報を調べましたが、なかなかこれだというものが見つかりませんでした
世の中にはメール送信の記事はたくさんありますが、受信の記事は少ないみたいです（今回の記事はそういった経緯からも執筆しているのです）
余談ですが、同様のことを[嘆いている方](http://mintblog.hatenablog.com/entry/2016/09/28/224830)がいらっしゃいました
本記事の流れとしては私が想定していたデータを取得するまでに辿った道順に書きますので、受信のためのコードが知りたい人は最後まで読み飛ばしてください

## 環境
- Ruby 2.5.0
- Rails 5.1.6
- CentOS 7.4

# imapでメールを受信できるはずが
Rubyを使ってメールを受信するために[te2u/imap.rb](https://gist.github.com/te2u/5506821)を参考にしました
今回はgmailを利用したので、

```ruby
imap_host = 'imap.gmail.com'
search_criterias = ['ALL'] # 検索条件：今回は全てのメールを受信したかったのでALLに
```

私はまずCentOS上ではなく、macの環境でお試し程度に動かしてみました

```rb
imap.search(search_criterias).each do |msg_id|
    msg = imap.fetch(msg_id, [subject_attr_name, body_attr_name]).first
    subject = msg.attr[subject_attr_name].toutf8.strip
    body = msg.attr[body_attr_name].toutf8.strip
    puts subject # 件名を表示
    puts body # 本文を表示
 end
```

すると見事に件名と本文が表示されていました
あとはCentOS上のRailsで実装すればやりたいことの多くは達成されるはず！
でした…

## Base64とはなんぞや?
実際は、日本文ではなく、よくわからない文字コードに変換されてしまいました
色々調べた結果[Base64](https://ja.wikipedia.org/wiki/Base64)という文字コードで取得されていました
例えば、「私は福岡の大学院生です」は以下のような文字列になるみたいです
`56eB44Gv56aP5bKh44Gu5aSn5a2m6Zmi55Sf44Gn44GZ`

それならばデコードすれば良いではないか！ということでRubyでBase64をデコードできないか調べるとQiitaで[RubyでBase64URLエンコーディング](https://qiita.com/awakia/items/049791daca69120d7035)という記事が出てきたので試してみました
しかし、なぜか変化なし
色々と試しても変わらないので私の上司に助けを求めました
すると、BODY(本文)の一部をBase64でデコードすると元の文章となって取得できることが判明したのでデコードできるはず、とのこと
また、headerがあるせいでデコードできないのでは？との指摘をいただきました
見直してみると

```ruby
Content-Type: text/html; charset="UTF-8" # この部分が
Content-Transfer-Encoding: base64        # 邪魔をしているみたい

56eB44Gv56aP5bKh44Gu5aSn5a2m6Zmi55Sf44Gn44G
```

header、ありますね
すいません、僕の知識不足で全く気付きませんでした

## header削除すれば良さそう
しかし、調べたところこのheaderを判別するのはなかなか厄介みたいです（[参照した記事](http://blog.remora.cx/2010/09/read-imap-mail-with-perl.html)）
しかも、添付ファイルがある場合は今回参照した記事では対応できないパターンがありました

# ここにきていきなりの解決
あれこれ試していると上司から[mikel/mail](https://github.com/mikel/mail)なるライブラリがあるとのこと
imap通信の処理ではないけど、fetchしたメールのパース処理に使えるのでは？と

## imapで受信してmikel/mailでパースすれば解決できる！

今回使用したコードをいかに載せておきます。
記事作成時にはRuby、Rails歴ともに1ヶ月も経っていないのでどんどんご指摘いただけると嬉しいです。

```ruby
require 'net/imap'
require 'kconv'
require 'mail'

# imapに接続
imap_host = 'imap.gmail.com' # imapをgmailのhostに設定する
imap_usessl = true # imapのsslを有効にする
imap_port = 993 # ssl有効なら993、そうでなければ143
imap = Net::IMAP.new(imap_host, imap_port, imap_usessl)
# imapにログイン
imap_user = 'your email address'
imap_passwd = 'email password'
imap.login(imap_user, imap_passwd)

imap.select('INBOX') # 対象のメールボックスを選択
ids = imap.search(['ALL']) # 全てのメールを取得

imap.fetch(ids, "RFC822").each do |mail|
  m = Mail.new(mail.attr["RFC822"])
  # multipartなメールかチェック
  if m.multipart?
    # plantextなメールかチェック
    if m.text_part
      body = m.text_part.decoded
    # htmlなメールかチェック
    elsif m.html_part
      body = m.html_part.decoded
    end
  else
    body = m.body
  end
end
```

`m.multipart`は複数のデータを持ったメールを見分けます
例えば、添付資料がある場合やhtmlメールとテキストメール両方を持った場合などです
これによってメールの本文を取得することができました！
ちなみに、本文以外も色々取得できるので試してみてください！（もしかしたら今後追記するかも）

## 余談
imap.fetchの第二引数を配列にして複合条件にするとなぜか取得できない（時間がかかる？）状況に出会いました
→引数を単体にして別々に取得すれば対応できた

```ruby
msg = imap.fetch(msg_id, [subject_attr_name, from_attr_name, to_attr_name]).first
```

ではなく、

```ruby
msg = imap.fetch(msg_id, 'BODY[TEXT]')
```

とすることで解決した、が、mikel/mailライブラリ使ったので結局この処理は使っていないです


imap.fetchする数が大きいとロックがかかる
→fetchする数を分けて対応した

```ruby
ids = imap.search(['ALL'])
ids.each_slice(100).to_a.each do |id_block| # 100件ごとにメールをfetchする
    imap.fetch(id_block, "RFC822").each do |mail|
        # なんか色々処理する
    end
end
```

mikel/mailライブラリをgemでインストールすると、たまたま今回作っていたMailモデルと競合してしまいました
→結局、開発初期ということもあり自作のMailモデルの名称を変更して対応しました

# おわりに
結局メールを受信する処理を作るために丸２日かけてしまいました
まだまだ未熟者ですね
今回初版（2018年4月2日時点）では、忘れて書ききれていない事もあるかもしれないので追記する可能性高いです。
なので、ご指摘いただけたら対応します