Railsでメール受信に反応してコントローラを起動する

PostfixとRailsで、メール受信に反応してコントローラを起動させるにはどうすればいいか。

基本方針

基本的には、特定のユーザに届いたメールをプログラムへパイプするようPostfixを設定することになる。では何にパイプするか?

本家Rails Guidesではscript/runnerでUserMailer.receive(STDIN.read)を実行するという方法が紹介されているが、

  • メール受信のたびにRailsアプリが起動されるので重い
  • SMTPサーバとRailsのサーバが同一でないといけない

などの欠点があるので、受信メールをRailsのコントーラに受け渡すスクリプト – taslamの日記で紹介されている方法を採用することにした。

スクリプトを軽く書き換え

上記のスクリプトはすごく便利だが、実際にコントローラでparamsを見てみるとメールがバラバラ死体になっているので、予めURLエンコードするように書き換える。

    require 'cgi'

    def proxy(str, options = {})
      start(str, options) do |server, mail|
        data = 'mail='+CGI.escape(mail)
        server.post(data)
      end
    end

Postfixの設定

/etc/aliasesに追記:

username: "| ruby /path/to/script.rb"

編集後に実行(/etc/aliasesの設定を反映させる):

# sudo newaliases

コントローラにて

外部から無理やりPOSTしているため、そのままだとActionController::InvalidAuthenticityTokenが発生する。以下を追記してそれを抑制する。

skip_before_filter :verify_authenticity_token

これでparams[:mail]に生のメールが入っている状態になる。自力でパースするのは面倒だな…と思っていたらTMailという素晴らしいライブラリがあったのでそれを使う。

mail = TMail::Mail.parse(params[:mail])
mail.base64_decode!

Mail.parseから返ってくるのはTMail::Mailオブジェクトなので、あとはTMailのRDocに従ってメールとおしゃべりしよう。基本的には

  • 送信元アドレス:mail['from'].addrs.first.address
  • 宛先アドレス:mail['to'].addrs.first.address
  • 宛先アドレスの@以前:mail['to'].addrs.first.local
  • 件名:mail.subject
  • 本文:mail.body

ぐらいがあれば十分でなかろうか。

余談

Rails Guidesに載っているほうのやり方は、Mailerモデルに処理をやらせるという点がどうも気持ち悪い。リクエストに反応して処理をするのがMVCのうちコントローラの役割なのは明らか。HTTPで来たリクエストはコントローラ、メールで来たリクエストはモデルが引き受けるのは不自然だ。

Categories: HowTo's, Tips and Tricks |Tagged , , | Trackback URL |