Skip to main content

Understanding the Zend Framework, Part 6: Sending e-mail

Gina Deol (gdeol@binaryits.com), Vice President, E-commerce Development, Binary IT Solutions
Gina Deol is in her last year as a bachelor's of science student in computer science and accounting information systems at Sacramento State University. She has worked as a Web development engineer and project coordinator, and she is currently vice president of e-commerce development for Binary IT Solutions, based in Sacramento.

Summary:  Earlier installments in this "Understanding the Zend Framework" series use the PHP Zend Framework to create the basic Chomp online feed reader. Now it's time to add e-mail to the mix. This article explains how to use the Zend_Mail component to send text and HTML e-mail alerts to users when new content has been added to subscribed feeds.

View more content in this series

Date:  15 Aug 2006
Level:  Intermediate
Activity:  2888 views

Prerequisites

The sample application used in this series is built using V0.1.3 of the Zend Framework for PHP. Prerequisites for the Zend Framework include PHP V5 and the Apache Web server. For detailed installation instructions, see Part 2.

To follow along, you should have a basic idea of programming in PHP. You should also have the code from earlier parts of the "Understanding the Zend Framework" series.


Introduction

In Part 1, we talk about the overall concepts of the Zend Framework, including a list of relevant classes and a general discussion of the MVC pattern. In Part 2, we expand on that to show how MVC can be implemented in a Zend Framework application. We also create the user registration and login process, adding user information to the database and pulling it back out again.

Parts 3 and 4 deal with the actual RSS and Atom feeds. In Part 3, we enable users to subscribe to individual feeds and to display the items listed in those feeds. We also discuss some of the Zend Framework's form-handling capabilities, validating data, and sanitizing feed items. Part 4 explains how to create a proxy to pull data from a site that has no feed.

The rest of the series involves adding value to the Chomp application. Part 5 explains how to use the Zend_PDF module to enable the user to create a customized PDF of saved articles, images, and search results. Here in Part 6, we use the Zend_Mail module to alert users to new posts. In Part 7, we look at searching saved content and returning ranked results. In Part 8, we create our own mash-up, adding information from Amazon, Flickr, and Yahoo! And in Part 9, we add Ajax interactions to the site using JavaScript object notation.

In this article, we will be sending two types of e-mail messages. The first is a simple text-based e-mail that simply lists the feeds to which a user has subscribed that have been updated since the user last read them. The second is an e-mail that actually sends the new posts that have arrived since the last time the user read a particular feed.

We will first update the database so every feed has an accurate last-updated value. We'll also update subscribed feeds with a last-pulled value when the reader views them. Finally, we'll create the routine that compares these dates, creates the e-mail messages, and sends them.


A brief introduction to e-mail

An e-mail message's structure is composed of the message header and the message body. The message header includes the information about the message, such as the receiver, sender, date, message ID, sender server IP address, routing information, and the Multipurpose Internet Mail Extensions (MIME) type. The MIME type identifies the message type as being a simple text message, HTML message, or message with attachment(s). The message body is simply the text of the message that can extend over several lines and may contain attachments.

E-mail is sent via mail transfer agents, mail delivery agents, and mail user agents. The mail transfer agent is also known as the mail server and is simply software that sends e-mail from one computer to another. The mail transfer agent receives the messages from another mail transfer agent, a mail user agent, or a mail submission agent. The mail delivery agent is a computer program that accepts incoming messages and forwards them to a destination on a remote server called a Simple Mail Transfer Protocol (SMTP) server. If the destination account is on a local machine, the mail delivery agent distributes the messages directly to the intended recipient's mailbox. A mail user agent is software used to read and send messages, such as Mozilla Thunderbird or Microsoft® Outlook®.


The Zend_Mail component

Even though it is relatively young, the Zend Framework has developed Zend_Mail with some very important features, such as the ability to add multiple recipients or to send multiple e-mail messages on one SMTP connection. Zend_Mail can also send HTML e-mail or create multipart e-mail messages for providing a text alternative to HTML. You can also send advanced messages with MIME attachments, custom mail headers, and controlled character sets. Zend_Mail comes with support for 8-bit, base64, binary, quotable-printable encoding, and alternative transports, such as POP3 and IMAP, as well as the ability to read received e-mail.

The Zend_Mail component includes:

  • Zend_Mail_Message
  • Zend_Mail_Abstract
  • Zend_Mail_Mbox
  • Zend_Mail_POP3
  • Zend_Mail_Transport_POP3
  • Zend_Mail_Maildir
  • Zend_Mail_Imap
  • Zend_Mail_Transport_Imap

Database changes

Here you need to add a lastpulled column to the subscribedfeeds table and a lastupdated to the feeds table. To do that, start the MySQL console and type the following:

alter table feeds add lastupdated datetime;
alter table subscribedfeeds add lastpulled datetime;

Modification in FeedController->viewChannelAction()

In FeedController->viewChannelAction(), you will update the lastpulled field with current date to keep the date when it is viewed last by the current user.


Listing 1. Updating the lastpulled value
                
<?php
    public function viewChannelAction()
    {
        $filterSession = Zend::registry('fSession');
        $username = $filterSession->getRaw('username');

        $filterGet = Zend::registry('fGet');
        $feedTitle = $filterGet->getRaw('title');

        $db = Zend::registry('db');
        $select = $db->select();
        $select->from('feeds', '*');
        $select->where('feedname=?', $feedTitle);
        $results = $db->fetchAll($select);

        $curDate = date("Y-m-d H:i:s")
        $table = 'subscribedfeeds';
        $row = array(
             'lastpulled' => $curDate
             );

        $where = "username='$username' and feedname='$feedTitle'";
        $rowsAffected = $db->update($table, $row, $where);

        $feedLink = $results[0]['link'];
        $rssFeed = Zend_Feed::import($feedLink);
        
        $view = Zend::registry('view');
        $view->title = $feedTitle;
        $view->rssFeed = $rssFeed;
        echo $view->render('viewChannel.php');
    }
?>

To update the subscribedfeeds table, we need to know the username for the logged-in user, so we grab that from the session. Next, we create a string for the current date and use it to create a row of information to be used to update the subscribedfeeds table. Notice that a row used in an update does not have to contain all of the columns in the table; only those included will be used to update the table. We then create the query (in other words, the where clause) and execute the update.


Updating feed data

The next step is to make sure we can tell when a feed was last updated. To do that, create a new PHP file called sendUpdates.php. (It may be simplest to place the file in your <ZEND_HOME>/library directory.) Your first instinct may be to place the functionality in the IndexController, but it is better as a separate file for two reasons: First, we'll want to execute the file on a regular basis, such as once a day, using cron, Windows scripting host, or a similar scheduling technology. Second, taking it out of the Web application means you won't have to worry about unauthorized Web users executing it.

Start by searching the database for all RSS- or Atom-type feeds (as opposed to HTML pages).


Listing 2. Finding the feeds
                
<?php
include 'Zend.php';

function __autoload($class)
{
    Zend::loadClass($class);
}

$params = array ('host' => 'localhost',
                 'username' => 'chompuser',
                 'password' => 'chomppassword',
                 'dbname'   => 'chomp');
$db = Zend_Db::factory('pdoMysql', $params);

$select = $db->select();
$select->from('feeds', '*');
$select->where("rss = 'true'");
$results = $db->fetchAll($select);
foreach($results as $row)
{
    echo $row['link']."\n";
}
?>

We connect to the database and creative execute the query. For each feed, we output the URL. To execute this file, go to the command line, change to the directory in which you saved the file, and type php sendUpdates.php.


Listing 3. Getting the dates
                
<?php
include 'Zend.php';

function __autoload($class)
{
    Zend::loadClass($class);
}

$params = array ('host' => 'localhost',
                 'username' => 'chompuser',
                 'password' => 'chomppassword',
                 'dbname'   => 'chomp');
$db = Zend_Db::factory('pdoMysql', $params);

$select = $db->select();
$select->from('feeds', '*');
$select->where("rss = 'true'");
$results = $db->fetchAll($select);
foreach($results as $row)
{
    echo $row['link']."\n";
    $lastUpdated = null;

    try {
        $thisFeed = Zend_Feed::import($row['link']);
        $lastUpdated = null;

        if ($thisFeed->pubDate())
        {
            //echo "Feed last updated at ".$thisFeed->pubDate()."\n";
            $lastUpdated = $thisFeed->pubDate();
        } 
        else 
        {
            if (isset($thisFeed[0]))
            {
                $firstItem = $thisFeed[0];
                if ($firstItem->pubDate()){
                    //echo "Most recent item on
 ".$firstItem->pubDate()."\n";
                    $lastUpdated = $firstItem->pubDate();
                } 
                else 
                {
                    if ($firstItem->published())
                    {
                        //echo "Most recent atom item on
 ".$firstItem->published()."\n";
                        $lastUpdated = $firstItem->published();
                    } 
                    else 
                    {
                        //echo "No date information available.\n";
                        $lastUpdated = null;
                    }
                }
            }
        }
    } 
    catch (Zend_Feed_Exception $e) 
    {
        //echo "Exception caught importing feed: {$e->getMessage()}\n";
    }

    //echo "Last updated is ".$lastUpdated."<br />";
    if (isset($lastUpdated)){
        $lastUpdated = strtotime($lastUpdated);
        $lastUpdated = date('Y-m-d H:i:s', $lastUpdated);
    }
    $table = 'feeds';
    $updateRow = array('lastupdated' => $lastUpdated);
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}
?>

