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で来たリクエストはコントローラ、メールで来たリクエストはモデルが引き受けるのは不自然だ。