2010年7月1日木曜日

FreeBSD,Perl スクリプトで,GmailのSMTPからメールを送る

 Net::SMTP::TLS は2006年1月以来更新が止まっていて,2016年時点ではすでに非推奨になっている。 そこで,2016年11月に FreeBSD で,Perl で gmail からメールを送る:Net::SMTP::TLS をやめて,Net::SMTP,IO::Socket::SSL,Authen::SASL を使うことにした というのを書いたので,そちらも参照してみて欲しい。
 後日 FreeBSD, Perl の Net::SMTP::TLS ではまるというのを書いた。 そちらも参考にしてみて欲しい。
2009年3月に FreeBSD上でPerlのスクリプトを使ってプロバイダの認証あり587番ポートからメールを送信する,という話を書いた。

今回,似たようなことをしたので,それを記録に残しておこうと思う。やった作戦は何かというと,Gmail(Googleのmail)のSMTPとPerlのスクリプトを組み合わせて,googleのアカウントからメールを送る,という作戦。なんでこんな作戦をしたくなったかというと,同窓会関係のメールの一斉送信のため。個人的に高校の同期の同窓会のサイトを作っているのだが,そのメンバーに一斉にメールをしたいことがあるのだが,メールする先はPCだったり携帯だったりする。それまでは,GmailでBcc: フィールドにアドレスを100以上並べて送信したのだが,この間その方法で送信すると,携帯で受け取った人から「文字化けで全然あかん」というお叱りを多数受け取った。そのうちPCで受け取った友人からも文字化けを指摘された。いろいろ見てみると,携帯によってはBcc:フィールドを他の目的で使うことがあるみたい。また,Bcc:フィールドを長くするとトラブルを生じるみたい。また,文字コードをデフォルトに設定していると,変な文字があるといつものISO-2022-JPではない文字コードにされてしまうみたい。そのため,ひどい文字化けが起こったようである。

そこで,いろいろ作戦を考えてみた。まずは,Perlのスクリプトを使って,うちのプロバイダのSMTPからメールを送信するのは以前やったので,それを使うという作戦。しかし,これは送信元がうちのアドレスになり,私一人で使ってるアドレスではないので,ちょっと難がある。他にも,メールアドレスを20個ずつぐらいに分割して,数回に分けて送信する,とか,google groupを複数作って(確か収容できるアドレスに最大値があったと思う → 制限はないかも…)そこから送信させる,など。しかし,どれもいまいちだった。そこでいろいろ調べているうち,gmailもsmtpで送信できる,というのを思い出した。そこで,「gmail smtp」で検索してみると,ちゃんとgoogleのヘルプページがひっかかった。それによれば
「送信メール (SMTP) サーバー - TLS を使用する場合」は,
・サーバーが「smtp.gmail.com (認証の使用)」で,
認証が必要で,
・「STARTTLS の使用: オン」(TLSを使えということらしい),
・ポートは「465 または 587」を使い,
・アカウント名は「Gmail のユーザー名(「@gmail.com」を含む)」で,
・メールアドレスは「Gmail のメールアドレス (username@gmail.com)」,
・パスワードは「Gmailのパスワード」,
を使えば使えるみたい。
これなら,以前の投稿で書いたNet::SMTP::TLSJcode.pmを使えば簡単に実現できそう。と,いうことで挑戦してみた。前回との違いは「NoTLS => 'NO',」という行を削除したぐらい。後は,subjectや本文,アドレスリストを別ファイルで用意しておいて,読み込ませるようにしておいた。

