This blog post has first been published in the Qafoo blog and is duplicated here since I wrote it or participated in writing it.
Cover image for post Apache Zeta Components: Doing mail right

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

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.

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.

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.

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!