Enhancing your Webmail program

by Bjxrn Borud

In this Apache/PHP tutorial, Bjorn Borud adds new features and code to the Webmail program created in his first tutorial, such as creating mailbox folders and simplifying the interface.

In my first tutorial I created a simple Webmail application that uses the IMAP protocol to access a user's mailbox. In somewhere around 300 lines of code we were able to implement most of what you need to access an IMAP server to read, send, and delete email.

You might have asked yourself what on earth you would want to do with such an application when there are perfectly adequate mail programs around, and indeed, a handful of people did e-mail me to ask that very question.

Apart from the clear practicality as an example in an educational context, a Webmail application can be very useful in Real Life as well. For example, it might be a good alternative way of accessing your e-mail while on the road: you just need to pop into some Internet café, and all you need is a browser to access your mail.

NOTE: See page 6 for the code you'll need, and page 7 for the index page code.

More features!

The first version of the Webmail interface had a glaring omission: support for mail folders. Only people who hardly get any mail at all or who delete all their mail after they've read it would not be interested in having the option of moving messages to other folders.

First off, we've added another page that shows the user the mail a_folders she has created. This page is displayed when the user clicks the "folders" button that appears at the bottom left group of buttons whenever the user is viewing the contents of a folder.

contents of a folder

The folders page is quite minimalist. You can select a folder to browse from the list or you can create a new folder by typing in a folder name and pressing the "new folder" button.

the folders page

While viewing the contents of a folder you can now move messages into other folders as well. Just mark the checkboxes in front of the messages you wish to move into a particular folder (just like you would do if you wanted to delete them), then select a folder from the folder pulldown menu in the lower right corner and click the "move to" button. The selected mail messages will then be moved to the target folder.

Folders and files

To make the files in which the folders reside easier to identify, and therefore easier to list in the Webmail interface, I've added some code to massage the folder names a bit.