以下に,ソースコードを載せておこう。ちなみにプロのPerl使いとは違って,ちょっとどんくさい感じであるが,その点はご容赦願いたい。ここでは,subjectは同じディレクトリのsubjectという名前のファイルにテキストでいれている。subjectは短い方がよく,1行に収めないといけない(1行しか読み込んでない)。アドレスリストは同じく同じディレクトリにmaillistというファイル名のテキストファイルで用意する。これは1行に1アドレスのみとしている。TABで区切って名前や敬称などをいれて差し込むことも可能である(途中の「# $line =~ s/MMMM0MMMM/$name/;」という行はそのためにある。また,本文は同じディレクトリにmessageという名前のテキストファイルを用意しておく。

このスクリプトでは,短時間に大量に送信してスパム発信者と思われても困るので,メール送信とメール送信の間に7~17秒の間を置いている。この時間間隔でいいのか,あるいは長すぎるのか,は不明だが,一応100個程度のメールは送信はできた。もうちょっと短くてもいいかなぁ?という印象だが…

また,「GetTimeStr」,「GetDateStr」,「ZeroPadding」というサブルーチンはそれぞれ,時刻文字列,日付文字列,一桁の数字の前にゼロを加える,というルーチンである。

#! /usr/local/bin/perl
#
use Jcode;
use Net::SMTP::TLS;
#
$min_wait_time = 7; # seconds
$wait_time_width = 10; # seconds
#
my $mailhost = 'smtp.gmail.com';
my $mailport = 587;
my $mail_username = 'xxxyyy@gmail.com';
my $mail_password = 'passwordpassword';
my $from_mail = 'xxxyyy@gmail.com';
#
#
# subject
open(SUBJ, "./subject");
$subject=<SUBJ>;
$subject=~ s/\s*$//; # remove spaces, cr and lf at the end
close(SUBJ);
$subject = jcode($subject)->jis;
#
#
# get address list
open(LIST, "./maillist");
$count=0;
while(<LIST>) {
    $to_mail=$_;
    $to_mail =~ s/\s*$//; # remove spaces, cr and lf at the end
    if ($to_mail !~ /^#/) {
        $count++;
        $stop_time = $min_wait_time + int(rand($wait_time_width));
        print '今から'.$stop_time.'秒待って xxxyyy@gmail.com からの送信を開始します。('.&GetTimeStr.")\n";
        sleep($stop_time);
        print 'No.'.$count.": '".$to_mail."'宛の送信を開始しました。(".&GetTimeStr.")\n";
#
#
# get message
        $message= '';
        open(MES, "./message");
        my $line;
        while(<MES>) {
            $line=$_;
#            $line =~ s/MMMM0MMMM/$name/;
#            $line =~ s/MMMM1MMMM/$title/;
            $message=$message.$line;
        }
        close(MES);
#------------------
        $message = jcode($message)->jis;

        my $header;
        $header = "From: " . jcode("$from_mail")->mime_encode . "\n";
        $header .= "To: " . jcode("$to_mail")->mime_encode . "\n";
        $header .= "Subject: " . jcode($subject)->mime_encode . "\n";
        $header .= "MIME-Version: 1.0\n";
        $header .= "Content-type: text/plain; charset=ISO-2022-JP\n";
        $header .= "Content-Transfer-Encoding: 7bit\n\n";

        my $smtp = new Net::SMTP::TLS(
            $mailhost,
#           NoTLS => 'NO',
            Port => $mailport,
            User => $mail_username,
            Password => $mail_password
        );

        $smtp->mail($from_mail);
        $smtp->to($to_mail);
        $smtp->data();
        $smtp->datasend($header);
        $smtp->datasend($message);
        $smtp->dataend();
        $smtp->quit;

        print 'No.'.$count.": '".$to_mail."'宛への送信が終了しました。(".&GetTimeStr.")\n";
        print '---------------------------------------'."\n";
    } # if
} # while
close(LIST);

print '====================================================='."\n";
print 'xxxyyy@gmail.com から '.$count.' 名宛にメールを送信しました。'."\n";
print &GetDateStr.'  '.&GetTimeStr."\n\n";

exit;
#==========================================
#
# get time-string 時刻
#
sub GetTimeStr {
    my(@GTS_date_array) = @_;
    if ($GTS_date_array[1] eq '') {
        @GTS_date_array = localtime(time);
    }
    my($dum);
    $dum=&ZeroPadding($GTS_date_array[2]).":".&ZeroPadding($GTS_date_array[1])
          .":".&ZeroPadding($GTS_date_array[0]);
    return $dum;
}
#
# get date-string 日付
#
sub GetDateStr {
    my(@GTS_date_array) = @_;
    if ($GTS_date_array[1] eq '') {
        @GTS_date_array = localtime(time);
    }
    my($dum);
    $dum=($GTS_date_array[5]+1900)."/".&ZeroPadding($GTS_date_array[4]+1)
          ."/".&ZeroPadding($GTS_date_array[3]);
    return $dum;
}
#
# zero padding ゼロで埋める(2文字の時だけ)
#
sub ZeroPadding {
    my($dum)=sprintf("%d",@_);
    $dum="00".$dum;
    $dum=substr($dum,(length($dum)-2));
    return $dum;
}
上記のスクリプトを使う場合,maillistはテスト用を含めて複数用意しておいて,適宜maillistにコピーして使うと便利である。また,subjectやmessageは,subject.20100701のようにコピーしておくと,何を送ったかがわかってよいかもしれない。

このスクリプトを使って送信すると,gmailのアカウントの送信メールにずらっと送信した履歴が残る。あまりいっぱい残ってもいまいちなので,一斉にうまく消す方法を今模索しているところである。

(追記) script内で「<」と「>」をそのまま入力したら,htmlのタグと認識されてしまっていた。訂正しておいた。(2010/7/29)

0 件のコメント: