Rubyで日本語のメールを送る。

なるべく少ないライブラリで正しい日本語のメールを送る方法について。

結論

先に結論から。

require "nkf"
require "net/smtp"

def sendmail(from, to, subject, body, host = "localhost", port = 25)
  body = <<EOT
From: #{from}
To: #{to.to_a.join(",\n ")}
Subject: #{NKF.nkf("-WMm0", subject)}
Date: #{Time::now.strftime("%a, %d %b %Y %X %z")}
Mime-Version: 1.0
Content-Type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit

#{NKF.nkf("-Wjm0", body)}
EOT

  Net::SMTP.start(host, port) do |smtp|
    smtp.send_mail body, from, to
  end
end

ポイント

  • nkf
    • Subjectは-Mオプションで出力する。自動で折り返してくれるので。自分でpack('m')などとしない。
    • MIMEデコードをしないように、-m0オプションをつける。
    • 入力の自動判別をしないように、-Wオプションをつける。(W:UTF-8 / E:EUC-JP / S:SJIS)
  • 最低限3つのMIMEヘッダをつける。
    • Mime-Version
    • Content-Type
    • Content-Transfer-Encoding
  • 改行をCR+LFに揃える処理は不要。net/smtpのsend_mailのコメントに変換すると書いてある。
    • Single CR ("\r") and LF ("\n") found in the +msgstr+, are converted into the CR LF pair.
  • RailsではActionMailer+GetTextを使うが、ちょっとメールを送るだけの用途には大げさだと思う。
  • gem版のTMailを使う場合はTMail.KCODE="EUC"と設定した上で、日本語をEUCで渡す必要がある。

前提

素のメールの仕様はRFC 5322。US-ASCIIしか使えない。

素メールを拡張するのがMIME(Multipurpose Internet Mail Extension)。RFC 2045〜2049。

MIMEについての記事。

ヘッダで非ASCII文字を使うにはRFC 2047。=?ISO-2022-JP?B?xxxxx?=みたいなやつ。「MIMEエンコード」「MIME Bエンコード」などと呼ばれる。

このエンコードは難しい。Perlメモが詳しい。

nkfはこいつを自動的にデコードしてくれる(してしまう)。デコードを抑制するため-m0をつけるよう心がける。

$ echo -n 'abc' | nkf -MB
YWJj
$ echo -n '=?ISO-2022-JP?B?YWJj?=' | nkf
abc
$ echo -n '=?ISO-2022-JP?B?YWJj?=' | nkf -m0
=?ISO-2022-JP?B?YWJj?=

一般に、まともに外に出せるメールをつくるなら

1) To:、Subject:、Date:、Mime-Version:、Content-Type: をつける
2) ヘッダにある mail safe でない部分を B エンコード
3) 必要なら本体を Base64 エンコード
4) できればヘッダの文法チェック

は最低でも行う必要があります。

[ruby-list:22157] Re: net/smtp

Rubyでメールを送る。

Rubyで日本語のメールを送るにはどうすればいいのか。US-ASCIIだけでいいなら簡単だ。

require "net/smtp"

from = "from@example.com"
to   = "to@example.com"

body = <<EOT
From: #{from}
To: #{to}
Subject: test
Date: #{Time::now.strftime("%a, %d %b %Y %X %z")}

Hi,

This is US-ASCII mail test.
EOT

Net::SMTP.start("localhost", 25) {|smtp| smtp.send_mail body, from, to }

これにMIMEヘッダと日本語の処理を追加すると、

これでも一応送れる。が、Perlメモにあるとおり、Subjectのエンコードがこのままでは駄目。

TMailの古いバージョンを使ってみる。

TMailにはMIME Bエンコーダ・デコーダが含まれている。

古いTMailは$KCODE='e'などとしてEUCを設定すると件名をMIME Bエンコードできる。以下から0.10.8をダウンロード。

#! /usr/bin/env ruby

$KCODE = 'e'

$:.unshift("./tmail-0.10.8/lib")
# require "rubygems"

require "tmail"

mail = TMail::Mail.new
mail.subject = "テスト"

puts mail.encoded
# -> Subject: =?iso-2022-jp?B?GyRCJUYlOSVIGyhC?=

TMailの最近のバージョンを使う。

TMailは2007年からメンテナが変わっている。

gemでインストールした新しいTMailで同じ事をしてもうまくいかない。

$ sudo gem install tmail

もうちょっとパラメータを足してみるが、やはりエンコードされない。

#! /usr/bin/env ruby

require "rubygems"
require "tmail"

mail = TMail::Mail.new

mail.date = Time.now
mail.mime_version = "1.0"
mail.set_content_type "text", "plain", {"charset"=>"iso-2022-jp"}
mail.transfer_encoding = "7bit"
mail.message_id = TMail.new_message_id("example.com")

mail.to = "test@example.com"
mail.from = "from@example.com"
mail.subject = ""
mail.body = "テストメールです。"

puts mail.encoded

うーん……と悩んでソースを見ると、TMail.KCODEを設定すればいいことがわかった。

#! /usr/bin/env ruby

require "rubygems"
require "tmail"

TMail.KCODE='EUC'

mail = TMail::Mail.new

mail.subject = NKF.nkf("-We", "")
puts mail.encoded

mail.subject = NKF.nkf("-We", "ASCII 日本語 ASCIIと日本語 ASCII ASCII あいうえおあいうえおあいうえお\n")
puts mail.encoded

出力はこんな感じ。ちゃんと分割されているみたい。

Subject: =?iso-2022-jp?B?GyRCTmMbKEI=?=

Subject: ASCII =?iso-2022-jp?B?GyRCRnxLXDhsGyhCIEFTQ0lJGyRCJEhGfEtcOGwbKEI=?=
        ASCII ASCII =?iso-2022-jp?B?GyRCJCIkJCQmJCgkKiQiJCQkJiQoJCokIiQkJCYbKEI=?=
        =?iso-2022-jp?B?GyRCJCgkKhsoQg==?=

nkfでデコードできるか確認。

$ ruby ./encode_test.rb | nkf
Subject: 例

Subject: ASCII 日本語 ASCIIと日本語 ASCII ASCII あいうえおあいうえおあいうえお

うまくいった。うまくいったけど、、。うーん。。EUCに変換する意味は全くないし、件名のエンコードだけならわざわざTMailを使う必要もないような……。スッキリしない。

nkf -M を使う。

puts NKF.nkf("-M", "ASCII 日本語 ASCIIと日本語 ASCII ASCII あいうえおあいうえおあいうえお")

ASCII =?ISO-2022-JP?B?GyRCRnxLXDhsGyhCIEFTQ0lJGyRCJEhGfEtcOGwbKEI=?= ASCII
 ASCII =?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKiQiJCQkJiQoJCokIiQkJCYkKCQqGyhC?=

こうかー!-MはASCIIだけ入力して、何も起こらないなーと思ってた。頭悪い。

GmailSMTPサーバを使う。

smtp.gmail.comを使って送信する方法。

を参考に。

Ruby 1.8のNet::SMTPはSTARTTLSに対応していないが、gemで解決する。素晴らしい。

sudo gem install tlsmail

で、

require "rubygems"
require "tlsmail"

とした上で、

  Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
  Net::SMTP.start("smtp.gmail.com", 587, "localhost.localdomain", gmail_user, gmail_pass, "plain") do |smtp|
    smtp.send_mail body, from, to  
  end

で送信できた。

の sendgmail.rb を参照。



まだ追記するかも。