如何使用PHP查找退回或未传递的邮件ID

时间:2013-08-02 12:12:06

标签: php email

我正在使用PHP Mail功能发送邮件
我只需要找到邮件的状态,无论是发送还是退回 有没有办法解决这个问题

1 个答案:

答案 0 :(得分:2)

您可以将return-path标头与特定的电子邮件地址一起使用 - 例如bounce@domain.com 之后,所有退回(未交付)的电子邮件将返回收件箱以获取bounce@domain.com

您可以通过PHP连接到此pop3收件箱并阅读所有邮件。 解析每条消息并找到(可能是通过正则表达式)错误代码和原因...

我使用此代码:

$bounce = new BounceMail();

$bounce->setDebug(false);

//create connection
$bounce->createConnection('server', 110, 'user', 'password');

//parse messages
$bounce->parseMessages();

//save parsed messages to database
$messages = $bounce->getParsedMessages();
foreach ($messages as $message_id => $message) {

    /* variable $message looks like:
       $message = array(
          'email_address' => 'email', 
          'bounce_type' => 'bounce type', 
          'smtp_server' => 'smtp_server', 
          'smtp_error' => 'smtp_error', 

       );
    */

    //save returned email to database here

}

和BounceMail类:

<?php
/*
* Depends on 'Net/POP3.php';
*/
/**
 * Class BounceMail
 */
class BounceMail extends Net_POP3
{
    /**
     * @var array
     */
    private $messages = array();
    /**
     * @var array
     */
    private $parsed_messages = array();
    /**
     * @var array
     */
    private $partially_parsed_messages = array();
    /**
     * @var array
     */
    private $allowed_email_domains = array();

    /**
     * @var array
     */
    private $allowed_email_addresses = array();


    /**
     * @param $host
     * @param $port
     * @param $username
     * @param $password
     * @return bool
     * @throws ErrorException
     */
    public function createConnection($host, $port, $username, $password)
    {
        if (!$this->connect($host, $port)) {
            Throw new ErrorException("Error connecting to POP3 socket.");
        }
        if (true !== ($login = $this->login($username, $password, true))) {
            Throw new ErrorException("Login failed:" . $login->getMessage());
        }
        if (false === ($messages = $this->getListing())) {
            Throw new ErrorException("Error getting POP3 box listing.");
        } else {
            $this->messages = $messages;
        }
        return true;
    }

    /**
     * @return bool
     */
    public function parseMessages()
    {
        $this->newDebugMessage('PARSING ' . count($this->messages) . ' MESSAGES');

        $fully_parsed = 0;
        foreach ($this->messages as $message) {

            $parsed_message = $this->parseMessage($message);

            $email_address = $this->getEmailAddress($parsed_message);
            $smtp_error = $this->getSMTPError($parsed_message);
            $smtp_server = $this->getSMTPServer($parsed_message);

            $bounce_type = $this->getBounceType($smtp_error);

            if ($this->checkAllowedContent($parsed_message)) {
                $this->newParsedMessage($message['msg_id'], $email_address, $smtp_server, $smtp_error, $bounce_type);

                if (!empty($email_address) && !empty($smtp_error) && !empty($smtp_server) && !empty($bounce_type)) {
                    $fully_parsed++;
                } elseif (!empty($email_address) && (empty($smtp_error) || empty($smtp_server) || empty($bounce_type))) {
                    $this->newPartiallyParsedMessage($email_address, $parsed_message, array($smtp_error, $smtp_server, $bounce_type));
                }
            }

        }
        $this->newDebugMessage('SUCCESSFULLY PARSED ' . count($this->parsed_messages) . ' MESSAGES');
        $this->newDebugMessage('EVERYTHING FOUND IN ' . $fully_parsed . ' MESSAGES');

        return true;
    }

    /**
     * @return array
     */
    public function getParsedMessages()
    {
        return $this->parsed_messages;
    }

    /**
     * @param bool $as_text
     * @return array|string
     */
    public function getPartiallyParsedMessages($as_text = true)
    {
        if ($as_text) {
            $text = '';
            foreach ($this->partially_parsed_messages as $message) {
                foreach ($message as $data) {
                    $text .= $data . "\r\n";
                }
                $text .= "\r\n---------------\r\n\r\n";
            }
            return $text;
        } else {
            return $this->partially_parsed_messages;
        }
    }

