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

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

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

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

net connection

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

1. 转发服务器设置

转发服务器上需要一个程序来接收邮件请求,然后返回发送的结果。转发服务器我们使用了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账户信息。

2. 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标签都没有了,这样转发出去的邮件也全部是纯文本格式,显得比较难看。

关键字:WordPress, OpenShift, PaaS

本文链接:树叶的BLOG >> 利用phpCloud代发邮件解决WordPress不能发送邮件的问题

本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 Unported许可协议进行许可。

上一篇 : Windows环境下cloudControl开发环境的搭建和安装WordPress 下一篇 : 免费获取25G的Box.net空间