Some web applications might require features of an email client to be
made available the users. In these situations, we can either write our
own or customize opensource clients like SquirrelMail or Roundcube.
Regardless of what you choose, knowledge of working with the PHP IMAP
mail extension will be helpful.
In this two-part series I’ll explain how to work with the IMAP
extension. This is the first part which discusses the necessary
functions for connecting to mail servers and reading messages. The
second part will talk about actions for working with email, like
deleting messages, downloading attachments, etc.
To demonstrate functionality, I’ll use code samples that can be used
to start scripting your own mail client. I’ll assume a single web script
which uses these URL parameters to call the necessary functions:
func
– the type of functionality required (ex: read email, view inbox, delete message)
folder
– the name of the mailbox folder to connect to (ex: inbox, sent, spam)
uid
– the unique ID of the email targeted
The parameters can be retrieved using
$_GET
and a
switch
statement can be used to invoke the appropriate actions.
|
<?php
$func = (! empty ( $_GET [ "func" ])) ? $_GET [ "func" ] : "view" ;
$folder = (! empty ( $_GET [ "folder" ])) ? $_GET [ "folder" ] : "INBOX" ;
$uid = (! empty ( $_GET [ "uid" ])) ? $_GET [ "uid" ] : 0;
switch ( $func ) {
case "delete" :
deleteMail( $imap , $folder , $uid );
break ;
case "read" :
deleteMail( $imap , $folder , $uid );
break ;
case "view" :
default :
viewMail( $imap , $folder );
break ;
}
|
Connecting to IMAP
To establish a connection to the IMAP server, we use the
imap_connect()
function as shown here:
|
<?php
$imap = imap_open( $mailboxPath , $username , $password );
|
The mailbox path, username, and password strings are required
parameters to connect to the server. You can learn about the optional
parameters
in the manual.
The mailbox path is a string that identifies server and port
information in braces followed by the name of the desired mail folder.
Here are a few strings for the inbox folder for popular mail providers:
- Gmail
{imap.gmail.com:993/imap/ssl}INBOX
- Yahoo
{imap.mail.yahoo.com:993/imap/ssl}INBOX
- AOL
{imap.aol.com:993/imap/ssl}INBOX
Some servers do not have SSL enabled, in which case you would omit
“SSL” from the string. Other servers might use self-signed
certificates, in which you would include “novalidate-cert”.
|
<?php
$imap = imap_open( "{localhost:993/imap/ssl/novalidate-cert}" , "username" , "password" );
|
With an open connection to the mail server, now we can look at the functions used for the following activities:
- Displaying the list of mailbox folders in your email account
- Displaying the list of email messages in the folder
- Viewing the email’s content
Listing the Folders
Inbox, sent, trash, and spam folders are seen in pretty much every
email account, and users can often create custom folders as well. In
order to view messages in these folders, we need to change our
connection string. For example, I used “INBOX” in the path string
earlier. If I wanted to connect to the spam folder, I might want to use
something like “Spam” instead. But even though it might be listed as
Spam in your email account when viewed through a mail client, the real
folder name might be different behind the scenes. We can list all of the
available folders in an account using
imap_list()
.
|
<?php
$folders = imap_list( $imap , "{imap.gmail.com:993/imap/ssl}" , "*" );
echo "<ul>" ;
foreach ( $folders as $folder ) {
$folder = str_replace ( "{imap.gmail.com:993/imap/ssl}" , "" , imap_utf7_decode( $folder ));
echo '<li><a href="mail.php?folder=' . $folder . '&func=view">' . $folder . '</a></li>' ;
}
echo "</ul>" ;
|
We have to pass the connection handle obtained with
imap_open()
as the initial parameter to
imap_list()
.
We also need to pass a bare path sting (without the folder, e.g.
“INBOX”). The star as the third parameter requests all of the available
folders.
Listing Email Messages
Each folder has a list of available email messages, so let’s see how we can generate a listing of the messages in our inbox.
We need to first get the number of messages available using
imap_num_msg()
. Then we can use the
imap_header()
function to get the header information for each message.
Let’s say if we wanted to the last 20 emails:
|
<?php
$numMessages = imap_num_msg( $imap );
for ( $i = $numMessages ; $i > ( $numMessages - 20); $i --) {
$header = imap_header( $imap , $i );
$fromInfo = $header ->from[0];
$replyInfo = $header ->reply_to[0];
$details = array (
"fromAddr" => (isset( $fromInfo ->mailbox) && isset( $fromInfo ->host))
? $fromInfo ->mailbox . "@" . $fromInfo ->host : "" ,
"fromName" => (isset( $fromInfo ->personal))
? $fromInfo ->personal : "" ,
"replyAddr" => (isset( $replyInfo ->mailbox) && isset( $replyInfo ->host))
? $replyInfo ->mailbox . "@" . $replyInfo ->host : "" ,
"replyName" => (isset( $replyTo ->personal))
? $replyto ->personal : "" ,
"subject" => (isset( $header ->subject))
? $header ->subject : "" ,
"udate" => (isset( $header ->udate))
? $header ->udate : ""
);
$uid = imap_uid( $imap , $i );
echo "<ul>" ;
echo "<li><strong>From:</strong>" . $details [ "fromName" ];
echo " " . $details [ "fromAddr" ] . "</li>" ;
echo "<li><strong>Subject:</strong> " . $details [ "subject" ] . "</li>" ;
echo '<li><a href="mail.php?folder=' . $folder . '&uid=' . $uid . '&func=read">Read</a>' ;
echo " | " ;
echo '<a href="mail.php?folder=' . $folder . '&uid=' . $uid . '&func=delete">Delete</a></li>' ;
echo "</ul>" ;
}
|
The
$imap
connection should be open to the desired
folder. We can then traverse through the last 20 emails using the number
of messages received by
imap_num_msg()
. The connection and email number are given to
imap_header()
to retrieve the header information, which can then be parsed for the
interesting details like the sender’s email address and name, subject,
etc.
Note that the email number we get from using the total message count
is not a unique ID for the message. If you have 100 emails in your
inbox, then the last number will be 100, the previous will be 99, and so
on. But its not unique. If you delete message number 100 and then
receive a new message, it’s number will also will be 100.
We have to get the unique ID for an email in order to proceed with
other actions. Each email does have an unique ID, called UID, which we
can get by providing the email number to the
imap_uid()
function. The UID is unique and will not change over time.
Viewing Message Contents
Reading email is not really as simple as the previous tasks, so I’m going to use the
Receive Mail class
developed by Mitul Koradia as a starting point to help make things
easier. From the class I’ve extracted and adapted the following three
functions for our example here:
|
<?php
function getBody( $uid , $imap ) {
$body = get_part( $imap , $uid , "TEXT/HTML" );
if ( $body == "" ) {
$body = get_part( $imap , $uid , "TEXT/PLAIN" );
}
return $body ;
}
function get_part( $imap , $uid , $mimetype , $structure = false, $partNumber = false) {
if (! $structure ) {
$structure = imap_fetchstructure( $imap , $uid , FT_UID);
}
if ( $structure ) {
if ( $mimetype == get_mime_type( $structure )) {
if (! $partNumber ) {
$partNumber = 1;
}
$text = imap_fetchbody( $imap , $uid , $partNumber , FT_UID);
switch ( $structure ->encoding) {
case 3: return imap_base64( $text );
case 4: return imap_qprint( $text );
default : return $text ;
}
}
if ( $structure ->type == 1) {
foreach ( $structure ->parts as $index => $subStruct ) {
$prefix = "" ;
if ( $partNumber ) {
$prefix = $partNumber . "." ;
}
$data = get_part( $imap , $uid , $mimetype , $subStruct , $prefix . ( $index + 1));
if ( $data ) {
return $data ;
}
}
}
}
return false;
}
function get_mime_type( $structure ) {
$primaryMimetype = array ( "TEXT" , "MULTIPART" , "MESSAGE" , "APPLICATION" , "AUDIO" , "IMAGE" , "VIDEO" , "OTHER" );
if ( $structure ->subtype) {
return $primaryMimetype [(int) $structure ->type] . "/" . $structure ->subtype;
}
return "TEXT/PLAIN" ;
}
|
The
getBody()
function gets the email’s content by passing its UID and the IMAP connection. Inside the function, we call the
get_part()
function with the content type as TEXT/HTML. Plain text emails are much
easier to read. So we first try to find the HTML content inside the
email.
Then we read the structure of the email using
imap_fetchstructure()
function. This function would have taken the email number by default,
but we changed the library function to use the UID instead by passing
the
FT_UID
constant.
Then we get the mime type of the email message using the
get_mime_type()
function. There are eight mime types returned by this function as integers:
- 0 – TEXT
- 1 – MULTIPART
- 2 – MESSAGE
- 3 – APPLICATION
- 4 – AUDIO
- 5 – IMAGE
- 6 – VIDEO
- 7 – OTHER
We convert the returned integer into actual mime type string using the mime types array.
Multipart messages can have a large number of subtypes, so we
traverse recursively through all of the subtypes using the part numbers
and retrieve the email content using
imap_fetchBody()
when the correct part is found using the mime type.
Then, we use an appropriate decode function according to the encoding
type of the message and return the content. A complete list of
available encoding types is shown here:
- 0 – 7BIT
- 1 – 8BIT
- 2 – BINARY
- 3 – BASE64
- 4 – QUOTED-PRINTABLE
- 5 – OTHER