Sending e-mails from shells

E-mail sending is not a task that requires any interaction by visitors to our web applications, so it is pointless to make them wait for their delivery, which is exactly what we would do if we were to send an e-mail from a controller's action.

Deferring e-mail sending to a shell makes real sense both from a performance perspective and from the administrator point of view, as we may also add the ability to re-send failed e-mails.

This recipe uses the Email component provided by CakePHP to send a fictitious newsletter, adding the ability to test the sending process through a shell parameter.

Getting ready

To go through this recipe we need some data to work with. Create a subscribers table with the following SQL statement:

CREATE TABLE `subscribers`(
`id` INT UNSIGNED AUTO_INCREMENT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);

Create a newsletters table with the following statement:

CREATE TABLE `newsletters`(
`id` INT UNSIGNED AUTO_INCREMENT NOT NULL,
`title` VARCHAR(255) NOT NULL,
`body` TEXT NOT NULL,
`sent` TINYINT(1) UNSIGNED NOT NULL default 0,
PRIMARY KEY(`id`)
);

Create a newsletters_subscribers table with the following statement:

CREATE TABLE `newsletters_subscribers`(
`id` INT UNSIGNED AUTO_INCREMENT NOT NULL,
`newsletter_id` INT UNSIGNED NOT NULL,
`subscriber_id` INT UNSIGNED NOT NULL,
`sent` TINYINT(1) UNSIGNED NOT NULL default 0,
PRIMARY KEY(`id`)
);

Now add some sample data to these tables with the following statements:

INSERT INTO `subscribers`(`name`, `email`) VALUES
('John Doe', '[email protected]'),
('Jane Doe', '[email protected]'),
INSERT INTO `newsletters`(`title`, `body`) VALUES
('My first newsletter', 'This is the body for <strong>my first newsletter</strong>'),

Create a file named newsletter.php and place it in your app/models folder, with the following contents:

<?php
class Newsletter extends AppModel {
public $hasMany = array('NewslettersSubscriber'),
}
?>

How to do it...

Create a file named email.php and place it in your app/vendors/shells, with the following contents:

<?php
App::import('Component', 'Email'),
class EmailShell extends Shell {
public $uses = array('Newsletter', 'Subscriber'),
public function startup() {
$this->Email = new EmailComponent();
$this->Email->delivery = 'smtp';
$this->Email->smtpOptions = array(
'host' => 'smtp.email.com',
'username' => 'smtpUser',
'password' => 'smtpPassword'
);
}
public function main() {
$email = !empty($this->params['to']) ? $this->params['to'] : array();
$newsletter = $this->Newsletter->find('first', array(
'conditions' => array('sent' => false),
'recursive' => -1
));
if (empty($newsletter)) {
$this->out('All newsletters have been sent'),
$this->_stop();
}
$this->out('Sending newsletter "'.$newsletter['Newsletter']['title'].'"'),
$subscribers = $this->Subscriber->find('all'),
foreach($subscribers as $subscriber) {
$this->out('Sending to '.$subscriber['Subscriber']['email'].'... ', false);
$currentEmail = !empty($email) ? $email : $subscriber['Subscriber']['email'];
if (!empty($email)) {
$this->Email->headers['Destination'] = $subscriber['Subscriber']['email'];
}
$this->Email->sendAs = 'html';
$this->Email->subject = $newsletter['Newsletter']['title'];
$this->Email->from = 'My Application <[email protected]>';
$this->Email->to = $subscriber['Subscriber']['name'] . ' <'.$currentEmail.'>';
$sent = $this->Email->send($newsletter['Newsletter']['body']));
if ($sent) {
$this->out('DONE'),
} else {
$error = !empty($this->Email->smtpError) ? $this->Email->smtpError : '';
$this->out('ERROR' . (!empty($error) ? ': '.$error : ''));
}
$this->Newsletter->NewslettersSubscriber->create(array(
'newsletter_id' => $newsletter['Newsletter']['id'],
'subscriber_id' => $subscriber['Subscriber']['id'],
'sent' => $sent
));
$this->Newsletter->NewslettersSubscriber->save();
$this->Email->reset();
}
$this->Newsletter->id = $newsletter['Newsletter']['id'];
$this->Newsletter->saveField('sent', true);
}
}
?>

Make sure to change the following lines in the startup() function to match your settings:

$this->Email->delivery = 'smtp';
$this->Email->smtpOptions = array(
'host' => 'smtp.email.com',
'username' => 'smtpUser',
'password' => 'smtpPassword'
);

If you wish to use PHP's mail() function instead of SMTP, change the delivery property of the Email component to mail. Once configured, you can run the shell with the following command to force all e-mails to go to your specific address (in this case, [email protected]):

$ cake/console/cake email -to [email protected]

As all e-mails are forced to be sent to [email protected] through the use of the shell parameter, we need a way to tell if each e-mail will be going to the real e-mail address. Use your e-mail program to view the headers of the e-mail, and you will notice the following header lines:

To: John Doe <[email protected]>
From: My Application <[email protected]>
Subject: My first newsletter
X-Mailer: CakePHP Email Component
X-Destination: [email protected]

From these headers we can tell that the X-Destination header is set to the address to which the e-mail was originally intended for.

How it works...

The EmailShell starts by implementing the startup() method, which is called before any shell command or its entry method is executed. In this method, we create an instance of the Email component. Once we have the instance, we configure its delivery settings through the properties delivery and smtpOptions.

The entry method main() checks to see if the to parameter is given. If so, this will be the e-mail to which all e-mails will be sent to, a basic way to test the sending process. It continues to fetch the first newsletter that has not yet been sent, and the list of subscribers that should receive the newsletter.

For each of those subscribers, we set the appropriate properties of the Email component:

Property

Purpose

sendAs

Type of e-mail to send. Can be text, html, or both. We set it to html to specify that we are sending an HTML-only e-mail.

subject

The subject of the e-mail.

from

The address sending the e-mail.

to

The destination address. If the parameter is provided, this is the e-mail to send to, otherwise we use the e-mail of the subscriber.

Finally, we proceed to send the actual e-mail through the component's send() method, informing the user of the result of the operation, and resetting the e-mail contents with the component's reset() method prior to the next loop in the for operation. We end the shell by marking that the newsletter is sent.

See also

  • Sending an e-mail in Chapter 11, Utility Classes and Tools
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.186.79