A simple way to queue e-mail using Zend_Mail

Hello.

I want to share very simple and easy way of e-mail queues using Zend_Mail. The examples in the article is intentionally made as simple as possible and not tied to the framework, because the aim of the article is to show method and not a specific implementation. In addition, this solution does not have to be used within Zend Framework, it is easy to fit into any project.

Straight to the point. We need:
table in the database;
class transport;
file implements sending messages (run for the crown).

First define the database table:
the
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,
in_process INT UNSIGNED DEFAULT NULL DEFAULT 0, 
time_last_attempt INT UNSIGNED DEFAULT NULL,
create_time INT UNSIGNED NOT NULL
);


Next, create a class of our transport system:
the
class Zend_Mail_Transport_Sendmail extends EmailQueueTransport
{
/**
* Send mail using EmailQueue
*
* @access public
* @return void
* @throws Zend_Mail_Transport_Exception If parameters is  set  but not a string
* @throws Zend_Mail_Transport_Exception If failed to add a message in queue
*/
public function _sendMail()
{
if ($this->parameters !== null && !is_string($this->parameters)) {
/**
* Exception is thrown here because the $parameters is a public property
*/
throw new Zend_Mail_Transport_Exception('Parameters were set but are not a string');
}

$db = Zend_Db_Table_Abstract::getDefaultAdapter();
$statement = $db->prepare('
INSERT email_queue 
SET recipients = to :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 Zend_Mail_Transport_Exception(
'Failed to add a message in queue.');
} 
}
}

All of which makes this class, it receives the already prepared data from Zend_Mail and stores them in the database. If you create an entry in a table failed, throws an exception Zend_Mail_Transport_Exception.

Well, the file that implements the sending of message assume that the file is in a separate folder in the application root:
the
<?php
/**
* Add the messages in the log
* 
* @param  type  $message 
* @return void
*/
function set_log($message) 
{
$message = date('H:i d.m.Y ', time()) . $message . "\r\n";
$logFile = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . basename(__FILE__, '.php') . '.log';
error_log($message, 3, $logFile); 
}

try {
$config = include realpath(dirname(__FILE__) . '/../') . '/application/config/config.php'; // Path to config file
$configDb = $config['db']['params'];

$db = new PDO(
'mysql:host=' . $configDb['host'] . ';dbname=' . $configDb['dbname'],
$configDb['username'],
$configDb['password'],
array(
PDO::MYSQL_ATTR_INIT_COMMAND = > "SET NAMES 'utf8'",
'profiler' => false,
)
);
} catch (PDOException $e) {
set_log('Connection error:' . $e->getMessage());
} 

$limit = 100; // limit rows
$processLimitTime = 600; // 10 minutes
$statement = $db->query('
SELECT * 
FROM email_queue 
WHERE attempts < max_attempts
AND in_process < ' . (time() - $processLimitTime) . '
ORDER BY id ASC
LIMIT ' . $limit
);
$rows = $statement- > fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$db->beginTransaction();
$result = $db- > exec('
UPDATE email_queue 
SET in_process = ' . time() . '
WHERE id = ' . $row['id']
);
if (!$result) {
set_log('Error when updating record from the table "email_queue". Id queue is ' . $row['id'] . '.');
continue;
}

$sent = mail(
$row['recipients'],
$row['subject'], 
$row['message'], 
$row['header'], 
$row['parameters']
);
$db->commit();

if ($sent) {
$result = $db- > exec('DELETE from email_queue WHERE id =' . $row['id']);
if (!$result) {
set_log('Error when deleting record from the table "email_queue". Id queue is ' . $row['id'] . '.');
}

} else {
$result = $db- > exec('
UPDATE email_queue 
SET is_false = 1, 
in_process = 0,
attempts = ' . ($row['attempts'] + 1) . ' 
WHERE id = ' . $row['id']
);
if (!$result) {
set_log('Error when updating record from the table "email_queue". Id queue is ' . $row['id'] . '.');
}
set_log('Error when sending messages to e-mail. Id queue is ' . $row['id'] . 'e-mails is' . $row['recipients'] . '.');
}
}

Use of queue:
the
$mail = new Zend_Mail();
$mail- > setFrom($from);
$mail->addTo($to);
$mail- > setBodyHtml($body);
$mail->setSubject($subject);

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

Accordingly, if you need to send a message to the queue, using mail () to not pass a car or pass null.
P. S. Maybe someone will consider it more correct to use Zend_Queue, and perhaps will be right. I in any case do not claim that the method described in the article is the only solution. Solutions are always a lot, I have described only one of them.

P. P. S. special thanks for useful comments to users markPnk and gen.
Article based on information from habrahabr.ru

Comments

Popular posts from this blog

Powershell and Cyrillic in the console (updated)

Active/Passive PostgreSQL Cluster, using Pacemaker, Corosync

Automatic deployment ElasticBeanstalk using Bitbucket Pipelines