First, we're loading the actual XML feed. Assuming we're dealing with RSS, we first check for an overall pubDate for the feed. Because this value is optional, we may need to fall back on checking the most recently posted item for its pubDate. Finally, if this is an Atom feed, we'll request the published date, instead. Once we have the day, we can extract it in the appropriate format and update the row in the database.


Finding updated feeds

Now that we know each field has been updated, we can send users e-mail messages, letting them know if their favorite feeds have been updated. To do this, you will compare the lastupdated field for table feeds and the lastpulled field of table subscribedfeeds, and send e-mail messages to users for newer feeds. Add the code below to sendUpdates.php.


Listing 4. Finding updated fields
                
...
    $table = 'feeds';
    $updateRow = array('lastupdated' => $lastUpdated);
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}
$select = $db->select();
$select->from('users, subscribedfeeds, feeds', '*');
$select->where('users.Username=subscribedfeeds.username');
$select->where('feeds.feedname=subscribedfeeds.feedname');
$select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
$results = $db->fetchAll($select);
foreach($results as $row)
{
    $email = $row['emailaddress'];
    $fName = $row['firstname'];
    $lName = $row['lastname'];
    $feedName = $row['feedname'];

    //send e-mail here
}
?>

We are fetching data from the database after comparing the lastupdated field for table feeds and the lastpulled field of table subscribedfeeds.

Now it's time to actually send the e-mail messages.


Sending messages

Zend_Mail, supported by Zend_Mime, creates and sends e-mail messages with the format text or HTML. Zend_Mime is a support set for handling multipart MIME messages. It is used not only by Zend_Mail but also by any other applications requiring MIME support. Zend_Mime has a set of methods specified to work with MIME:

  • boundary(), which returns the MIME boundary string, used to separate parts of the message
  • boundaryLine(), which returns the complete MIME boundary line
  • encodeBase64(), which encodes a string into base64 encoding
  • encodeQuotablePrintable(), which encodes a string with quotable-printable mechanism
  • isPrintable(), which returns TRUE if the given string contains no unprintable characters and returns FALSE if the given string does contain printable characters
  • mimeEnd(), which returns the complete MIME end boundary line

Zend_Mail allows e-mail to be sent in an easy form while preventing mail injection. Zend_Mail messages can be sent via SMTP, so Zend_Mail_Transport_SMTP needs to be created and registered with Zend_Mail before the send() method is called. Add the following code to sendUpdates.php.


Listing 5. Sending the e-mail messages
                
...
    $table = 'feeds';
    $updateRow = array('lastupdated' => $lastUpdated);
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}

require_once 'Zend/Mail/Transport/Smtp.php';
$tr = new
Zend_Mail_Transport_Smtp('mail.example.com');
Zend_Mail::setDefaultTransport($tr);
$select = $db->select();
$select->from('users, subscribedfeeds, feeds', '*');
$select->where('users.Username=subscribedfeeds.username');
$select->where('feeds.feedname=subscribedfeeds.feedname');
$select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
$results = $db->fetchAll($select);
foreach($results as $row)
{
    $email = $row['emailaddress'];
    $fName = $row['firstname'];
    $lName = $row['lastname'];
    $feedName = $row['feedname'];

    $mail = new Zend_Mail ();
    $mail->setFrom ('alerts@chomp.backstopmedia.com', 'Chomp! Alerts');
    $mail->addTo ($email, "$fName $lName");
    $mail->setSubject ('Your feeds have been updated');
    $mail->setBodyText ("One of your subscribed feeds, $feedName, ".
                        "has been updated.");
    $mail->send ();
}
?>

The first step is to set the transport for the message. This enables you to specify a different SMTP server than the one listed in your PHP.ini file. Next, we create the actual Zend_Mail object and populate. Here, we are setting the to and from values, as well as the subject and body. In this case, we are specifying only a text body. In a moment, we will look at sending HTML and multipart messages.

Finally, we actually send the message. Note that the way this routine is structured, we are actually sending an e-mail for each feed that has been updated. Obviously, this would not be convenient for users who have multiple feeds.

Let's look at sending only one e-mail per user and how to use HTML capabilities to send individual item links.


Sending HTML e-mail messages

The final step in our updates routine is to change the process so that rather than sending a user a new e-mail for every feed that has been updated, we send only one e-mail if any of the feeds have been updated. We'll also add links to the actual items, themselves.

