【Perl】文字化けしないメールの送り方


Gmail の SMTP サーバを使ってメールを送るのに四苦八苦したメモ。次のサイトを参考にして書いてみた。

Gmail の SMTP を利用して Perl からメール送信(添付ファイル付) – memo.mzt
http://d.hatena.ne.jp/mzt/20080219/p1

Encode モジュールでの MIME Encode
http://www.ksknet.net/perl/encodemime_enco.html

ただ送るだけなら割と簡単なのだが、文字化けを防いだり、第三、第四水準といった最新の文字セットを含めようとすると結構複雑。この本が大変参考になった。

プログラマのための文字コード技術入門
http://www.amazon.co.jp/dp/477414164X/

最終的な完成型はこれだ1

ダウンロード

#!/usr/bin/perl
use utf8;
use strict;
use warnings;
use feature qw! say !;
use Encode;
use MIME::Entity;
use Net::SMTP::SSL;

# SMTPサーバの設定
my %smtp = (
    Server => "smtp.gmail.com",
    Port => "465",
    User => 'user@remora.cx',
    Password => "パスワード",
);

# メールヘッダの設定
my %mail = (
    From => '送信元 <from@remora.cx>',
    To => '宛先 <to@remora.cx>',
    Subject => "テスト件名",
);

# ヘッダをB符号化
$_ = encode( "MIME-Header-ISO_2022_JP", $_ ) for values %mail;

# 文字コードを指定
$mail{Type} = "text/plain; charset=utf-8";
# Base64符号化
$mail{Encoding} = "base64";

# メールの本文
$mail{Data} = <<EOD;
テスト本文
丸数字その1 : ①~⑩
丸数字その2 : ㊵㊶㊷㊸㊹㊺
BMP外の文字 :
    𡚴(山/女 U+216B4)
    𣘺(木+高の変形 U+2363A)
EOD

# メールを作成
my $mime = MIME::Entity->build( %mail );
say $mime->stringify;

# メールを送信
my $s = Net::SMTP::SSL->new( $smtp{Server}, Port => $smtp{Port} );
$s->auth( $smtp{User}, $smtp{Password} );
$s->mail( $mail{From} );
$s->to( $mail{To} );
$s->data;
$s->datasend( $mime->stringify );
$s->dataend;
$s->quit;
$ perl testGmail.pl
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: base64
MIME-Version: 1.0
X-Mailer: MIME-tools 5.428 (Entity 5.428)
From: =?ISO-2022-JP?B?GyRCQXc/Ljg1GyhC?= <from@remora.cx>
To: =?ISO-2022-JP?B?GyRCMDhAaBsoQg==?= <to@remora.cx>
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIN29MPhsoQg==?=

44OG44K544OI5pys5paHCuS4uOaVsOWtl+OBneOBru+8kSA6IOKRoO+9nuKR
qQrkuLjmlbDlrZfjgZ3jga7vvJIgOiDjirXjirbjirfjirjjirnjiroKQk1Q
5aSW44Gu5paH5a2XIDoKICAgIPChmrTvvIjlsbEv5aWzIFUrMjE2QjTvvIkK
ICAgIPCjmLrvvIjmnKgr6auY44Gu5aSJ5b2iIFUrMjM2M0HvvIkK

Outlook 2007 での表示例

Y.Oz Vox で配布されている YOzFontNF フォントを使った。

2010-07-13_115023.png

以下、今回勉強した点を纏めてみる。

1. ヘッダを MIME エンコードする

$header = encode( "MIME-Header-ISO_2022_JP", $utf8 );

送信元、宛先、件名に ASCII 以外の文字を使うときは ISO-2022-JP にエンコードした上で B 符号化することが望ましい。それにはエンコーディング名として MIME-Header-ISO_2022_JP を指定する必要がある2

Encode::MIME::Header – search.cpan.org
http://search.cpan.org/~dankogai/Encode-2.39/lib/Encode/MIME/Header.pm

