Skip to content
Browse files

Initial EmailQueue code

  • Loading branch information...
0 parents commit 8919504def12c8eced0389149cd3df6de4b52186 @lorenzo lorenzo committed
Showing with 331 additions and 0 deletions.
  1. +15 −0 Config/Schema/email_queue.sql
  2. +80 −0 Console/Command/SenderShell.php
  3. +157 −0 Model/EmailQueue.php
  4. +79 −0 README.md
15 Config/Schema/email_queue.sql
@@ -0,0 +1,15 @@
+DROP TABLE IF EXISTS `email_queue`;
@AD7six
AD7six added a note

I'm in the habit of creating Config/schema.sql for all apps or Plugins

Thoughts?

If we have a consistent place for putting sql dumps, they are easy load (i.e. automatically in some cases)

@jippi
jippi added a note

I'm all for that :)

Right now, they are located in Config folder.
https://github.com/nodesagency/backend-contests/tree/master/Config

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+CREATE TABLE IF NOT EXISTS `email_queue` (
+ `id` char(36) CHARACTER SET ascii NOT NULL,
+ `to` varchar(100) NOT NULL,
+ `config` varchar(30) NOT NULL,
+ `template` varchar(50) NOT NULL,
+ `layout` varchar(50) NOT NULL,
+ `template_vars` text NOT NULL,
+ `sent` tinyint(1) NOT NULL,
+ `send_tries` int(2) NOT NULL,
+ `send_at` datetime DEFAULT NULL,
+ `created` datetime NOT NULL,
+ `modified` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
80 Console/Command/SenderShell.php
@@ -0,0 +1,80 @@
+<?php
+
+App::uses('Shell', 'Console');
+
+class SenderShell extends Shell {
+
+ public function getOptionParser() {
+ $parser = parent::getOptionParser();
+ $parser
+ ->description('Sends queued emails in a batch')
+ ->addOption('limit', array(
+ 'short' => 'l',
+ 'help' => 'How many emails should be sent in this batch?',
+ 'default' => 50
+ ))
+ ->addOption('template', array(
+ 'short' => 't',
+ 'help' => 'Name of the template to be used to render email',
+ 'default' => 'default'
+ ))
+ ->addOption('layout', array(
+ 'short' => 'w',
+ 'help' => 'Name of the layout to be used to wrap template',
+ 'default' => 'default'
+ ))
+ ->addSubCommand('clearLocks', array(
+ 'help' => 'Clears all locked emails in the queue, useful for recovering from crashes'
+ ));
+ return $parser;
+ }
+
+/**
+ * Sends queued emails
+ *
+ * @access public
+ */
+ public function main() {
+ Configure::write('App.baseUrl', '/');
+ $emailQueue = ClassRegistry::init('EmailQueue.EmailQueue');
@jippi
jippi added a note

why not use $uses in the class property?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ $emails = $emailQueue->getBatch($this->params['limit']);
+ foreach ($emails as $e) {
+ $configName = $e['EmailQueue']['config'] === 'default' ? $this->params['config'] : $e['EmailQueue']['config'];
+ $template = $e['EmailQueue']['template'] === 'default' ? $this->params['template'] : $e['EmailQueue']['template'];
+ $layout = $e['EmailQueue']['layout'] === 'default' ? $this->params['layout'] : $e['EmailQueue']['layout'];
+
+ try {
+ $email = new CakeEmail($configName);
+ $sent = $email
+ ->to($e['EmailQueue']['to'])
+ ->template($template, $layout)
+ ->send();
+ } catch (SocketException $e) {
+ $this->err($e->getMessage());
+ $sent = false;
+ }
+
+
+ if ($sent) {
+ $emailQueue->success($e['EmailQueue']['id']);
+ $this->out('<sucess>Email ' . $e['EmailQueue']['id'] . ' was sent</sucess>');
+ } else {
+ $emailQueue->fail($e['EmailQueue']['id']);
+ $this->out('<error>Email ' . $e['EmailQueue']['id'] . ' was not sent</error>');
+ }
+
+ }
+ $emailQueue->releaseLocks(Set::extract('{n}.EmailQueue.id', $emails));
+ }
+
+/**
+ * Clears all locked emails in the queue, useful for recovering from crashes
+ *
+ * @return void
+ **/
+ public function clearLocks() {
+ ClassRegistry::init('EmailQueue.EmailQueue')->clearLocks();
+ }
+
+}
157 Model/EmailQueue.php
@@ -0,0 +1,157 @@
+<?php
+
+App::uses('AppModel', 'Model');
+
+/**
+ * EmailQueue model
+ *
+ */
+class EmailQueue extends AppModel {
+
+/**
+ * Name
+ *
+ * @var string $name
+ * @access public
+ */
+ public $name = 'EmailQueue';
+
+/**
+ * Database table used
+ *
+ * @var string
+ * @access public
+ */
+ public $useTable = 'email_queue';
+
+/**
+ * Stores a new email message in the queue
+ *
+ * @param mixed $to email or array of emails as recipients
+ * @param array $data associative array of variables to be passed to the email template
+ * @param array $options list of options for email sending. Possible keys:
+ *
+ * - send_at : date time sting representing the time this email should be sent at (in UTC)
+ * - template : the name of the element to use as template for the email message
+ * - layout : the name of the layout to be used to wrap email message
+ * - config : the name of the email config to be used for sending
+ *
+ * @return void
+ */
+ public function enqueue($to, array $data, $options = array()) {
+ $defaults = array(
+ 'send_at' => gmdate('Y-m-d H:i:s'),
+ 'template' => 'default',
+ 'layout' => 'default',
+ 'template_vars' => $data,
+ 'config' => 'default'
+ );
+
+ $email = $options + $defaults;
+ if (!is_array($to)) {
+ $to = array($to);
+ }
+
+ foreach ($to as $t) {
+ $email['to'] = $t;
+ $this->create();
+ $this->save($email);
+ }
+ }
+
+/**
+ * Returns a list of queued emails that needs to be sent
+ *
+ * @param integer $size, number of unset emails to return
+ * @return array list of unsent emails
+ * @access public
+ */
+ public function getBatch($size = 10) {
+ $this->getDataSource()->begin();
+
+ $emails = $this->find('all', array(
+ 'limit' => $size,
+ 'conditions' => array(
+ 'EmailQueue.sent' => false,
+ 'EmailQueue.send_tries <=' => 3,
+ 'EmailQueue.send_at <=' => gmdate('Y-m-d H:i'),
+ 'EmailQueue.locked' => false
+ ),
+ 'order' => array('EmailQueue.created' => 'ASC')
+ ));
+ $this->updateAll(array('locked' => true, array('EmailQueue.id' => Set::extract('{n}.EmailQueue.id', $emails))));
+ $this->getDataSource()->commit();
+ return $emails;
+ }
+
+/**
+ * Releases locks for all emails in $ids
+ *
+ * @return void
+ **/
+ public function releaseLocks($ids) {
+ $this->updateAll(array('locked' => false), array('EmailQueue.id' => $ids));
+ }
+
+/**
+ * Releases locks for all emails in queue, useful for recovering from crashes
+ *
+ * @return void
+ **/
+ public function clearLocks() {
+ $this->updateAll(array('locked' => false));
+ }
+
+/**
+ * Marks an email from the queue as sent
+ *
+ * @param string $id, queued email id
+ * @return boolean
+ * @access public
+ */
+ public function success($id) {
+ $this->id = $id;
+ return $this->saveField('sent', true);
+ }
+
+/**
+ * Marks an email from the queue as failed, and increments the number of tries
+ *
+ * @param string $id, queued email id
+ * @return boolean
+ * @access public
+ */
+ public function fail($id) {
+ $this->id = $id;
+ $tries = $this->field('send_tries');
+ return $this->saveField('send_tries', $tries + 1);
+ }
+
+/**
+ * Converts array data for template vars into a json serialized string
+ *
+ * @return void
+ **/
+ public function beforeSave() {
+ if (isset($this->data[$this->alias]['template_vars'])) {
+ $this->data[$this->alias]['template_vars'] = json_encode($this->data[$this->alias]['template_vars']);
+ }
+ }
+
+/**
+ * Converts template_vars back into a php array
+ *
+ * @return void
+ **/
+ public function afterFind($results, $primary) {
+ if (!$primary) {
+ return $results;
+ }
+
+ foreach ($results as &$r) {
+ $r[$this->alias]['template_vars'] = json_decode($r[$this->alias]['template_vars'], true);
+ }
+ return $results;
+ }
+
+}
79 README.md
@@ -0,0 +1,79 @@
+# Interactive shell for CakePHP #
+
+This plugin provides an interface for creating emails on the fly and
+store them in a queue to be processed later by an offline worker using a
+cakephp shell command.
+
+## Requirements ##
+
+* CakePHP 2.x
+
+## Installation ##
+
+There are a few ways you can choose for intalling this plugin:
+
+_[Manual]_
+
+* Download this: [https://github.com/nodesagency/cake-interactive-shell/zipball/master](https://github.com/nodesagency/cakephp-email-queue/zipball/master)
@AD7six
AD7six added a note

Do we have a convention for naming the repos of our plugins?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+* Unzip that download.
+* Copy the resulting folder to `app/Plugin`
+* Rename the folder you just copied to `EmailQueue`
+
+_[GIT Submodule]_
+
+In your app directory type:
+
+ git submodule add git://github.com/nodesagency/cakephp-email-queue.git Plugin/EmailQueue
+ git submodule init
+ git submodule update
+
+_[GIT Clone]_
+
+In your plugin directory type
+
+ git clone git://github.com/nodesagency/cakephp-email-queue.git Plugin/EmailQueue
+
+### Enable plugin
+
+In 2.0 you need to enable the plugin your `app/Config/bootstrap.php` file:
+
+ CakePlugin::load('EmailQueue');
+
+If you are already using `CakePlugin::loadAll();`, then this is not necessary.
+
+### Load required database table
+
+In order to use this plugin, you need to create a database table.
+Required SQL is located at
+
+ # Config/Schema/email_queue.sql
+
+Just load it into your database.
+
+## Usage
+
+Whenever you need to send an email, use the EmailQueue model to create
+and queue a new one by storing the correct data:
+
+ ClassRegistry::init('EmailQueue.EmailQueue')->enqueue($to, $data, $options);
+
+`enqueue` method receives 3 arguments:
+
+- First argument is a string or array of email addresses that will be treated as recipients.
+- Second arguments is an array of view variables to be passed to the
+ email template
+- Third arguments is an array of options, possible options are
+ * `send_at` : date time sting representing the time this email should be sent at (in UTC)
+ * `template` : the name of the element to use as template for the email message
+ * `layout` : the name of the layout to be used to wrap email message
+ * `config` : the name of the email config to be used for sending
+
+### Sending emails
+
+Emails should be sent using bundled Sender command, use `-h` modifier to
+read available options
+
+ # Console/cake EmailQueue.Sennder -h
+
+You can configure this command to be run under a cron or any other tool
+you wish to use.

0 comments on commit 8919504

Please sign in to comment.
Something went wrong with that request. Please try again.