    /**
     * @param array $domains
     */
    public function setAllowedEmailDomains(array $domains)
    {
        $this->allowed_email_domains = $domains;
    }

    /**
     * @param array $addresses
     */
    public function setAllowedEmailAddresses(array $addresses)
    {
        $this->allowed_email_addresses = $addresses;
    }

    /**
     * @param $message
     * @return bool|string
     */
    public function newDebugMessage($message)
    {
        if ($this->_debug) {
            $trace = debug_backtrace();
            $debug_message = !empty($trace) && isset($trace[1]["function"]) ? $trace[1]["function"] . ' | ' : '';
            $debug_message .= !empty($trace) && isset($trace[1]["method"]) ? $trace[1]["method"] . ' | ' : '';
            $debug_message .= $message . "\r\n";
            echo $debug_message;
            return $debug_message;
        }
        return false;
    }

    /**
     * @param $parsed_message
     * @return bool
     */
    private function checkAllowedContent($parsed_message)
    {
        foreach ($parsed_message as $content) {
            if (!$this->parseAllowedContent($content)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param $content
     * @return bool
     */
    private function parseAllowedContent($content)
    {
        $patterns = array(
            'SENDER POLICY FRAMEWORK' => '/(5.7.1\s*Sender\s*Policy\s*Framework)/im',
            'TOO MANY HOPS' => '/too\s*many\s*hops/im',
            'BLOCKED' => '/554\s*Blocked\s*by\s*Reputation\s*Enabled\s*Defense/im'
        );
        foreach ($patterns as $name => $pattern) {
            $matches = array();
            preg_match($pattern, $content, $matches);
            if (isset($matches[1]) && !empty($matches[1])) {
                $this->newDebugMessage('DISALLOWED CONTENT FOUND (' . $name . ')');
                return false;
            }
        }
        return true;
    }

    /**
     * @param $message_id
     * @param $email_address
     * @param $smtp_server
     * @param $smtp_error
     * @param $bounce_type
     * @return array|bool
     */
    private function newParsedMessage($message_id, $email_address, $smtp_server, $smtp_error, $bounce_type)
    {
        if (!empty($message_id) && !empty($email_address)) {
            return $this->parsed_messages[$message_id] = array('email_address' => $email_address, 'smtp_server' => $smtp_server, 'smtp_error' => $smtp_error, 'bounce_type' => $bounce_type);
        }
        return false;
    }

    /**
     * @param $email_address
     * @param array $parsed_message
     * @param array $vars
     * @return array|bool
     */
    private function newPartiallyParsedMessage($email_address, array $parsed_message, array $vars = array())
    {
        $full_content = '';
        foreach ($parsed_message as $content) {
            $full_content .= $content . "\r\n";
        }
        if (!empty($email_address) && !empty($full_content)) {
            if (!empty($vars)) {
                foreach ($vars as $var) {
                    $full_content .= "\r\n" . $var . ' | ';
                }
            }
            return $this->partially_parsed_messages[] = array($email_address, $full_content);
        } else {
            return false;
        }
    }

    /**
     * @param $message
     * @return array|bool
     */
    private function parseMessage($message)
    {
        if (empty($message)) {
            $this->newDebugMessage('Empty message');
            return false;
        }
        if (!$content = $this->getMsg($message['msg_id'])) {
            $this->newDebugMessage('Error reading content of message :' . $message['msg_id'] . '/' . $message['uidl']);
        }

        //todo add attachments
        if (!empty($content)) {
            return array('content' => $content);
        }
        return false;
    }

    /**
     * @param $parsed_message
     * @return bool
     */
    private function getEmailAddress($parsed_message)
    {
        if (empty($parsed_message)) {
            $this->newDebugMessage('Empty parsed message');
            return false;
        }

        $found_emails = array();
        foreach ($parsed_message as $content) {
            $found_emails = array_unique(array_merge($found_emails, $this->parseEmailAddress($content)));
        }
        if (count($found_emails) > 1) {
            $this->newDebugMessage('More than one e-mail address found: ' . join('; ', $found_emails));
            return false;
        }
        if (count($found_emails) == 1) {
            $this->newDebugMessage('Found e-mail address: ' . $found_emails[0]);
            return $found_emails[0];
        }

        $this->newDebugMessage('No e-mail address found');
        return false;
    }

    /**
     * @param $content
     * @return array
     */
    private function parseEmailAddress($content)
    {
        $email_patterns = array('/[^-]To:\s?<?([a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+)>?/i',);
        $found_emails = array();
        foreach ($email_patterns as $pattern) {
            $matches = array();
            preg_match_all($pattern, $content, $matches);
            if (!empty($matches) && isset($matches[1])) {
                $recipient_emails = $matches[1];
                $recipient_emails = array_filter($recipient_emails, array($this, 'filter_allowed_domains'));
                $recipient_emails = array_filter($recipient_emails, array($this, 'filter_allowed_addresses'));
                if (!empty($recipient_emails)) {
                    $found_emails = array_unique(array_merge($found_emails, $recipient_emails));
                }
            }
        }
        return $found_emails;
    }

    /**
     * @param $email
     * @return bool
     */
    private function filter_allowed_domains($email)
    {
        return !in_array(substr(strrchr($email, "@"), 1), $this->allowed_email_domains);
    }

    /**
     * @param $email
     * @return bool
     */
    private function filter_allowed_addresses($email)
    {
        return !in_array($email, $this->allowed_email_addresses);
    }

    /**
     * @param $smtp_error
     * @return bool|int
     */
    private function getBounceType($smtp_error)
    {
        if (empty($smtp_error)) {
            $this->newDebugMessage('Empty smtp error');
            return false;
        }
        $status = substr($smtp_error, 0, 1);
        switch ($status) {
            case 4:
                return 1; //temporary
                break;
            case 5:
                return 2; //permanent
                break;
        }
        return false;
    }

    /**
     * @param $parsed_message
     * @return bool
     */
    private function getSMTPServer($parsed_message)
    {
        if (empty($parsed_message)) {
            $this->newDebugMessage('Empty parsed message');
            return false;
        }

        $found = array();
        foreach ($parsed_message as $content) {
            $found = array_unique(array_merge($found, $this->parseSMTPServer($content)));
        }
        if (count($found) > 0) {
            $server = substr(join('; ', $found), 0, 254);
            $this->newDebugMessage('Found SMTP server: ' . $server);
            return $server;
        }

        $this->newDebugMessage('No SMTP server found');
        return false;
    }

    /**
     * @param $content
     * @return array
     */
    private function parseSMTPServer($content)
    {
        $patterns = array('/remote-mta:\s?dns;\s?([^\s]*)/i', '/reporting-mta:\s?dns;\s?([^\s]*)/i', '/received-from-mta:\s?dns;\s?([^\s]*)/i');
        $found_statuses = array();
        foreach ($patterns as $pattern) {
            $matches = array();
            preg_match_all($pattern, $content, $matches);
            if (!empty($matches) && isset($matches[1])) {
                $found_statuses = array_unique(array_merge($found_statuses, $matches[1]));
            }
        }
        return $found_statuses;
    }

    /**
     * @param $parsed_message
     * @return bool
     */
    private function getSMTPError($parsed_message)
    {
        if (empty($parsed_message)) {
            $this->newDebugMessage('Empty parsed message');
            return false;
        }

        $found = array();
        foreach ($parsed_message as $content) {
            $found = array_unique(array_merge($found, $this->parseSMTPError($content)));
        }

        if (count($found) > 0) {
            $error = substr(join('; ', $found), 0, 19);
            $this->newDebugMessage('Found SMTP Error: ' . $error);
            return $error;
        }

        $this->newDebugMessage('No SMTP Error found');
        return false;
    }

    /**
     * @param $content
     * @return array
     */
    private function parseSMTPError($content)
    {
        //replace errors with codes
        $content = str_replace('User mailbox exceeds allowed size', ' 452 4.1.1 ', $content);
        $content = str_replace('user is over quota', ' 452 4.1.1 ', $content);

        //find error
        $patterns = array(
            '/status:\s?#?(\d\.\d\.\d)/i',
            '/[\d]{3}\s#?(\d\.\d\.\d)/',
            '/\(#(\d\.\d\.\d)\)/',
            '/Remote SMTP Server Returned:\s?#?([\d]{3})/i',
        );
        $found_statuses = array();
        foreach ($patterns as $pattern) {
            $matches = array();
            preg_match_all($pattern, $content, $matches);
            if (!empty($matches) && isset($matches[1])) {
                $found_statuses = array_unique(array_merge($found_statuses, $matches[1]));
            }
        }
        return $found_statuses;
    }
}

你应该在parseSMTPError中添加更多的常规