How to use the MIME API

Table of Contents

  1. How to use the MIME API
    1. Creating MIME messages
    2. Creating and Sending E-Mail with an attachment by using the MIME_Mail class
    3. Parsing a MIME message
    4. Using MIME Viewers
    5. Reading message headers
      1. Using a mail server connection
      2. Using a header text
    6. Complete example script

Creating MIME messages

Scenario: We have a text block (such as text/calendar data) which needs to be added as a MIME part to an email message. We then need to add a couple of additional headers to this message and finally read it back as a string.

For Message Body:

require_once 'Horde/MIME/Message.php';
$message = new MIME_Message();
$part = new MIME_Part('text/plain', $message_text);
$message->addPart($part);

For text/calendar attachment:

$part = new MIME_Part('text/calendar', $text_calendar_data);
$message->addPart($part);

For Headers:

require_once 'Horde/MIME/Headers.php';
$headers = new MIME_Headers();
$headers->addHeader('Header 1', $Header_1_Value);
$headers->addHeader('Header 2', $Header_2_Value);
$headers->addMIMEHeaders($message);

To return the message as a string:

$string = $headers->toString() . "\n\n" . $message->toString();

Creating and Sending E-Mail with an attachment by using the MIME_Mail class

require_once 'Horde/MIME/Mail.php';

// New Horde MIME_Mail Object
$mail = new MIME_Mail();

// Set the header date
$mail->addHeader('Date', date('r'));

// Set the from address
$mail->addHeader('From', 'sender@example.com');

// Set the subject of the mail
$mail->addHeader('Subject', 'Horde MIME_Mail example');

// Set the text message body
$mail->setBody('Example MIME message with an attachment');

// Add the file as an attachment, set the file name and what kind of file it is.
$mail->addAttachment('/tmp/some_file.zip', 'some_file.zip', 'application/x-zip-compressed');

// Add recipients
$mail->addRecipients('recipient@example.com');

// Get the mail driver
$mail_driver = $conf['mailer']['type'];
$mail_params = $conf['mailer']['params'];
if ($mail_driver == 'smtp' && $mail_params['auth'] &&
    empty($mail_params['username'])) {
    if (Auth::getAuth()) {
        $mail_params['username'] = Auth::getAuth();
        $mail_params['password'] = Auth::getCredential('password');
    }
}

// Send the mail
if (!is_a($sent = $mail->send($mail_driver, $mail_params), 'PEAR_Error')) {
    print "E-Mail sent\n";
} else {
    print "E-Mail not sent\n";
}

Parsing a MIME message

Scenario: We have an existing text string which contains a valid MIME email message. We need to parse this string in order to read back the body of a specific MIME part with a certain content type.

require_once 'Horde/MIME/Structure.php';
$message = MIME_Structure::parseTextMIMEMessage($message_text);

If you have access to the mail server, you can also use:

$structure = @imap_fetchstructure($stream, $uid, FT_UID);
$message = MIME_Structure::parse($structure);

To determine the structure of the MIME message, e.g. to find out the MIME ID we are looking for:

$map = $message->contentTypeMap();

$map is an array with key being the MIME IDs and values being the Content Types of that IDs.

To retrieve a certain MIME part of the message:

$part = $message->getPart($mime_id);

The following code snippets require at least Horde 3.2.

To retrieve the body text:

require_once 'Horde/MIME/Contents.php';
$contents = new MIME_Contents($message);
$body_id = $contents->findBody();
if ($body_id) {
    $part = &$contents->getMIMEPart($body_id);
    $body = $part->transferDecode();
} else {
    $body = 'Could not render body of message.';
}

To retrieve the HTML body text if any exists:

require_once 'Horde/MIME/Contents.php';
$contents = new MIME_Contents($message);
$body_id = $contents->findBody('html');
if (is_null($body_id)) {
    $body_id = $contents->findBody();
}
if ($body_id) {
    $part = &$contents->getMIMEPart($body_id);
    $body = $part->transferDecode();
} else {
    $body = 'Could not render body of message.';
}

To retrieve a list of all attachments:

$attachments = $contents->getAttachmentContents();
foreach ($attachments as $attachment) {
    echo 'Attachment ' . htmlspecialchars($attachment['name']) . ' is ' . strlen($attachment['data']) . ' bytes long.';
}

If you don't retrieve all attachments as expected, in particular if any attached mail/rfc822 parts are returned empty, apply the patch mimeDecode.php.patch that has been attached to this wiki page to the file Mail/mimeDecode.php in your PEAR directory.

Using MIME Viewers

Scenario: Continuing the previous example we want to render the text/calendar part to HTML. This code currently only works inside of a Horde application because it relies on the registry and Horde's configuration files.

require HORDE_BASE . '/config/mime_drivers.php';
require_once 'Horde/MIME/Viewer.php';
$viewer = MIME_Viewer::factory($part);
$html = $viewer->render();

Reading message headers

Using a mail server connection

Scenario: We want to read a certain message from a certain message on the mail server, for example to search for messages with a known header.

/* Load libraries. */
require_once 'Horde/MIME/Headers.php';
require_once 'Horde/NLS.php';

