Qafoo GmbH - passion for software quality

Help you and your team benefit from new perspectives on cutting-edge quality engineering techniques and tools through the Qafoo team weblog.

By Kore Nordmann, first published at Thu, 25 Nov 2010 14:04:55 +0100

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Apache Zeta Components: Doing mail right

Sending and receiving mail is a regular, but often cumbersome task. Especially, when it comes to more complex mails than just plain text. HTML and alternate text parts, embedded images, attachments and digests can make your brain spin if you need to do it yourself. The Apache Zeta Mail component makes it easy for you to send such mails, but also to receive them! In this article I show you how to send HTML emails with embedded images and how to receive all mails from an IMAP server and forward them in a single mail, using a digest.

In following, I provide you with three more advanced examples for uses of the Zeta Mail component. To get an insight into further possibilities and full API docs, please refer to the documentation of the Zeta Mail component. The example sources are also provided in our Github repository, so you can grab them easily.

Getting started

Want to be backed by a professional company when using Zeta Components? Buy Qafoo blanket our per ticket support!

To get started with Apache Zeta Mail, you need to install (at least) the Zeta Base component and of course the Mail component. At the current state, Apache Zeta Components do not have an official release, yet. So you can either use the most recent eZ Components release or check out the components from the Zeta SVN. I won't go into detail on this procedure, since it is pretty well documented on our website. Same applies to our autoload mechanism, for which you have multiple choices to fit it into your environment.

So, let's get straight to the point …

Sending HTML email

Using the Zeta Mail component you can send email composed of almost arbitrary MIME parts, so sending HTML email with embedded images is an easy task:

if ( !isset( $argv[1] ) ) { die( "Missing image file to send.\n" ); } if ( ( $imagePath = realpath( $argv[1] ) ) === false ) { die( "Image file '{$argv[1]}' not found.\n" ); } require_once 'Base/src/ezc_bootstrap.php'; $mail = new ezcMail(); $mail->from = new ezcMailAddress( 'somebody@example.com', 'Some Body' ); $mail->addTo( new ezcMailAddress( 'anybody@example.com', 'Any Body' ) ); $mail->subject = 'A pic from Some Body'; $textPart = new ezcMailText( 'This email contains HTML content.' . 'Please enable viewing HTML to fully enjoy it.' ); $htmlPart = new ezcMailText( '<html>' . '<h1>Some Body wants you to see this image</h1>' . '<img src="cid:included_image"/> . '</html>' ); $htmlPart->subType = 'html'; $imagePart = new ezcMailFile( $imagePath ); $imagePart->contentId = 'included_image'; $mail->body = new ezcMailMultipartAlternative( $textPart, new ezcMailMultipartRelated( $htmlPart, $imagePart ) ); $transport = new ezcMailMtaTransport(); $transport->send( $mail );

This is a simple script you could use on your server to get uploaded images sent to you as HTML mails. After some checks for the shell parameter, the Zeta Components autoload is included using our bootstrap file.

Now an instance of ezcMail is created, which represents an email. Of course an email needs a from field. Email addresses are covered in instances of ezcMailAddress in order to specify the address itself and an optional name. the ezcMail::addTo() method can be called multiple times in order to add recipients (of course addCc() and addBcc() exist, too). The subject is defined intuitively.

Do you miss a feature in Apache Zeta Components? Qafoo experts offer you payed development at fair rates!

So, if you are sending HTML email, it is good practice to at least include alternative plain text content which is displayed by the email client if it cannot handle HTML email or if the user simply switched it off. Therefore, an ezcMailText object is created first. The ezcMailText class (as the ezcMail class itself) extends ezcMailPart and can therefore be used to compose email bodies.

HTML is nothing really more than plain text, but a corresponding MIME type must be indicated in the email source. Therefore the $htmlPart needs the subType property to be set accordingly. To include an image from a file, you need a ezcMailFile part. If you now want to reference this part in the HTML part, you need to give it a contentId (CID). You know with IDs they may be somehow arbitrary, but excluding white spaces and special chars is a good idea. The HTML part already used the CID of the included image in the src attribute of the img tag to reference it.

Now all elements of the desired email have been created and the body can be composed: Since plain text and html parts should be alternatives, an ezcMailMultipartAlternative part is used to wrap them in the body. The HTML part is in addition related to the file part (the image) and is therefore put into an according container to reflect this.

Now the ezcMail object is ready for sending. In order to do this, you need an instance of ezcMailTransport. In the example, ezcMailMtaTransport is used, which sends mails using PHPs mail() function. Alternatively, you could connect to an SMTP server directly, using the ezcMailSmtpTransport. The latter one uses PHPs socket API for connection, not an SMTP extension.

You see, sending HTML email with embedded images can be so easy …

Receiving mail

While there are many mail sending components for PHP out there, libraries to retrieve and parse mail are no that common. The Zeta Mail component allows you to do both. So, maybe you remember the times before smart phones, when you were lucky to have your phone support email? The following script reads all mails from an IMAP inbox and creates imaginary blog entries from them:

require_once 'Base/src/ezc_bootstrap.php'; require_once 'blog_entry.php'; require_once 'blog_entry_creator.php'; $imapOptions = new ezcMailImapTransportOptions(); $imapOptions->ssl = true; $imap = new ezcMailImapTransport( 'example.com', 993, $imapOptions ); $imap->authenticate( 'somebody@example.com', 'foo23bar' ); $imap->selectMailbox( 'Inbox' ); $messageSet = $imap->fetchAll(); $parser = new ezcMailParser(); $mails = $parser->parseMail( $messageSet ); $blogEntryCreator = new qaBlogEntryCreator(); foreach ( $mails as $mail ) { $entry = $blogEntryCreator->createEntry( $mail ); $entry->save(); }

The initial two require_once statements include custom classes needed in the example. These are shown below, as they are needed. In order to connect to an IMAP server with SSL encryption an options object is first created and the corresponding option is set. After that, an instance of ezcMailImapTransport is used for the actual connection. This object communicates with the server through sockets and therefore does not need any special extension. You see some of its possibilities in action in the example, but there are far more. Look them up in the IMAP Transport documentation when you need them.

Do you want you and your colleagues to become experts in using Zeta Components? Qafoo offers you a highly customized training!

Of course you need to authenticate against the server and select a mailbox to browse. The fetchAll() method of the IMAP transport returns a so-called mail set object, which abstracts reading mails from IMAP. You can create similar sets from e.g. mbox files. The ezcMailParser object is responsible for parsing such a set of mails into ezcMail objects. Exactly, they return you the very same data structure you use for creating mails, isn't that cute? You can see what more you can do with this in the next example.

After parsing the mails into an array of ezcMail instances, we use the custom qaBlogEntryCreator class to process them:

class qaBlogEntryCreator { protected $entry; public function createEntry( ezcMail $mail ) { $this->entry = new qaBlogEntry(); $this->entry->setSubject( $mail->subject ); $walkContext = new ezcMailPartWalkContext( array( $this, 'walkPart' ) ); $walkContext->filter = array( 'ezcMailText', 'ezcMailFile' ); $mail->walkParts( $walkContext, $mail ); return $this->entry; } public function walkPart( ezcMailPartWalkContext $context, ezcMailPart $part ) { switch ( true ) { case ( $part instanceof ezcMailFile ): $this->entry->addImage( $part->contentId, $part->fileName ); break; case ( $part instanceof ezcMailText ): if ( $part->subType === 'html' ) { $this->entry->setContent( $part->text ); } break; } } }

The createEntry() method is used in the example to create a blog entry from a mail object. It creates a new, rudimentary blog entry object and sets the subject for the blog entry from the emails subject.

After that, a so-called walk context is created. This context determines a callback to which processing of a mail part (remember mail parts from the last example?) is delegated while the parts of a mail are walked recursively. In addition to that, the $walkContext is provided with a filter, determining a whitelist for interesting mail parts. For the blog entry we need an HTML content part and potential files. Walking the mail parts recursively is done by calling the mails walkParts() method with the created context and a root part to start with.

Do you want to learn professional object oriented design? Rent a Qafoo expert for your training.

During the walk, the qaBlogEntryCreator->walkPart() method is called for every ezcMailText and ezcMailFile part. Inside this method, files are added as images to the blog entry, using their content ID and file path. The attached files do already reside on your disk at this stage, so file path points into a temp dir (specified through options or system default). The content ID is needed to replace the <img src="…"/> parts of the HTML content in reverse order to what you saw in the first example. The HTML content can simply be extracted.

Sending a digest

The last example in this blog entry shows how to combine receiving and sending email using the Zeta Mail component. Imagine you have a dedicated mail box for support issues, which is maintained by some of your colleagues. You don't want to be bothered by every single mail, but since you are curious, you want to receive a digest with all mails received during the day in the evening. Nothing easier than that:

require_once 'Base/src/ezc_bootstrap.php'; $imapOptions = new ezcMailImapTransportOptions(); $imapOptions->ssl = true; $imap = new ezcMailImapTransport( 'example.com', 993, $imapOptions ); $imap->authenticate( 'somebody@example.com', 'foo23bar' ); $imap->selectMailbox( 'Inbox' ); $mailSet = $imap->fetchAll(); $parser = new ezcMailParser(); $retMails = $parser->parseMail( $mailSet ); $mail = new ezcMail(); $mail->from = new ezcMailAddress( 'somebody@example.com' ); $mail->addTo( new ezcMailAddress( 'anybody@example.com', 'Any Body' ) ); $mail->subject = 'Daily digest'; $digest = new ezcMailMultipartDigest(); foreach ( $retMails as $retMail ) { $digest->appendPart( new ezcMailRfc822Digest( $retMail ) ); } $mail->body = $digest; $transport = new ezcMailMtaTransport(); $transport->send( $mail );

The first part is already known from the last example:

  1. Connect to the IMAP server

  2. authenticate and select the mailbox

  3. receive and parse the mails.

In order to send a digest, you of course need a new ezcMail object with corresponding from, to and subject information. However, this email does not contain a plain text, HTML or combined body, but a digest. This digest is represented as an instance of ezcMailMultipartDigest. To this base body part, all the retrieved mails are then added as digest parts (ezcMailRfc822Digest) and the part is simply set as the body of the mail.

Ready to go, the mail is sent in the same way is seen in example 1.

Conclusion

I hope you saw that the Zeta Mail component is a very powerful tool for sending and receiving mails. If you are in the need of sending or processing mails the next time, just give it a try.

Of course, if you are keen on mail and have a cool idea for the component or miss a feature, please join us in the Apache Zeta Components project over at the Apache foundation!

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Get Technical Insights With Our Newsletter

Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.

Comments

  • Stephen S. Musoke on Mon, 29 Nov 2010 05:55:48 +0100

    How does the ZetaMail compare to PHPMailer and SwiftMailer, similar examples:

    - PHPMailer - http://phpmailer.worxware.com/index.php?pg=examplebmail

    - Swift Mailer - http://swiftmailer.org/docs/message-quickref

  • Tobias Schlitt on Wed, 01 Dec 2010 10:52:57 +0100

    I don't know much about neither of them. However, I had a quick look at the code of both of them.

    PHPMailer looks much more monolithic and inflexible than Zeta Mail. Zeta Mail follows OOP principles quite good, while PHPMailer mainly consists of 3 huge classes (the main class source file is ~73kb). In addition, PHPMailer seems to miss mail receiving and parsing functionality, as far as I could see.

    The Swift Mailer code seems to be more flexible than PHPMailer one. It appears to me that it supports a huge lot of features around mail sending, but mail receiving and parsing is not supported. Furthermore I'm not sure how easy it would be to extend the library for additional MIME parts like digest.

    Please correct me if I'm wrong.

    Regards,
    Toby

  • Stephen S. Musoke on Thu, 02 Dec 2010 15:35:29 +0100

    I now see what you are saying

    There seems to be a need to create many object instances, which while elegant will definitely make the library seem daunting to new users and adopters

    Is there a way of having some of the classes hidden from the developer, via public setters/getters that abstract the details

    For example when adding an address in PHP Mailer, you provide an email and a name (optional), the ezcMailAddress instance can be created internally

    I think Zend Framework has this kind of abstraction where you can provide an array of options (including) a class name and the object classes are initialized for the user

    Your thoughts?

  • Tobias Schlitt on Thu, 02 Dec 2010 15:47:10 +0100

    Hi Stephen,

    if you don't want to deal with all the internal stuff, we have the ezcMailComposer, which provides a convenience facade for the most common cases in a single object. That is: Simple text mail, HTML mail, HTML / Text alternative, Attachements. You only need to deal with very few objects. Find an example here:

    http://incubator.apache.org/zetacomponents/documentation/trunk/Mail/tutorial.html#sending-a-mail-with-the-composer

    However, if you need to build more complex mail, you still can do it. And I think that's the more important point here.

    We do not make use of classical getters and setters, but instead use overloading to allow you simple $foo->bar access to properties. Therefore we wrap the email address into a dedicated object. I personally don't consider its usage daunting, do you?

    I'm not sure what you mean with your last sentence. It somewhat sounds like a dependency injection container (DIC). Did I get it wrong? If now, we don't have a DIC in Zeta Components. Our way of handling configuration of components are option classes. You can create an instance of an option class, which validates the configuration values you set, and then submit it to the constructor of the class you want to instantiate. This mechanism can of course be used to integrate Zeta Components into a DIC.

    HTH,
    Toby

  • Stephen S. Musoke on Fri, 03 Dec 2010 09:47:14 +0100

    The composer is less daunting than the example in the blog above.

    Under normal conditions its easy to digest a lot of code, but my experience has been that with newer developers or when searching for a solution under pressure, the more code there is, the less you an understand.

    Thank you for taking the time to clarify the issues, and Keep up the excellent job that you are doing.

  • Tobias Schlitt on Fri, 03 Dec 2010 09:49:14 +0100

    Yeah, I understand that issue. However, it's not easy to provide both, flexibility to do all the nifty stuff and an easy way to get the most wanted stuff done. However, we try. :)

    If you want to, please subscribe to the Zeta developer list (zeta-dev@incubator.apache.org) to give us further feedback where we could offer easier ways.

    Thanks for your feedback!