あーさーの備忘録

ゆっくり自由に生きてます

maildropのmailfilterで受信メールをプログラムの標準入力に渡す

こんにちは、最近水曜日のカンパネラにハマっているあーさーです。

maildropとは

今日は、maildropの.mailfilterという振り分け機能のようなものを使用して、受信したメールをプログラムにの標準入力に渡す方法をご紹介します。maildropっていうのはiCloudの話ではなく、MDA(Mail Delivery Agent)といって、届いたメールをメールボックスまで配達するプログラムの一種です。有名なレンタルサーバーだと、さくらのレンタルサーバー(http://www.sakura.ne.jp/)やエックスサーバー(https://www.xserver.ne.jp/)などがmaildropを使用しています。今回はこの2社のレンタルサーバーでの設定方法を載せておきます。

mailfilterの場所

mailfilterとは、文字通り届いたメールが通る振り分けプログラムのようなものになります。このファイルの場所はレンタルサーバーによって違うのですが、さくらとエックスサーバーの場合を載せておくので参考にしてください。

さくらのレンタルサーバーの場合

/home/[サーバーID]/MailBox/[アドレス名]/.mailfilter

よく見ると.procmailrcや.forwardというファイルもあるので、maildropではなくてprocmailを使っているのかもしれませんね。実際Webサイトを見るとprocmailのパスが掲載されていますし。でも.mailfilterの書き方がprocmailじゃないんだよなぁ……

エックスサーバーの場合

/home/[サーバーID]/[ドメイン名]/mail/[サブドメイン]/[アドレス名]/.alias

エックスサーバーの方はちょっとディレクトリが複雑ですね。エックスサーバーにも.mailfilterというファイルが有るのですが、このファイルで他のいろいろなスクリプトを読み込んでいる感じなので、そこで読み込まれている.aliasファイルに記述するのが無難だと思います。

mailfilterの書き方

例えば、spamspam.spamから来たメールをgomibakoディレクトリに配達する場合は、下のように書きます。

if (/^From: spam@spam.spam/:h)
{
    to "/gomibako/"
}

/~/の部分はおなじみの正規表現です。:hと書くことでヘッダのみにマッチさせます。to "[ディレクトリ]"で指定したディレクトリにメールを配達し、配達を終了します。toではなくccを使うことで、配達を終了させずに処理を終了させることができます。また、exitと書くことでも配達処理を終了できます。ディレクトリを入れる部分に"! hoge@hoge.com"と!とメールアドレスを入れることで、指定したメールアドレスにメールを送ることができます。たとえば、

cc "! poyo@poyo.com"
cc "! fuga@fuga.com"

と書くことで、届いたメールを2メールアドレスに転送させることができます。これ使えばメーリングリストとか簡単に作れる気がしてきます。

届いたメールをプログラムに渡す

これを応用することで、届いたメールをプログラムの標準入力に渡すことが出来ます。例えば、ログファイルを生成したり、メールが届いたという通知をLINEやDiscordに送ったりできます。可能性は無限大です。今回はPHPを使ってみます(というより私がPHP以外まともに書けない)。

cc "| /usr/bin/php7.0 /home/hoge/hoge.com/script/notification.php"
exit

シェルコマンドで御用達のパイプが登場します。cc "| [PHPのパス] [スクリプトのパス]"と記述すると届いたメールがスクリプトの標準入力に渡されプログラムが実行されます。最後のexitは書かなくてもいいですが、書いておくとメールボックスにメールが保存されなくなります。

スクリプトを書く

次に、渡されるスクリプト側を書きます。メールのデータを解析するのに、今回はPEARのMail::mimeDecodeを使用します。導入方法は他のサイトでも探してください。惰性でPEAR使ったのですが、もしかしたらphp-mime-mail-parserというライブラリ使ったほうが記述が楽かもしれませんね。気力があったらこちらを使った方法も後日書きたいと思います。

#!/usr/bin/php7.0
<?php
//PEARのMail_mimeDecodeを読み込む
require_once('/home/hoge/hoge.jp/pear/PEAR/Mail/mimeDecode.php');
//文字コードはUTF-8で日本語だよ
mb_internal_encoding('UTF-8');
mb_language('japanese');
//メールのデータが標準入力に渡されるのでそれを$sourceに格納
$source = file_get_contents("php://stdin");
if (!$source) {
    die('Error');
}
//Mail_mimeDecodeの設定
$decode_opt = array(
    'include_bodies' => true,
    'decode_bodies'  => true,
    'decode_headers' => true,
);
$decoder = new Mail_mimeDecode($source);
$mail_data = $decoder->decode($decode_opt);
//メールの内容をUTF-8にエンコードしてそれぞれ変数に格納
$from = mb_convert_encoding($mail_data->headers['from'], 'UTF-8', 'ISO-2022-JP,UTF-8,SJIS');
$subject = mb_convert_encoding($mail_data->headers['subject'], 'UTF-8', 'ISO-2022-JP,UTF-8,SJIS');
$ctype = strtolower($mail_data->ctype_primary);
if ($ctype === 'multipart') {
    foreach ($mail_data->parts as $part)
    {
        if (strtolower($part->ctype_primary) === 'text' and strtolower($part->ctype_secondary) === 'plain') {
            $body = "\n" . mb_convert_encoding($part->body, 'UTF-8', 'ISO-2022-JP,UTF-8,SJIS');
        }
    }
}
elseif ($ctype === 'text') {
    $body = "\n" . mb_convert_encoding($mail_data->body, 'UTF-8', 'ISO-2022-JP,UTF-8,SJIS');
}
else {
    die('Error');
}

HTMLメールとかいう害悪のせいで若干複雑になっていますね。これで、$body・$from・$subjectにそれぞれ目的のデータが格納されます。文字コードEUCじゃないと動かないと書いてあるサイトもありましたがとりあえずはUTF-8で大丈夫そうです。気をつけてほしいことが2点あります。

1点目は、プログラムの最初に、#!/usr/bin/php7.0PHPのパスを書くことです。これはPHP以外の言語を使用する場合も同じなので気をつけてください。

2点目は、プログラムが異常終了すると、メール送信者にエラーメールが届くことです。このプログラムだと、die()が実行されてしまう場合はエラーメールが届いてしまいます。他にも、些細なsyntax errorなどでいちいちエラーメールを生成するので、作成時はメールを送ってチェックするのではなく、メールのデータを直接渡してコマンドラインで実行させるなどすると良いと思います。

ここまで書けたら、あとはstream_context_create()してWebhookにPOST送ってあげるとメール通知プログラムの完成です。PHPfile_get_contents()が優秀すぎてつらいです。