/* Custom MIME_Headers class because we need to implement _getStream(). */
class MyHeaders extends MIME_Headers {

    function _getStream()
    {
        return imap_open('{localhost/imap}INBOX', 'username', 'password');
    }
}

/* Get message subject. */
$headers = new MyHeaders($message_uid);
$headers->buildHeaders();
$subject = $headers->getValue('Subject');

Using a header text

If the headers can't be received from an IMAP server or already are available as a string, this alternative approach could be used:

$headerText = 'Return-Path: <john@example.com>
Message-ID: <20080228160832.604925ntqxdo4o00@example.com>
Date: Thu, 28 Feb 2008 16:08:32 +0100
From: John Doe <john@example.com>
To: jane@example.com
Subject: Some Subject';

/* Load libraries. */
require_once 'Horde/MIME/Structure.php';

/* Get message subject. */
$headers = MIME_Structure::parseMIMEHeaders($headerText);
$subject = $headers['Subject'];

Complete example script

This is a complete example script that parses all messages of an INBOX for any CSV attachments, extract these attachments from the messages, saves them in a folder, and deletes the parsed message. The script can also be downloaded from the AttachedFiles area of this page.

#!/usr/bin/php
<?php

/* IMAP/POP3 server settings. */
$imap_user = '';
$imap_pass = '';
$imap_host = 'localhost';
$imap_protocol = 'imap/notls';

/* Directory where the CSV attachments get stored WITH trailing slash. */
$target_dir = '/home/jan/csv/';

/* Search string for potential messages.
 * Might be optimized by using something like 'SUBJECT "Always the same"'. */
$imap_search = 'ALL';

/* Possible mime types of CSV attachments. */
$csv_mimetypes = array('text/plain',
                       'text/x-comma-separated-values',
                       'application/csv',
                       'application/vnd.ms-excel');

/* If necessary set the include path to the location where PEAR and the Horde
 * packages are installed. */
// ini_set('include_path', '/usr/share/php');

/****************************************************************************/

/**
 * Shows all IMAP errors and exits.
 */
function error_out()
{
    $errors = imap_errors();
    if ($errors) {
        foreach ($errors as $error) {
            echo $error . "\n";
        }
    }
    exit;
}

/* Load libraries. */
require_once 'Horde/CLI.php';
require_once 'Horde/MIME/Structure.php';
require_once 'Horde/MIME/Headers.php';
require_once 'Horde/NLS.php';

/* Setup CLI. */
if (!Horde_CLI::runningFromCLI()) {
    exit("Must be run from the command line\n");
}
Horde_CLI::init();

/* Custom MIME_Headers class. */
class MyHeaders extends MIME_Headers {

    function _getStream()
    {
        return $GLOBALS['imap'];
    }
}

/* Open IMAP connection. */
$imap = imap_open(sprintf('{%s/%s}INBOX', $imap_host, $imap_protocol),
                  $imap_user, $imap_pass);
if (!$imap) {
    error_out();
}

/* Regexp for target filename. */
$regexp = '/_(\d+).csv$/';

/* Get all messages from the folder. */
$messages = imap_search($imap, $imap_search, FT_UID);

foreach ($messages as $uid) {
    $struct = imap_fetchstructure($imap, $uid, FT_UID);

    /* Is this no multipart message? */
    if ($struct->type != 1) {
        continue;
    }

    /* Parse message structure. */
    $body = imap_fetchheader($imap, $uid, FT_UID) .
            imap_body($imap, $uid, FT_UID);
    $message = MIME_Structure::parseTextMIMEMessage($body);
    $map = $message->contentTypeMap();

    /* Search for message with possible CSV attachment. */
    foreach ($map as $mime_id => $mime_type) {
        if (!in_array($mime_type, $csv_mimetypes)) {
            continue;
        }
        $mime_part = $message->getPart($mime_id);
        $filename = $mime_part->getName(true);
        if (String::lower(substr($filename, -4)) != '.csv') {
            continue;
        }

        /* CSV file found. */
        $content = $mime_part->transferDecode();

        /* Get message subject. */
        $headers = new MyHeaders($uid);
        $headers->buildHeaders();
        $subject = $headers->getValue('Subject');
        if (empty($subject)) {
            $subject = $filename;
        } else {
            $subject .= '.csv';
        }

        /* Find unique file name. */
        $filename = preg_replace('/[^a-z0-9_\-\.]/i', '_', $subject);
        while (file_exists($target_dir . $filename)) {
            if (preg_match($regexp, $filename, $match)) {
                $filename = preg_replace($regexp, '_' . ($match[1] + 1) . '.csv', $filename);
            } else {
                $filename = substr($filename, 0, -4) . '_1.csv';
            }
        }
        
        /* Write CSV file. */
        $fp = fopen($target_dir . $filename, 'w');
        if (!$fp) {
            exit("Can't open file '" . $target_dir . $filename . "' for writing.\n");
        }
        fwrite($fp, $content);
        fclose($fp);

        /* Delete message. */
        imap_delete($imap, $uid, FT_UID);
    }
}

/* Purge and close mailbox. */
// imap_expunge($imap);
imap_close($imap);
error_out();