When you want to enter or create a folder the folder name that you typed (or clicked on) is filtered through the function


  function m_mailbox_name ($mbx)
    global $M_MBOX, $M_PREFIX;

    /* no mailbox specified means we map it to the inbox */
    if ($mbx == "") {
      return $M_MBOX . "INBOX";

    /* replace some special chars */
    $mbx = ereg_replace("[^[:alnum:]]", "_", $mbx);

    return $M_MBOX . $M_PREFIX . $mbx;

If the mailbox name was blank, it will assume that we want the default folder. In the IMAP protocol this folder is called

. If the mailbox we request has a name, the code will remove any non-alphanumeric characters and the variables
are prepended to the mail folder name.

The variable

contains information about the host and the port we wish to connect to and the kind of protocol we want to talk. In this case
would typically have the value:

To this we then add the string contained in

and finally the folder name.

Why do you want to do that?

The prefix
is added to make listing mail folders easier. When the client asks for a list of mail folders, it can provide the IMAP server with a prefix to the filenames. It works just like the shell: if you say
you will match all filenames starting with the string "docu".


enables us to tuck a prefix onto the actual filenames of our folders, thus making it easy to tell the IMAP server that we want it to list anything that starts with the prefix as if it were a mail folder:
$mailboxes = imap_listmailbox($MBOX, $M_MBOX, "$M_PREFIX%");  

The third argument to the PHP3 function

tells the IMAP server to only list filenames starting with the chosen prefix. Before showing the name to the user we massage the name a bit, removing the prefix and the host/port/service information.

In short: folder names on disk have prefixes so we can identify them. When displayed to the user everything but the folder name itself is stripped away.

Keeping track

Now that we have several folders, we have to keep track of which folder we're in. Actually, most of the code to do this was already added in version 1.0. The
variable is used to keep track of what folder is currently being viewed.

To remember what folder we were in while browsing a message, when moving or deleting messages some pages have a hidden input field (well, "input field" is obviously a bad name if you're not supposed to input anything into it or even see it, but nevertheless, 'tis an input field). This is sort of a sneaky way to have the application open the correct folder.

We could also have used cookies to keep track of the current folder. Setting cookies is fairly simple in PHP3 and reading them is even simpler. (Yes, you guessed it; cookies are turned into variables just like forms fields are).

Note: See page 5 for a rundown of what's new in this tutorial.

What's next?

Our little Webmail interface is still under 500 lines of code. Considering what it would take to write an equivalent application with a GUI, that is not bad at all. Of course there is a lot left to be desired. For one, the folders should be hierarchical and they should be displayed as such. Some tasks could be automated, such as the creation of monthly archive folders. E-mail could be moved from the default folder into such archive folders when a message reaches a certain age.

Also, we haven't begun to dive into MIME and multipart MIME messages.

Tune in next month and find out where we go next!ø

Related resources

1. Web mail in PHP Part I of this workshop.
2. How to set cookies in PHP3.
3. www.apache.org The Apache Web server project.
4. www.php.net Homepage for PHP3 info and development.
5. ftp.cac.washington.edu/imap/ University of Washington's IMAP directory
6. www.imap.org/ The basics on IMAP.
7. http://horde.org/imp/ IMP, a much more advanced IMAP interface written in PHP3.

Bjørn Borud works as a systems engineer at Fast Search and Transfer ASA, which develops large-scale search engines and cutting-edge image compression software. In his copious spare time Bjørn reads, writes software, and sometimes writes articles for various publications.

New functions

  • m_move_mail()
    Move one or more mail messages from the current mailbox to some other mailbox.

  • m_list_mailboxes()
    Return an array containing the names of the mailfolders the IMAP server can find. The names returned in the array have been stripped of their prefixes.

  • m_folder_form()
    Display the folder list and provide a data entry field where the user can type in a new folder name.

  • m_create_mailbox()
    Creates a new mail-folder.

  • m_strip_mbox_name()
    Takes the mailbox name as returned by the internal IMAP functions and strips off all the ugly parts the user doesn't want to know about.

These functions were changed

  • m_login()
    Minor bugfix plus added some code to incorporate the new features in the interface.

  • m_mailbox_name()
    Added prefix to all mailbox names except the default mailbox called "INBOX".
Also, the index.php3 file was changed a bit to reflect the new functionality available and to perhaps be a bit more readable.


    print "
\n"; print ""; print ""; print "\n"; $i = 1; $bgcolor = $M_COLOR_EVEN; print "\n"; print " \n"; print "\n"; if ($mailboxes) { while (list($dummy, $box) = each($mailboxes)) { $bgcolor = ($i%2 == 0)?$M_COLOR_EVEN:$M_COLOR_ODD; print "\n"; print " \n"; print "\n"; $i++; } } print "
Folder name
Default inbox
\n"; print ""; print "\n"; print "\n"; } function m_create_mailbox ($new = '') { global $MBOX, $M_REALM; global $PHP_AUTH_USER, $PHP_AUTH_PW; if (! $MBOX) { m_login("INBOX"); } if ($new == "") { return false; } $name = m_mailbox_name($new); $result = imap_createmailbox($MBOX, $name); return $result; } function m_login ($mailbox = '') { global $MBOX, $M_REALM; global $PHP_AUTH_USER, $PHP_AUTH_PW; if ($MBOX) { return true; } if (! $PHP_AUTH_USER) { m_reject($M_REALM); } $MBOX = @imap_open(m_mailbox_name($mailbox), $PHP_AUTH_USER, $PHP_AUTH_PW); if (! $MBOX) { m_reject($M_REALM); } return true; } function m_list($mailbox = '') { global $MBOX, $PHP_SELF; global $M_COLOR_ODD, $M_COLOR_EVEN, $M_COLOR_HEAD, $M_COLOR_BG; /* if not logged into server, do so */ if (! $MBOX) { if (! m_login($mailbox)) { return false; } } $num = imap_num_msg($MBOX); print "
\n"; print "
\n"; print ""; print ""; print "\n"; for ($i=1; $i < ($num+1); $i++) { $head = imap_header($MBOX, $i, 50, 50, 0); $from = $head->fetchfrom; $subj = $head->fetchsubject; $date = m_date_format($head->date); $bgcolor = ($i%2 == 0)?$M_COLOR_EVEN:$M_COLOR_ODD; print "\n"; print " \n"; print " "; print ""; print "\n"; print "\n"; } if ($num <= 0) { print "\n"; } print "\n"; print "

"; print "No messages in mailbox"; print "

"; print "\n"; print ""; print ""; print ""; print "\n"; print ""; print ""; $mailboxes = m_list_mailboxes(); if (count($mailboxes) > 0) { print "\n"; } print "
\n"; print "
\n"; return true; } function m_display($msgno, $mailbox = '') { global $MBOX, $M_COLOR_HEAD, $M_COLOR_BG; global $PHP_SELF; if (! $MBOX) { if (! m_login($mailbox)) { return false; } } $struc = imap_fetchstructure($MBOX, $msgno); if (! $struc) { return false; } $head = imap_header($MBOX, $msgno, 50, 50, 0); $from = $head->fromaddress; $subj = $head->subject; $date = $head->date; $body = htmlentities(imap_body($MBOX, $msgno)); print "
\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
Message #$msgno: $from / $subj
  print "From: $from\n";
  print "Subject: $subj\n";
  print "Date: $date\n";
  print "
\n"; print "$body\n"; print "
"; print "
"; print "\n"; print "\n"; print "\n"; print ""; print ""; print "     "; print "
\n"; print "
\n"; return true; } function m_delete ($msgno, $mailbox='') { global $MBOX; if (is_array($msgno)) { while (list($dummy, $num) = each($msgno)) { imap_delete($MBOX, $num); } imap_expunge($MBOX); } else { return false; } return true; } function m_compose ($msgno='', $mailbox='') { global $MBOX, $M_COLOR_HEAD, $M_COLOR_BG; global $PHP_SELF, $PHP_AUTH_USER, $M_MAILSERVER; if ($msgno != '') { $head = imap_header($MBOX, $msgno, 150, 150, 0); $to = $head->fromaddress; $subject = "Re: " . $head->subject; $body = "$to wrote:\n"; $body .= ereg_replace("\n","\n>", "\n" . imap_body($MBOX, $msgno)); } else { $to = ""; $subject = ""; $body = ""; } print "
\n"; print "\n"; print "\n"; print ""; print "\n"; print ""; print "\n"; print "\n"; print ""; print "
"; print "\n"; print "
"; print ""; print ""; print ""; print "
\n"; print "
\n"; } function m_send ($to, $subject, $body) { global $PHP_AUTH_USER, $M_MAILSERVER, $M_SYSNAME; if ($PHP_AUTH_USER && $M_MAILSERVER && $to && $body) { $headers = "From: $PHP_AUTH_USER@$M_MAILSERVER\n"; $headers .= "Reply-to: $PHP_AUTH_USER@$M_MAILSERVER\n"; $headers .= "Content-Type: text/plain; charset=iso-8859-1\n"; $headers .= "Content-Transfer-Encoding: 8bit\n"; $headers .= "X-Mailer: $M_SYSNAME/" . phpversion() . "\n"; return mail($to, $subject, $body, $headers); } return false; } function m_date_format($datestr) { if (ereg("([[:digit:]]{1,2})[[:space:]]+([[:alpha:]]{3})[[:space:]]+([[:digit:]]{4})", $datestr, $regs)) { return $regs[0]; } return $datestr; } function m_mailbox_name ($mbx) { global $M_MBOX, $M_PREFIX; /* no mailbox specified means we map it to the inbox */ if ($mbx == "") { return $M_MBOX . "INBOX"; } /* replace some special chars */ $mbx = ereg_replace("[^[:alnum:]]", "_", $mbx); return $M_MBOX . $M_PREFIX . $mbx; } function m_strip_mbox_name ($mbx) { global $M_PREFIX; ereg("$M_PREFIX(.*)$", $mbx, $regs); return $regs[1]; } function m_reject($dom) { Header("HTTP/1.0 401 Unauthorized"); Header("WWW-authenticate: basic realm=\"$dom\""); print "Access denied\n"; exit; } /* make sure there is NO trailing space here!!! */ ?>





  <?PHP print "$M_SYSNAME"; ?>


Folder: INBOX,
User: $PHP_AUTH_USER"; } else { print "

Folder: $m,

"; } print "

\n"; if ($cmd == "delete") { m_delete($marked, $m); m_list($m); } elseif ($cmd == "display") { m_display($n, $m); } elseif ($cmd == "folders") { m_folder_form(); } elseif ($cmd == "new folder" || $new_folder != "") { m_create_mailbox($new_folder); m_folder_form(); } elseif ($cmd == "move to") { m_move_mail($marked, $tm); m_list($m); } elseif ($cmd == "compose" || $cmd == "reply") { m_compose($n, $m); } elseif ($cmd == "send") { m_send($to, $subject, $body); m_list($m); } else { m_list($m); } ?>

This article was originally published on Tuesday May 25th 1999
Mobile Site | Full Site