Start by changing the single query to two queries, with the outside query looping through each user.


Listing 6. Breaking it up by user
                
...
    $where = "link = '".$row['link']."'";
    $rowsAffected = $db->update($table, $updateRow, $where);
}

require_once 'Zend/Mail/Transport/Smtp.php';
$tr = new
Zend_Mail_Transport_Smtp('mail.example.com');
Zend_Mail::setDefaultTransport($tr);

$userSelect = $db->select();
$userSelect->from('users', '*');
$userResults = $db->fetchAll($userSelect->__toString());
foreach ($userResults as $thisUserRow){

    $thisUser = $thisUserRow['username'];
    $email = $thisUserRow['emailaddress'];
    $fName = $thisUserRow['firstname'];
    $lName = $thisUserRow['lastname'];

    $select = $db->select();
    $select->from('subscribedfeeds, feeds', '*');
    $select->where("subscribedfeeds.username='$thisUser'");
    $select->where('feeds.feedname=subscribedfeeds.feedname');
    $select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
    $results = $db->fetchAll($select->__toString());

    $bodytext = "";

    foreach($results as $row)
    {

        $feedName = $row['feedname'];
        $bodytext = $bodytext . "One of your subscribed feeds, " .
                    $feedName . ", "has been updated.\n";

    }

    $mail = new Zend_Mail ();
    $mail->setFrom ('alerts@chomp.backstopmedia.com', 'Chomp! Alerts');
    $mail->addTo ($email, "$fName $lName");
    $mail->setSubject ('Your feeds have been updated');
    $mail->setBodyText ($bodytext);
    $mail->send ();

}
?>

We retrieve all of the user information and key the feed search off this user. We then move the actual sending of the mail outside this loop, instead building a string of text to add to the body of the message. When the script has looked at all of the feeds for this user, it creates the e-mail and sends it off.

Now we just have to add the actual feed items.


Listing 7. Adding the feed items
                
...
    $select->where('feeds.lastupdated > subscribedfeeds.lastpulled');
    $results = $db->fetchAll($select->__toString());

    $bodytext = "";
    $bodyhtml = "";

    foreach($results as $row)
    {

        $feedName = $row['feedname'];
 
        $bodytext = $bodytext . '\n' . $feedName . " has new items:\n\n";
        $bodyhtml = $bodyhtml . '<p>' . $feedName . " has new
 items:</p>";

        $thisFeed = Zend_Feed::import($row['link']);
        foreach($thisFeed as $thisItem){
           $thisItemDate = null;
           if ($thisItem->pubDate())
           {
               $thisItemDate = $thisItem->pubDate();
           } 
           else 
           {
               $thisItemDate = $thisItem->published();
           } 
           if ($thisItemDate > $row[lastpulled]){
               $bodytext = $bodytext . $row['feedname'].'\n';
               $bodyhtml = $bodyhtml . '<a
 href="'.$row['link'].'">'.$row['feedname'].'</a><br />';
           }
        }
    }

    $mail = new Zend_Mail ();
    $mail->setFrom ('alerts@chomp.backstopmedia.com', 'Chomp! Alerts');
    $mail->addTo ($email, "$fName $lName");
    $mail->setSubject ('Your feeds have been updated');
    $mail->setBodyText ($bodytext);
    $mail->setBodyHtml ($bodyhtml);

    $mail->send ();

}
?>

In this case, we create a message that sends a text and an HTML version of the message, so we create variables to hold both strings. Then, for each feed, we import the feed, looping through each of its items to see if any were posted after the last time the user read the feed, as defined by the lastpulled column. If any items satisfy this qualification, we add them to the body of the message. For the text body, we add only the name, while we also add a link to the item for the HTML version.

When we are ready to send the actual message, we populate the text as we did before, but we also populate the HTML version using the setBodyHtml() method.


Summary

This article discusses how we can use the Zend_Mail module for a variety of functions. We used it to alert users to new posts in their subscribed feeds and to send those posts using HTML e-mail. In a production application, you will likely find many uses for this capability, such as sending out password reminders and periodic subscriber mailings.

In Part 7 of this "Understanding the Zend Framework" series, we look at using the Zend Framework's search capabilities.



Download

DescriptionNameSizeDownload method
Part 6 source codeos-php-zend6.zip3KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Gina Deol is in her last year as a bachelor's of science student in computer science and accounting information systems at Sacramento State University. She has worked as a Web development engineer and project coordinator, and she is currently vice president of e-commerce development for Binary IT Solutions, based in Sacramento.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=153263
ArticleTitle=Understanding the Zend Framework, Part 6: Sending e-mail
publish-date=08152006
author1-email=gdeol@binaryits.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers