Очередь e-mail посредством Zend_Mail

в 4:09, , рубрики: email, php, zend, Zend Framework, очередь сообщений, метки: , , ,

Всем привет.

Хочу поделиться способом организации e-mail очереди с помощью Zend_Mail.

Сразу к делу. Нам потребуется:
— таблица в БД;
— класс транспорта EmailTransport;
— файл реализующий отправку сообщений (запускается по крону).

Сперва определим таблицу БД:

CREATE TABLE email_queue ( 
    id                INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 
    recipients        TEXT NOT NULL, 
    subject           CHAR(255) NOT NULL, 
    message           TEXT NOT NULL, 
    header            TEXT NOT NULL, 
    parameters        TEXT, 
    max_attempts      TINYINT UNSIGNED NOT NULL DEFAULT 3, 
    attempts          TINYINT UNSIGNED NOT NULL DEFAULT 0, 
    is_false          TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, 
    time_last_attempt INT UNSIGNED DEFAULT NULL,
    create_time       INT UNSIGNED NOT NULL
);

Далее, создадим класс нашего транспорта:

class EmailQueueTransport extends ZendMailTransportSendmail
{
    /**
     * Send mail using EmailQueue
     *
     * @access public
     * @return void
     * @throws ZendMailTransportExceptionRuntimeException If parameters is set but not a string
     * @throws ZendMailTransportExceptionRuntimeException If failed to add a message in queue
     */
    public function _sendMail()
    {
        if ($this->parameters !== null && !is_string($this->parameters)) {
            /**
             * Exception is thrown here because $parameters is a public property
             */
            throw new ZendMailTransportExceptionRuntimeException(
                'Parameters were set but are not a string');
        }
               
        $db = ZendDbTableAbstractTable::getDefaultAdapter();
        $statement = $db->prepare('
            INSERT email_queue 
            SET recipients  = :recipients,
                subject     = :subject, 
                message     = :message,
                header      = :header,
                parameters  = :parameters,
                create_time = :create_time
        ');          
        $result = $statement->execute(array(
            'recipients'  => $this->recipients,
            'subject'     => $this->_mail->getSubject(), 
            'message'     => $this->body,
            'header'      => $this->header,
            'parameters'  => $this->parameters,
            'create_time' => time()
        ));
        
        if (!$result) {
            throw new ZendMailTransportExceptionRuntimeException(
                'Failed to add a message in queue.');
        }          
    }
}

Всё что делает этот класс, это — получает уже подготовленные данные из Zend_Mail и сохраняет их в БД. Eсли создать запись в таблице не удалось, выбрасывает исключение RuntimeException.

Ну и файл реализующий отправку сообщений:

<?php
...
// Подключение к БД, увеличение лимита времени исполнения, памяти и т.д.
...

$limit = 100;
$result = mysql_query('SELECT * FROM email_queue WHERE attempts <= max_attempts LIMIT ' . $limit);
$num_results = mysql_num_rows($result);
if ($num_results > 0) {
    for ($i = 0; $i < $num_results; $i++) {
        $row = mysql_fetch_assoc($result);

        $isSent = mail(
            $row['recipients'],
            $row['subject'], 
            $row['message'], 
            $row['header'], 
            $row['parameters']
        );
        if ($isSent) {
            mysql_query('DELETE from email_queue WHERE id = ' . $row['id']);
            if (!mysql_affected_rows()) {
                echo 'Error when deleting record from the table "email_queue". Id queue is ' . $row['id'] . '.';
            }
        
        } else {
            mysql_query('
                UPDATE email_queue 
                SET is_false = 1, 
                    attempts = ' . $row['attempts'] + 1 . ' 
                WHERE id = ' . $row['id']
            );
            if (!mysql_affected_rows()) {
                echo 'Error when updating record from the table "email_queue". Id queue is ' . $row['id'] . '.';
            }
            echo 'Error when sending messages to e-mail. Id queue is ' . $row['id'] . ', e-mails is ' . $row['recipients'] . '.';
        }
    }
}

Использование очереди:

$mail = new ZendMailMail();
$mail->setFrom($from);
$mail->addTo($to);
$mail->setBodyHtml($body);
$mail->setSubject($subject);

$transport = new EmailQueueTransport();
$mail->send($transport);

Соответственно, если нужно отправить сообщение без очереди, через mail() — не передайте транспорт либо передайте null.

P.S. В примерах я использовал Zend Framework 2.0, которая на момент написания статьи ещё находится на стадии бета-тестирования. Если вы работаете с версией 1.*, то нужно переименовать классы соответствующим образом: ZendMailMail -> Zend_Mail,
ZendMailTransportSendmail -> Zend_Mail_Transport_Sendmail,
ZendMailTransportExceptionRuntimeException -> Zend_Mail_Transport_Exception,
ZendDbTableAbstractTable -> Zend_Db_Table_Abstract

Автор: Nilov_A

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js