2. メール本文のエンコード方法

メール本文に ASCII 以外の文字を使うときは決められた方式でエンコードしないといけない。Gmail その他のウェブメールは割合融通が利くように作られているのだが、古くからのメーラーは RFC に則った実装しかされていない場合がほとんどだ。正しいメールの送り方としては次の 2 通りが考えられる。

(1) ISO-2022-JP でエンコードする

一番単純で古くから使われているやり方だ。Content-Type ヘッダに文字コードとして ISO-2022-JP を指定してやればよい。元来メール本文は 7bit のエンコーディング(日本語なら ISO-2022-JP)しか想定されていないので、特に指定がなければこの方式だとみなされる3

Content-Type: text/plain; charset=iso-2022-jp

メール本文

これはどんなメーラーでも 100% 通じる方式なのだが、ISO-2022-JP というのはサポートしている文字が少ない。第三、第四水準の漢字は勿論、丸数字のような一般的な文字さえ使えないのだ4

(2) UTF-8 でエンコードする

そこで Unicode を使う。これなら今利用されているメーラーのほとんどが対応可能である。だが、8bit エンコーディングである UTF-8 を使うならばちょっとした手順が必要だ。

Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

44Oh44O844Or5pys5paH

Content-Transfer-Encoding ヘッダを指定し、メール本文を Base64 符号化している。後は Unicode に対応したフォントさえあれば良いわけだ。

もっとも、Base64 符号化は必須ではない。次のようにしても多くのメーラーはきちんと表示してくれる。

Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit

メール本文

以下、参考書籍からの引用。

8 ビットデータをそのまま通す「8bit」と「binary」を用いる場合は、通信経路が 8 ビットに対応している必要があります。もし 8 ビットを通さないシステムを経由するときは、7 ビットで表現可能な base64 や quoted-printable に変換されます。

“8 ビットを通さないシステム”というのが未だに存在するのかは知らないが、大事を取って Base64 符号化するのが最善だろう。

ちなみに、MIME::Entity の初期値は「binary」である。この場合、Gmail や iPhone では読めたものの、Outlook 2007 では盛大に文字化けしてしまった。

Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: binary

メール本文

3. SSL over SMTP(STARTTLS)を使用して送信する

今回のメール送信には Gmail を使ったが、ここの SMTP サーバは SSL over SMTP でないと利用できない。Perl で SSL over SMTP を利用するには Net::SMTP::SSL モジュールを使うといい。Linux や Mac OS X ならば、

$ sudo cpan -i Net::SMTP::SSL

だけでインストールできる5のだが、Windows(ActivePerl)では少し複雑。サードパーティのレポジトリを PPM に追加する必要がある。

2010-07-13_131443.png

後は例に挙げた通りである。文字コードでつまずいたものの、手順自体は単純なものだった。


  1. 以下のリストには BMP 外の文字が含まれているので、環境(古い Windows XP 環境など)によっては一部文字化けしているかも知れない。 
  2. Encode::MIME::Header モジュールで推奨されているのは、単に MIME-Header として UTF-8 文字列のまま B 符号化する方式だ。この方法なら件名などに BMP 外の文字を書くことも可能だろう。しかし今回は後述のように、ヘッダ部は ISO-2022-JP、本文は UTF-8 という変則的な運用をしている。件名に丸数字が載せられなくても困ることは少ないが、本文がそれでは実用上不便だろうという考えだからだ。両方ともを UTF-8 にしても仕様上間違いではないのだが、送信元が文字化けしてしまうのは、本文の文字化けよりも悲惨であろうからやはり避けたい。 
  3. 便宜上、一部のメールヘッダだけを表示している。以下同様。 
  4. 丸数字が使えているように見えることもあるが、全てのプラットフォームで同じように見えているとは限らない。 
  5. 事前に OpenSSL のインストールが必要。 

コメントを残す