Action Mailerのマルチパートメールでファイル形式の優先順位が変わっちゃう件
Action Mailer でマルチパートメールを送るようにしていて、かつ mail
メソッドにブロックを渡す場合は注意が必要です。html 版、text 版の優先順位が意図せず変わってしまう場合があります。
環境
MacOSX Mavericks ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0] rails-3.2.11 actionmailer-3.2.11
そもそもマルチパートメールって?
1通のメール中に複数の形式のメール本文を含められる機能です。
例えば html 形式と text 形式、両方のメール本文を含めれば、html 対応のメール表示ソフトではリッチな html 形式で表示させ、そうでなければ text 形式で表示させる、てことが可能です。
マルチパートメール on Rails
rails でマルチパートメールを送るのは超簡単。mailer のテンプレートとして複数の形式のファイルを用意しておくだけです。
app/views/user_mailer/password_reset.html.erb # html版 app/views/user_mailer/password_reset.text.erb # text版
あとは普通にメールを送信するだけ。
UserMailer.password_reset(user).deliver
送信されるメールの内容はこんな感じ。1本のメールに複数の形式が含まれてるのが分かると思います。
Sent mail to xxxx@skimatalk.com (1786ms) # メールヘッダ Date: Mon, 02 Feb 2015 11:09:00 +0900 From: noreply@skimatalk.com To: xxxx@skimatalk.com Message-ID: <xxxx> Subject: xxxx Mime-Version: 1.0 Content-Type: multipart/alternative; # Content Type でマルチパート指定 boundary="--==_mimepart_54cedc31b283f"; # 各パートの境界となる文字列を定義 charset=UTF-8 Content-Transfer-Encoding: 7bit ----==_mimepart_54cedc31b283f # パート境界1 (text版ここから) Date: Mon, 02 Feb 2015 11:09:00 +0900 # text版メールヘッダ Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: base64 Content-ID: <xxxx> 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 # text版メール本文 (base64 encoded) 44GS44G744GS44G744GS44G744GS44G744GS44G744GS 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 44GS44G744GS44G744GS44G744GS44G744GS44G744GS ----==_mimepart_54cedc31b283f # パート境界2 (html版ここから) Date: Mon, 02 Feb 2015 11:09:00 +0900 # html版メールヘッダ Mime-Version: 1.0 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: base64 Content-ID: <xxxx> 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 # html版メール本文 (base64 encoded) 44GS44G744GS44G744GS44G744GS44G744GS44G744GS 44G744GS44G744GS44G744GS44G744GS44G744GS44G7 44GS44G744GS44G744GS44G744GS44G744GS44G744GS ----==_mimepart_54cedc31b283f-- # パート境界3 (メール末尾)
各形式の優先順位は ActionMailer::Base
の default
メソッドで parts_order
の値をセットすることで指定出来ます。この値はデフォルトで ["text/plain", "text/enriched", "text/html"] となってます(参考)。各形式の内容はこの順番で出力されます(先ほどの例も然り)。
class UserMailer < ActionMailer::Base default parts_order: ["text/plain", "text/enriched", "text/html"] def password_reset(user) ... end end
この順番だと、一番上に出力される text/plain
形式の内容が最優先で表示されそうですが、違います。
RFCに、表示の際の優先順位は逆順とするように定められており、この場合、一番下の text/html
が最優先となります。
この理由もRFCにありました。もっともシンプルな形式を一番上に出力するようにしておけば、MIME非対応のメール表示ソフトであっても読みやすいよね、ということのようです。なるほどちゃん。
placing the plainest alternative first is the friendliest possible option when mutlipart/alternative entities are viewed using a non-MIME- compliant mail reader.
mail メソッドにブロックを渡すと parts_order 指定が無視されちゃう
mail メソッドにはブロックを渡すことが出来ます。下記のように、ファイル形式ごとにレイアウトを指定出来たりします。
class UserMailer < ActionMailer::Base default parts_order: ["text/plain", "text/enriched", "text/html"] def password_reset(user) mail to: 'xx@skimatalk.com', subject: 'hello!' do |format| format.text { render layout: 'mailer' } format.html { render layout: 'mailer' } end end end
これで、
app/views/layouts/mailer.text.erb app/views/layouts/mailer.html.erb
がメールのレイアウトとして使われるって寸法。
が、ここに落とし穴があります。
format.text { render layout: 'mailer' } format.html { render layout: 'mailer' } # html形式が後
この順番を逆にして
format.html { render layout: 'mailer' } format.text { render layout: 'mailer' } # text形式が後
ってしちゃうと、parts_order
での指定を無視して、html 形式が先、text 形式が後に出力されます。
すると RFC のルール通り、text 版の方が優先して表示されちゃいます。html 版は決して表示されなくなります。なんと。
ActionMailer::Base のドキュメントを読んでもそんなこと書いてないですねえ。(該当箇所は "If you want to explicitly render only certain templates..." らへんから)