TOP > スクリプト制作メモ > Perlでメール送信

≫Perlでメール送信

掲示板その他のCGIでメール送信機能を必要とすることは多いと思います。
Perlでメール送信するための技術情報をメモしておきます。

いまどき、迷惑メール送信はケーサツに捕まりますよ。

≫sendmailコマンド

Perl でメール送信と言えば、sendmail コマンドを使ったものがわりと普及しているのではないでしょうか。
$sendmail = '/usr/sbin/sendmail'; # sendmailコマンドパス
$from = 'foo@bar.com'; # 送信元メールアドレス
$to = 'hoge@example.com'; # あて先メールアドレス
$cc = 'hage@example.com'; # Ccのあて先メールアドレス
$subject = 'test'; # メールの件名
$msg = <<"_TEXT_"; # メールの本文(ヒアドキュメントで変数に代入)
message line 1
message line 2
_TEXT_
# sendmail コマンド起動
open(SDML,"| $sendmail -t -i") || die 'sendmail error';
# メールヘッダ出力
print SDML "From: $from\n";
print SDML "To: $to\n";
print SDML "Cc: $cc\n";
print SDML "Subject: $subject\n";
print SDML "Content-Transfer-Encoding: 7bit\n";
print SDML "Content-Type: text/plain;\n\n";
# メール本文出力
print SDML "$msg";
# sendmail コマンド閉じる
close(SDML);
メールヘッダ部で指定するあて先や送信元を有効にするために、sendmail は -t オプションをつけて起動します。

-i オプションは、通常、メールの最後を示す ' . ' (ピリオド)を無効にし、ピリオドのみの行も送れるようにします。

オプションについては、サーバの設定によっては、指定しても無効になる場合もあります。
例えば、-t オプションをつけて From: を指定しても、sendmail 起動ユーザのメールアドレスが自動的に From: として扱われる、など。

ヘッダ部と本文を分けるのは「\n\n」という2連続の改行です。

≫日本語を含むメール

日本語を含むメール送信をする場合は、文字コードの明示と文字コード変換処理が必要です。

一般的に、件名、本文を JIS に変換して対処します。
# JIS への変換に jcode.pl を用いる
require './jcode.pl';
# $subject, $msg を JIS に変換
&jcode::convert(\$subject,'jis');
&jcode::convert(\$msg,'jis');

# Content-type で文字コードを明示
print SDML "Content-type: text/plain;charset=\"ISO-2022-JP\"\n\n";
ISO-2022-JP は、JISコードを指します。
文字コード変換は jcode.pl でなくて、Jcode.pm でも nkf でも kconv でも、もちろん問題ないです。

≫日本語を含むメール 2 : 件名の文字化け

メールの件名は JIS コードに変換して送信しても、化けることがあります。
主な対策は、RFC2047 に従い BASE64 Bエンコードすることです。
と、言っても、厳密に対処するのは、かなり大変なので、そこそこの対処をします(´ー`)

mimew.pl を使うバージョン
# mimewでエンコード
require './mimew.pl';
&mimeencode(\$subject);

# Content-type で文字コードを明示
print SDML "Content-type: text/plain;charset=\"ISO-2022-JP\"\n\n";

Jcode.pm を使うバージョン
# 変換は Jcode.pm
use Jcode;
# $subject を JIS に変換
&Jcode::convert(\$subject,'jis');
$subject = jcode($subject)->mime_encode;

# Content-type で文字コードを明示
print SDML "Content-type: text/plain;charset=\"ISO-2022-JP\"\n\n";
件名以外のメールヘッダ(To: や From: や Cc:)で日本語を使用する場合(山田 太郎 <yamada@example.com>など)も、件名と同じくエンコード処理をします。

≫sendmail の限界

DNS のしっかりしたサーバなら問題ないのかもしれませんが、自宅サーバで DDNS な場合、sendmail コマンドでメールを送信すると、エラーが返ってきて、未着になることがあります。

それでなくても、最近は、迷惑メール対策もかなり厳しくなって、不完全なヘッダのせいではじかれることもしばしばです。

特に、某携帯電話キャリアはインターネットからのメールが“通常”遅延してくれます。
自宅サーバでDDNSだとほとんど届かなかったりすると思います

なんとかメール送信を確実にするために、プロバイダのSMTPを利用するという方法があります。

契約プロバイダの SMTP が解放されていなければ、諦めるしかないかもですが。。。

≫Net::SMTP

Perl でプロバイダの SMTP をたたくために、Net::SMTP モジュールを利用します。
#perl -MCPAN -e shell
cpan> install Net::SMTP

Net::SMTP でのメール送信は以下の通り。
# Net::SMTP でメール送信
use Net::SMTP;
# オブジェクトの作成(おまじない)
$smtp = Net::SMTP->new('SMTPサーバ', Hello=>"SMTPサーバ");
#送信元の指定
$smtp->mail($from);
#宛先の指定
$smtp->to($to);
# ヘッダ
$date = &date;
$smtp->data();
$smtp->datasend("Date:$date\n");
$smtp->datasend("From:$from\n");
$smtp->datasend("To:$to\n");
$smtp->datasend("Cc:$cc\n");
$smtp->datasend("Subject:$subject\n");
$smtp->datasend("Content-Transfer-Encoding: 7bit\n");
$smtp->datasend("Content-Type: text/plain;charset=\"ISO-2022-JP\"\n\n");
#本文
$smtp->datasend("$msg\n");
# データの終わり、メール送信
$smtp->dataend();
#SMTP接続の終了
$smtp->quit;

sub date {
$ENV{'TZ'} = "JST-9";
($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime(time);
@week = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
@month = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
$d = sprintf("%s, %d %s %04d %02d:%02d:%02d +0900 (JST)",
$week[$wday],$mday,$month[$mon],$year+1900,$hour,$min,$sec);
return $d;
}
日本語を含む場合の処理は、sendmail 同様です。

≫POP before SMTP

迷惑メール(SPAMメール)送信を防ぐために、多くのプロバイダでは、なんらかの対策が取られていることだと思います。

中でも多いのが、POP before SMTP という仕組みです。

POP before SMTP というのは、読んでそのまま、「SMTP の前に POP しろ」ということです。

簡単に言うと、「メール送信(SMTP)する前にメール受信(POP)しろ」と言うことで、メール受信する際のユーザ名とパスワードを使った認証(POP認証)によって、その認証から一定時間はメール送信(SMTP)が許可されるという仕組みになっています。
ただ、実際は、POP認証は認証作業のみで、メール受信までする必要はないです。

この POP before SMTP が壁になって、Net::SMTP が使えないことも多いと思うので、POP認証を実現しなければいけません。

メーラーで手動認証を行うとか、メーラーの設定で一定時間ごとに自動認証させるとか、fetchmail を利用するとか、いろいろ手はありますが、Perl で完結できます。

≫Net::POP3

Net::POP3 というモジュールを利用します。

POP接続をしてくれるモジュールですが、ここでは、POP before SMTP を切り抜けるための認証だけに使います。
# モジュールの読みこみ
use Net::POP3;
# オブジェクトの作成(おまじない)
$pop = Net::POP3->new('POPサーバ');
# POPサーバにユーザ名とパスワードで接続
if($pop->login('ユーザ名', 'パスワード') >= 0) {
$pop->quit; # 接続できたらNet::POP3終了
}else{
die "can not login.\n"; # 接続できなければエラー
}


TOP > スクリプト制作メモ > Perlでメール送信
(C) bayashi.net