互联网服务 2012 年 8 月 31 日

利用phpCloud代发邮件解决WordPress不能发送邮件的问题

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

转发服务器设置

转发服务器上需要一个程序来接收邮件请求,然后返回发送的结果。转发服务器我们使用了phpCloud,因为它使用了亚马逊的云服务器,稳定性比较好,而且可以从web界面配置SMTP服务器,这样mail()函数的邮件是通过SMTP服务器发出的,不会被作为垃圾邮件过滤掉。
发送的邮件内容和返回的数据都是json格式的,方便编码和解码。另外为了防止别人通过POST请求发送邮件,服务器端需要设置密码,如果密码不正确的话就拒绝发送。简单的示例代码:
<?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的值修改一下。
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标签都没有了,这样转发出去的邮件也全部是纯文本格式,显得比较难看。