Hatena::ブログ(Diary)

130単位

2011-10-03

Rails3 ISO-2022-JPでメール送信

先人達の知恵がありますが、別のアプローチを考えついたので記事にしてみます。

※検証したものの運用が不十分なため、問題が起きる可能性がありますので参考にされる場合はご注意ください

環境

要点

  • ActionMailerクラスのdefaultやmailメソッドでcharsetを指定しても、メール本文は変換されない
  • 本文を変換させるには、Mail::Bodyのインスタンスのcharsetを直接指定する

上記の記事ではMailライブラリにモンキーパッチをあてていますが、ActionMailerでも同様に、かつすっきり書くことができます。方法は2通りあります。

オーバーライド

app/mailers/application_mailer.rb (※ファイル名は自由)

#require 'nkf'
class ApplicationMailer < ActionMailer::Base
  default :charset => 'iso-2022-jp' #件名はこれでok

  def mail(headers={}, &block)
    mail = super
    mail.body.charset = 'iso-2022-jp' #本文は直接指定
    #mail.body = NKF.nkf('-w -J', NKF.nkf('-j -W', mail.body.raw_source)) #変換エラー対策 (旧)
    mail.body = mail.body.raw_source.encode('iso-2022-jp', :invalid => :replace, :undef => :replace).encode('utf-8') #変換エラー対策
    mail
  end
end

モンキーパッチ

config/initializers/mailer_fix.rb (※ファイル名は自由)

require 'action_mailer/base'
module ActionMailer
  class Base < AbstractController::Base
    default :charset => 'iso-2022-jp'

    def mail_with_fix(headers={}, &block)
      mail = mail_without_fix(headers, &block)
      mail.body.charset = 'iso-2022-jp'
      mail.body = mail.body.raw_source.encode('iso-2022-jp', :invalid => :replace, :undef => :replace).encode('utf-8')
      mail
    end
    alias_method_chain :mail, :fix
  end
end

変換エラー対策 (追記)

例えばWindowsの「〜」が入っていると、標準のencodeメソッドではうまく変換できません。

Encoding::UndefinedConversionError: U+FF5E to EUC-JP in conversion from UTF-8 to EUC-JP to stateless-ISO-2022-JP to ISO-2022-JP

id:ya_maさんの記事にあるようにNKFを使うのがいいようです。さらにActionMailerだけで完結するには、UTF-8ISO-2022-JPUTF-8という二重の変換を行う必要があります(ISO-2022-JPのままだとActiveSupport#blank?でエラーになる)。

Encoding::CompatibilityError: incompatible encoding regexp match (US-ASCII regexp with ISO-2022-JP string)

さらに追記

丸数字や"はしごだか"は上記では回避できないようです。encodeメソッドのオプションを使う方法がありました。

mail.body = mail.body.raw_source.encode('ISO-2022-JP', :invalid => :replace, :undef => :replace).encode('UTF-8')

留意点としては、機種依存文字が「?」に置き換えられてしまいますので、それを許容する必要があります。

まとめ

mailersの中だけで完結するオーバーライドのほうがわかりやすいと思いますし、複数のMailerクラスがある場合でも継承をうまく使えば1箇所の記述で済みます。ただし、例えばDeviseのようなメール送信を行う他のライブラリへの対応ができません。モンキーパッチだと、ActionMailerの書き換えであるためその点も解消されます。

Appendix

  • mail.transport_encoding = '8bit' とするとBase64エンコードを回避できる
    • これはUTF-8の場合にログでメール本文把握できるというメリットもある
    • via: Rails3レシピブック Recipe158
  • 添付ファイルもmail.parts.eachなどとしてcharsetを指定すればよさげ
  • 最新版Mail 2.3.0でもbodyにcharsetが渡っていないため、同様に使えると思われる
  • 二重の変換が無駄なため、Mail::Body#encoded相当の処理を今回上書きするメソッドに記述したほうがいい気がする

参考リンク


4797363827
Rails3レシピブック 190の技

4797359730
Ruby on RailsによるWebアプリケーション・スーパーサンプル改訂版

satoshisatoshi 2011/10/22 14:02 とても参考になります!ありがとうございます。
ちなみになんですが、Ruby1.9.2、Rails3.1で、パッチとオーバーライド、両方試してみたのですが、エンコーディングをしらべるとUTF-8なんですが、原因ってわかりますかね。。ヒントでもいただけたら嬉しいです><
ex)
hoge = mail to: 'hoge@example.com', subject: t('mail.subject'), template_name: 'hoge'
hoge.charset #ISO-2022-JP
hoge.subject.encoding #UTF-8

deeekideeeki 2011/10/22 17:45 mailメソッドで返されるMail::Messageインスタンスのsubjectには、Rubyコード上で記述した文字列がそのまま入ってるみたいです。
hoge.headerのほうをみてみると、ISO-2022-JPになってると思います。
送信は後者で行われると思いますので、受信したメールのソースも確認してみてください。
ご参考になれば幸いですー

satoshisatoshi 2011/10/22 20:44 なるほど!ヘッダー見れば大丈夫でした!
丁寧にありありがとうございました!

MTGMTG 2011/11/15 10:54 Ruby1.9.3+Rails3.1.1 に旧バージョンから移植中でした。
メール配信の文字化けでで困っていたところへ、このページを見つけました。
このような記事を掲載していただきありがとうございます。
オーバーライドの方法を参考に自分のアプリに導入してみました。
メール本文には有効ですが、メールの題名に文字化けが残ります。
実力不足で、どのように修正を加えたら良いのか分かりません。
ご教授願えないでしょうか?

deeekideeeki 2011/12/04 09:20 レスが遅くなり申し訳ありません。
件名に機種依存文字をつかっている可能性はないでしょうか?
どんな文字をつかっているか教えていただければ、調べてみたいと思います。

MTGMTG 2011/12/06 19:21 機種依存文字を使ってました。
現在は、以下のページで紹介されている方法で、いまのところうまくいっています。
http://www.oiax.jp/rails/zakkan/mail-iso-2022-jp.html

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/deeeki/20111003/rails3_mailer_iso2022jp
リンク元