CakePHP シェルで任意のメールを処理する

携帯サイトで画像投稿に ランダム文字列@domain.com みたいなアドレスをユーザーに発行して、そこに添付メールを送信してもらい、PHPで受け取ってパースしたかった。

PHPとsendmailで任意のメールアドレスに来たメールを処理する方法

こちらのブログで紹介されている通りなんだけど、/etc/mail/sendmail.cfを修正してもエラーになる。
表記が難解過ぎて自分で調べる気にもならない・・・。
ふとコメントを見ると「virtusertable」で設定するのが今風らしい。 で調べて半日以上ウダウダ格闘した結果。

/etc/aliases に追記

hoge: "| cake shell_name function -app /cake_path/app"

hoge に実体(linuxユーザー)はない。

php, cake, cake.phpはsendmailが呼べるように/etc/smrsh/にシンボリックリンクを貼っておく。

$ln -s /usr/bin/php php
$ln -s /cake_path/cake/console/cake cake
$ln -s /cake_path/cake/console/cake.php cake.php

ハッシュリスト再構築

newaliases

/etc/mail/virtusertable

@mail.hoge.com hoge

mail.hoge.comに来たメールは全てaliasesのhogeに投げるという設定。
本当は @hoge.com に全て飛ばしたかったのだが、ユーザーの有無に関わらず全て転送されてしまうため、サブドメインを用意した。
他に方法はあるんだろうか。

再構築。

/usr/sbin/makemap hash /etc/mail/virtusertable.db > /etc/mail/virtusertable

そんでもって更にサブドメインにしてしまったのでlocal-host-namesを修正。

/etc/mail/local-host-names

mail.hoge.com

たぶんsendmailの再起動、stop → startが必要。

ここからやっと本題?のCakePHPでの処理。
メールのパースにはPEARのMail_mimeDecodeを利用する。

/cake_path/vendors/pear_init.php

ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . dirname(__FILE__));

PEAR環境は整っているものとする。

Mail_Mime
/cake_path/vendors/Mail/mime.php
Mail_mimeDecode
/cake_path/vendors/Mail/mimeDecode.php
Mail_mimePart
/cake_path/vendors/Mail/mimePart.php

をダウンロード、設置。

/cake_path/app/vendors/shells/mailer.php とか

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
App::import('Vendor', array('pear_init'));
App::import('Vendor', 'Mail_mimeDecode', array('file' => 'Mail/mimeDecode.php'));
App::import('Component', 'Email');
 
class MailerShell extends Shell {
	function startup(){
	$this->Controller =& new Controller();
	$this->Email =& new EmailComponent(null);
	$this->Email->initialize($this->controller);
}
 
function receive() {
	// 標準出力から読み取り
	$stdin = $this->Dispatch->stdin;
	while(!feof($stdin)) {
		$email .= fgets($stdin, 4096);
	}
 
	$decoder = new Mail_mimeDecode($email);
	$params['include_bodies'] = true; //ボディを解析する
	$params['decode_bodies'] = true; //ボディをコード変換する
	$params['decode_headers'] = true; //ヘッダをコード変換する
	$structure = $decoder->decode($params);
 
	// たとえばtoをパース
	preg_match("/([a-zA-Z0-9"._-]+@[a-zA-Z0-9._-]+.+[a-zA-Z0-9]+)/"
	, $structure->headers['to']
	, $mailto);
 
	// 以後適当な処理
 
	// たとえば宛先不明なら
	$this->_user_unknown($mailto[0], $email);
}
 
function _user_unknown($to, $body) {
	$this->Email->smtpOptions = array(
		'port'=>'25',
		'timeout'=>'30',
		'host' => 'localhost',
	);
	$this->Email->delivery = 'smtp';
 
	$this->Email->from = "Mail Delivery Subsystem <mailer-daemon@mail.hoge.com>";
	$this->Email->to = "<".$to.">";
	$this->Email->subject = "Delivery Status Notification (Failure)";
	$this->Email->sendAs = "text";
	$this->Email->template = "annon_user";
 
	$this->Email->send($body);
}

toからDBに紐づいたユーザーを判別して云々とか。
設定したドメイン宛のメールが全てここに来ることになるので、User unknownとかも自分で送信しないといけないっぽい。

後は$structureに全部デコード状態で入っているので$this->log()とかして中身を見れば後はわかるかと。
文字列はエンコードタイプを見て、必要に応じて変換(mb_convert_encoding)する必要あり。
添付のバイナリデータはそのままファイルに出力すればOKだった。