由于担心用户群发垃圾邮件,有些空间服务商禁用了php的mail()函数,甚至连gmail IMAP常用的465端口也禁用了,导致SMTP方式也不能发送邮件。

这种限制在PaaS中特别常见,比如cloudControl禁用了mail()函数,OpenShift禁用了SMTP端口。虽然这种方式能非常有效地阻止垃圾邮件发送者,但也给普通用户带来了一些不便,比如WordPress的密码找回功能和留言通知都不能使用。

解决这个问题的一个简单有效的方法就是用别的服务器来代发邮件。简单地说,就是当WordPress有一个发送邮件的请求时,将这个请求的内容(发件人、收件人等)POST到另一个支持发送邮件的服务器,转发服务器收到这个请求后将邮件发出,并返回发送结果。示意图如下:

net connection

这种方法实现需要同时在WordPress服务器和转发服务器上做一些更改。

转发服务器设置

转发服务器上需要一个程序来接收邮件请求,然后返回发送的结果。转发服务器我们使用了phpCloud,因为它使用了亚马逊的云服务器,稳定性比较好,而且可以从web界面配置SMTP服务器,这样mail()函数的邮件是通过SMTP服务器发出的,不会被作为垃圾邮件过滤掉。

发送的邮件内容和返回的数据都是json格式的,方便编码和解码。另外为了防止别人通过POST请求发送邮件,服务器端需要设置密码,如果密码不正确的话就拒绝发送。简单的示例代码:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php 
/**
* Post: - item name - - description -
* pass your password
* content mail content
* Return - errnum - - msg -
* ERR_SENDOK Send OK
* ERR_PASSERR Password error
* ERR_PARAMERR Parameter error
* ERR_UNFIED Exception msg
*/

//error_reporting(E_ALL);

define('PASS', "123456");

define('ERR_SENDOK', 0);
define('ERR_PASSERR', 1);
define('ERR_PARAMERR', 2);
define('ERR_UNFIED', 3);

forward_mail();

function forward_mail() {

$err = array();

if(!isset($_POST["pass"]) || !isset($_POST["content"])) {
$err["errnum"] = ERR_PARAMERR;
$err["msg"] = "No pass or mail content specified!";
}

else {

if($_POST["pass"] != PASS) {
$err["errnum"] = ERR_PASSERR;
$err["msg"] = "Password error!";
}

else {
try {
$mail = @json_decode($_POST["content"]);

if(false == @validate_addr($mail->to)) {
$err["errnum"] = ERR_PARAMERR;
$err["msg"] = "Invalid recipient address!";
}
else {
if (false == @mail($mail->to, $mail->subject, $mail->body, $mail->header)) {
$err["errnum"] = ERR_UNFIED;
$err["msg"] = "Send failed!";
}
else {
$err["errnum"] = ERR_SENDOK;
$err["msg"] = "Send OK!";
}
}
}
catch (Exception $e) {
$err["errnum"] = ERR_UNFIED;
$err["msg"] = $e->getMessage();
}
}
}

echo json_encode($err);
die();
}

function validate_addr($address) {
return preg_match('/(.*)@(.*)\.(.*)/', $address);
}

?>

把这段代码保存为forwardmail.php保存到转发服务器上,然后直接访问这个文件,如果看到”{“errnum”:2,”msg”:”No pass or mail content specified!”}”这样的输出就表示服务器端已配置完成。

注:别忘记在phpCloud的控制台设置SMTP账户信息。

WordPress 服务器的设置

WordPress的wp_mail()函数是用PHPMailer发送邮件的,我们需要hook到phpmailer_init()函数,截取邮件的内容,然后POST到转发服务器上,然后接收发送结果并返回给WordPress。

在获取PHPMailer对象的收件人有个问题:由于PHPMailer类的收件人$to对象私有的,在外部不能获取这个值,我们需要修改一下类的源代码,将它设为公有对象,这样就可以在类的外部获取它的值。

PHPMailer的源码在wp-includes/class-phpmailer.php, 只要改一行代码:

change phpmailer src

注:直接修改WP源代码是一个非常不好的习惯,每次WordPress升级的时候都会将改过的文件还原,到时候出现一些奇怪的问题可能连自己也不知道改过哪里。也许你有更好的方法获取PHPMailer的对象,欢迎留言交流。

在主题的functions.php文件添加实现转发实现代码, 将$pass和$forward_server的值修改一下。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
add_action( 'phpmailer_init', 'forward_mail' ); 

function forward_mail( $phpmailer ) {

$pass = "12345";
$forward_server = "http://xxx.my.phpcloud.com/fwdmail.php";

$headers = "From: \"" . get_option('blogname') . "\" <$phpmailer->From>\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=" . get_option('blog_charset') . "\r\n";

$mail = array
(
'header' => $headers,
'to' => @implode(',', $phpmailer->to[0]),
'subject' => $phpmailer->Subject,
'body' => $phpmailer->Body
);

$data = array
(
'pass' => $pass,
'content' => json_encode($mail)
);

// Tell wordpress the result via fakemailer
try {
$result = @Post($forward_server, $data);
if (0 == @json_decode($result)->errnum) {
$phpmailer = new fakemailer(true);
}
else {
$phpmailer = new fakemailer(false);
}
}
catch (Exception $e) {
// Hide error information
}
}

function Post($url, $post) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}

// fakemailer inspired by stackoverflow.com
class fakemailer {

public $result;

public function __construct($forward_result=false) {
$this->result = $forward_result;
}

public function Send() {
return $this->result;
}
}

重新上传functions.php,然后登出WordPress给自己留言,去收件箱里查收一下是否能收到邮件。

mail content

已知Bug: 从PHPMailer获取的Body对象都是纯文本,所有的HTML标签都没有了,这样转发出去的邮件也全部是纯文本格式,显得比较难看。