由于担心用户群发垃圾邮件,有些空间服务商禁用了php的mail()函数,甚至连gmail IMAP常用的465端口也禁用了,导致SMTP方式也不能发送邮件。
这种限制在PaaS中特别常见,比如cloudControl禁用了mail()函数,OpenShift禁用了SMTP端口。虽然这种方式能非常有效地阻止垃圾邮件发送者,但也给普通用户带来了一些不便,比如WordPress的密码找回功能和留言通知都不能使用。
解决这个问题的一个简单有效的方法就是用别的服务器来代发邮件。简单地说,就是当WordPress有一个发送邮件的请求时,将这个请求的内容(发件人、收件人等)POST到另一个支持发送邮件的服务器,转发服务器收到这个请求后将邮件发出,并返回发送结果。示意图如下:
这种方法实现需要同时在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
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, 只要改一行代码:

注:直接修改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) ); try { $result = @Post($forward_server, $data); if (0 == @json_decode($result)->errnum) { $phpmailer = new fakemailer(true); } else { $phpmailer = new fakemailer(false); } } catch (Exception $e) { } } 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; }
class fakemailer { public $result; public function __construct($forward_result=false) { $this->result = $forward_result; } public function Send() { return $this->result; } }
|
重新上传functions.php,然后登出WordPress给自己留言,去收件箱里查收一下是否能收到邮件。
已知Bug: 从PHPMailer获取的Body对象都是纯文本,所有的HTML标签都没有了,这样转发出去的邮件也全部是纯文本格式,显得比较难看。