imap_fetchbody

(PHP 3, PHP 4, PHP 5)

imap_fetchbody --  Fetch a particular section of the body of the message

Description

string imap_fetchbody ( resource imap_stream, int msg_number, string part_number [, int options] )

This function causes a fetch of a particular section of the body of the specified messages as a text string and returns that text string. The section specification is a string of integers delimited by period which index into a body part list as per the IMAP4 specification. Body parts are not decoded by this function.

The options for imap_fetchbody() is a bitmask with one or more of the following:

  • FT_UID - The msg_number is a UID

  • FT_PEEK - Do not set the \Seen flag if not already set

  • FT_INTERNAL - The return string is in internal format, will not canonicalize to CRLF.

See also: imap_fetchstructure().


add a note add a note User Contributed Notes
Musawir Ali
13-Jul-2006 01:04
For those of you trying to create a "flexible" email reader, I suggest doing the following.

Firstly, instead of going through each part of the email and decoding it, I suggest you go in a specific order.

1) First go through all the parts and search for the part that has subtype=='HTML'. If found, use that as your message body, else go to Step 2.
2) If HTML component not found, search all parts for subtype=='PLAIN'. If found, use that as your message body else print out an error message (this should never occur).
3) At this point, you already have the main body of the email that you should display. The next thing to do is to find all the attachments. You do this by going through all parts, searching for the parts with ifdisposition==1 (there can be multiple).

This is the way I did my email reader program and it works perfectly. I started off by using the methods recommended in a lot of webpages (i.e. going through the parts tree like 1.2.1, 1, 2, etc) but the problem is the message body is never in some fixed part! Sometimes its in 1, somtimes 1.1.2, sometimes 2, and sometimes in some totally unexpected place! So my approach was to "search" for the parts that I'm interested. Hope this helps. I also managed to crack the problem with emails containing embedded images. I will probably write a tutorial on that soon.
se at designlinks dot net
03-Mar-2005 07:36
If you insist on using imap_fetchbody() to retrieve a mail body that doesn't contain 'parts' [normally you'd use imap_body() ], then note that the header text is in part '0' and the body text in part '1'.
So, imap_fetchbody($mbox,$msg,'0') will return the header and imap_fetchbody($mbox,$msg,'1') will return the body text.
moritz at mthielcke dot de
30-Jan-2005 10:35
after reading a lot of code here from the forum me "think" me have found an easy way to access single parts of a email.

richy's code helped me a lot, but seems to have problems with pgp-signed mails looking like this:

   1 message/rfc822
       1 multipart/signed
           1.1 multipart/alternative
               1.1.1 text/plain
               1.1.2 text/html
           1.2 something else (attachment/pgp)

Here is my solution:
http://www.mthielcke.de/imapr.html#colparts
guru at php dot net
04-Oct-2004 11:50
After many battles with the imap extension I took it upon myself to write my own implementation using sockets. Below is what I came up with, and hopefully it will be usefull to others as well. I know that there are some parts of this that can be tweaked, however I will leave that for you to do. Use/abuse/mutilate this as you wish (no license).

(Note: I am not placing the source here since it is well over 1900 lines ~48KB)

http://www.nutextonline.com/cimap.phps

If enough people find this usefull I will continue to improve it and eventually commit it (or have it committed) to pear.

Also worth noting is the fact that you can use the imap extensions on mbox format mailboxes. A combination of this class with the imap extension would certainly make a great combination of done correctly.
Adam
28-Sep-2004 02:12
It took me a while to figure out how part id's(numbers) are assigned to different parts of a message.  After getting a few AOL e-mails that had a semi-complex structure I was finally able to break it down.

0 multipart/mixed
   1 multipart/alternative
       1.1 text/plain
       1.2 text/html
   2 message/rfc822
       2 multipart/mixed
           2.1 multipart/alternative
               2.1.1 text/plain
               2.1.2 text/html
           2.2 message/rfc822
               2.2 multipart/alternative
                   2.2.1 text/plain
                   2.2.2 text/html

Hopefully this will help others get the different body parts of a message.
sales at nocwizard dot com
06-Sep-2004 08:59
I'm adding this for those of you like me who are scouring the web looking for code to break down a message.  These two functions will break down a multi-part and non multi-part message and return the contents of the message in the array $message.  It doesn't do attachments.  This assumes you've already opened the mailbox and pass it VIA the $mbox.

function retrieve_message($mbox, $messageid)
{
   $message = array();
  
   $header = imap_header($mbox, $messageid);
   $structure = imap_fetchstructure($mbox, $messageid);

   $message['subject'] = $header->subject;
   $message['fromaddress'] =  $header->fromaddress;
   $message['toaddress'] =  $header->toaddress;
   $message['ccaddress'] =  $header->ccaddress;
   $message['date'] =  $header->date;

  if (check_type($structure))
  {
   $message['body'] = imap_fetchbody($mbox,$messageid,"1"); ## GET THE BODY OF MULTI-PART MESSAGE
   if(!$message['body']) {$message['body'] = '[NO TEXT ENTERED INTO THE MESSAGE]\n\n';}
  }
  else
  {
   $message['body'] = imap_body($mbox, $messageid);
   if(!$message['body']) {$message['body'] = '[NO TEXT ENTERED INTO THE MESSAGE]\n\n';}
  }
  
  return $message;
}

function check_type($structure) ## CHECK THE TYPE
{
  if($structure->type == 1)
   {
     return(true); ## YES THIS IS A MULTI-PART MESSAGE
   }
 else
   {
     return(false); ## NO THIS IS NOT A MULTI-PART MESSAGE
   }
}
noose (at) nospam (dot) tweak (dot) pl
20-Jun-2004 07:04
Sorry for my english....

for read the attachment's:

<?
 
foreach ($_GET as $k => $v)
 {
     $
$k = $v;
 }
 
$login = 'mylogin';
 
$password = 'mypassword';
 
$host = '{myhost:110/pop3}';

$mbox = imap_open("$host", "$login", "$password");
$struckture = imap_fetchstructure($mbox, $id);
$message = imap_fetchbody($mbox,$id,$part);   
$name = $struckture->parts[$part]->dparameters[0]->value;
$type = $struckture->parts[$part]->typee;
############## type
if ($type == 0)
{
  
$type = "text/";
}
elseif (
$type == 1)
{
  
$type = "multipart/";
}
elseif (
$type == 2)
{
  
$type = "message/";
}
elseif (
$type == 3)
{
  
$type = "application/";
}
elseif (
$type == 4)
{
  
$type = "audio/";
}
elseif (
$type == 5)
{
  
$type = "image/";
}
elseif (
$type == 6)
{
  
$type = "video";
}
elseif(
$type == 7)
{
  
$type = "other/";
}
$type .= $struckture->parts[$part]->subtypee;
######## Type end

header("Content-typee: ".$type);
header("Content-Disposition: attachment; filename=".$name);

######## coding
$coding = $struckture->parts[$part]->encoding;
if (
$coding == 0)
{
  
$message = imap_7bit($message);
}
elseif (
$coding == 1)
{
  
$wiadomsoc = imap_8bit($message);
}
elseif (
$coding == 2)
{
  
$message = imap_binary($message);
}
elseif (
$coding == 3)
{
  
$message = imap_base64($message);
}
elseif (
$coding == 4)
{
  
$message = quoted_printable($message);
}
elseif (
$coding == 5)
{
  
$message = $message;
}
echo
$message;
########## coding end
 
imap_close($mbox);
?>
jsimlo yahoo com
25-May-2004 04:38
this may be the way, how to obtain all those part_numbers.... even when a message contains another message attached, and it contains another message attached...

<?
 $parttypes
= array ("text", "multipart", "message", "application", "audio", "image", "video", "other");
 function
buildparts ($struct, $pno = "") {
   global
$parttypes;
   switch (
$struct->type):
     case
1:
      
$r = array (); $i = 1;
       foreach (
$struct->parts as $part)
        
$r[] = buildparts ($part, $pno.".".$i++);

       return
implode (", ", $r);
     case
2:
       return
"{".buildparts ($struct->parts[0], $pno)."}";
     default:
       return
'<a href="?p='.substr ($pno, 1).'">'.$parttypes[$struct->type]."/".strtolower ($struct->subtype)."</a>";
   endswitch;
 }

 
$struct = imap_fetchstructure ($pop3mbox, $msguid, FT_UID);
  echo
buildparts ($struct);
?>

it will print something like:

<a href="?p=1">text/plain</a>, {<a href="?p=2.1">text/plain</a>, <a href="?p=2.2">text/html</a>}
Sito
24-May-2004 07:54
Better, a fix on a fix... ;)

function read_all_parts($uid){
 global $mime,$ret_info,$enc;
  $mime = array("text","multipart","message","application","audio",
"image","video","other","unknow");
  $enc  = array("7BIT","8BIT","BINARY","BASE64",
"QUOTED-PRINTABLE","OTHER");
    

 $struct = imap_fetchstructure( $this -> Link, $uid );

 $ret_info = array();

function scan($struct,$subkey){
 global $mime,$enc,$ret_info;
 
   foreach($struct as $key => $value){

 
   if($subkey!=0){
   $pid = $subkey.".".($key+1); }
   else { $pid = ($key+1); }
 
   $ret_info[]['pid'] = $pid;
   $ret_info[key($ret_info)]['type'] = $mime["$value->type"];
   $ret_info[key($ret_info)]['encoding'] = $enc["$value->encoding"];

   next($ret_info);
 
   if(($value->parts)!= null) {scan($value->parts,$pid); }
   }
 }

if(!is_null($struct->parts))
{
 scan($struct->parts,0);
}
else
{
   print_r($struct);
   echo("<hr>");
   $ret_info[]['pid']=1;
   $ret_info[key($ret_info)]['type']=$mime["$struct->type"];   
   $ret_info[key($ret_info)]['encoding']=$enc["$struct->encoding"];   
}

return $ret_info;

}
php at NOSPAM dot sibyla dot cz
05-Apr-2004 06:56
// $uid - msg number

function read_all_parts($uid){
 global $mime,$ret_info,$enc;
  $mime = array("text","multipart","message","application","audio",
"image","video","other","unknow");
  $enc  = array("7BIT","8BIT","BINARY","BASE64",
"QUOTED-PRINTABLE","OTHER");
    

 $struct = imap_fetchstructure( $this -> Link, $uid );

 $ret_info = array();

function scan($struct,$subkey){
 global $mime,$enc,$ret_info;
 
 foreach($struct as $key => $value){

 
  if($subkey!=0){
  $pid = $subkey.".".($key+1); }
  else { $pid = ($key+1); }
 
  $ret_info[]['pid'] = $pid;
  $ret_info[key($ret_info)]['type'] = $mime["$value->type"];
  $ret_info[key($ret_info)]['encoding'] = $enc["$value->encoding"];

  next($ret_info);
 
  if(($value->parts)!= null) {scan($value->parts,$pid); }
 }
 
 }

scan($struct->parts,0);

return $ret_info;

}
asfd at aasdfa dot com
30-Aug-2003 05:48
PLEASE DOCUMENT THIS POST AND SAVE US SOME TIME! :)
ac at artcenter dot ac
17-Feb-2002 04:24
Lets say your email has this structure:
1 multipart
   plain
   html
2 x-vcard
and... you can't seem to figure out how to get either plain or HTML part of the multipart message? have you been going through all the trouble looking for a PHP mime parser? well here is a simple solution which SHOULD BE documented in this manual. From example above, if you want to get the plain part of the "multipart" message do this:
$text=imap_fetchbody($mbox,$msg_num,"1.1");
all you do is instead of part number 1, put 1.1 or to get the HTML part put 1.2

EASY!
ulrich at kaldamar dot de
30-Apr-2003 07:54
The function imap_fetchbody() seems uncapable of getting subordinate parts of a message/rfc822-part. I had problems with getting an attachment that was forwarded in such a part, because the object given by imap_fetchstructure() would assume that the part was represented by the string "2.1.2.1.2".

So I wrote this set of functions which parses the raw message-body and creates an array with the struture corresponding to the structure given by imap_fetchstructure(). The function mail_fetchpart() (see below) will work on the array and return even those parts that I could not get with imap_fetchbody().

Example usage of this function: mail_fetchpart($mbox, 2, "2.1.2.1.2");

Note: If the message does not contain multiple pars, the body of the message can be accessed by the part-string "1".
I have more functions for parsing and decoding messages, just email me.

   // get the body of a part of a message according to the
   // string in $part
   function mail_fetchpart($mbox, $msgNo, $part) {
       $parts = mail_fetchparts($mbox, $msgNo);
      
       $partNos = explode(".", $part);
      
       $currentPart = $parts;
       while(list ($key, $val) = each($partNos)) {
           $currentPart = $currentPart[$val];
       }
      
       if ($currentPart != "") return $currentPart;
         else return false;
   }

   // splits a message given in the body if it is
   // a mulitpart mime message and returns the parts,
   // if no parts are found, returns false
   function mail_mimesplit($header, $body) {
       $parts = array();
      
       $PN_EREG_BOUNDARY = "Content-Type:(.*)boundary=\"([^\"]+)\"";

       if (eregi ($PN_EREG_BOUNDARY, $header, $regs)) {
           $boundary = $regs[2];

           $delimiterReg = "([^\r\n]*)$boundary([^\r\n]*)";
           if (eregi ($delimiterReg, $body, $results)) {
               $delimiter = $results[0];
               $parts = explode($delimiter, $body);
               $parts = array_slice ($parts, 1, -1);
           }
          
           return $parts;
       } else {
           return false;
       }   
      
      
   }

   // returns an array with all parts that are
   // subparts of the given part
   // if no subparts are found, return the body of
   // the current part
   function mail_mimesub($part) {
       $i = 1;
       $headDelimiter = "\r\n\r\n";
       $delLength = strlen($headDelimiter);
  
       // get head & body of the current part
       $endOfHead = strpos( $part, $headDelimiter);
       $head = substr($part, 0, $endOfHead);
       $body = substr($part, $endOfHead + $delLength, strlen($part));
      
       // check whether it is a message according to rfc822
       if (stristr($head, "Content-Type: message/rfc822")) {
           $part = substr($part, $endOfHead + $delLength, strlen($part));
           $returnParts[1] = mail_mimesub($part);
           return $returnParts;
       // if no message, get subparts and call function recursively
       } elseif ($subParts = mail_mimesplit($head, $body)) {
           // got more subparts
           while (list ($key, $val) = each($subParts)) {
               $returnParts[$i] = mail_mimesub($val);
               $i++;
           }           
           return $returnParts;
       } else {
           return $body;
       }
   }

   // get an array with the bodies all parts of an email
   // the structure of the array corresponds to the
   // structure that is available with imap_fetchstructure
   function mail_fetchparts($mbox, $msgNo) {
       $parts = array();
       $header = imap_fetchheader($mbox,$msgNo);
       $body = imap_body($mbox,$msgNo, FT_INTERNAL);
      
       $i = 1;
      
       if ($newParts = mail_mimesplit($header, $body)) {
           while (list ($key, $val) = each($newParts)) {
               $parts[$i] = mail_mimesub($val);
               $i++;               
           }
       } else {
           $parts[$i] = $body;
       }
       return $parts;
      
   }
bubblocity at yahoo dot com
25-Mar-2003 12:18
This is a well overdue addendum to the code submitted on
14-Jan-2002 02:58.  This function was called inside the GetParts function:

function concat_ws($separator, $str1, $str2){
   if(strlen($str1) && strlen($str2)){
       return $str1 . $separator . $str2;
   }
   else if (strlen($str1)){
       return $str1;
   }
   else if (strlen($str2)){
       return $str2;
   }   
   else return '';
      
}
robc at gmx dot de
01-Jan-2003 02:26
// This prints all important part numbers:

function mime_scan_multipart(&$parts, $part_number)
{
       $n = 1;
       foreach ($parts as $obj) {
               if ($obj->type == 1)
                       mime_scan_multipart($obj->parts, "$part_number$n.");
               else
                       echo "$part_number$n\n";
               $n++;
       }
}

function mime_scan(&$obj)
{
       if ($obj->type == 1)
               mime_scan_multipart($obj->parts, "");
       else
               echo "1";
}

...
$obj = imap_fetchstructure($fd, $id);
mime_scan($obj);
markusNOSPAM at broede dot NO_SPAM dot de
13-Aug-2002 11:54
Maybe you prefer a short and recursive way to parse a message
and get the part numbers built:
-------------------------------------------------------------

function parse_message($obj, $prefix="") {
/* Here you can process the data of the main "part" of the message, e.g.: */
  do_anything_with_message_struct($obj);

  if (sizeof($obj->parts) > 0)
   foreach ($obj->parts as $count=>$p)
     parse_part($p, $prefix.($count+1));
}

function parse_part($obj, $partno) {
/* Here you can process the part number and the data of the parts of the message, e.g.: */
  do_anything_with_part_struct($obj,$partno);

  if ($obj->type == TYPEMESSAGE)
   parse_message($obj->parts[0], $partno.".");
  else
   if (sizeof($obj->parts) > 0)
     foreach ($obj->parts as $count=>$p)
       parse_part($p, $partno.".".($count+1));
}

/* Let's say, you have an already opened mailbox stream $mbox
   and you like to parse through the first message: */

$msgno = 1;
parse_message(imap_fetchstructure($mbox,$msgno));
sarachaga at yahoo dot com
05-Mar-2002 03:23
For those guys trying to extract a message/rfc822 parts correctly here is a hint:

As you may note, if you recurse over the structure object returned by imap_fetchstructure, when you reach the message/rfc822 you find that the message itself counts as a part too, so an attachment in that message should be 2.1.2 (the .1. is the whole rfc822 msg), but if you use that part number in imap_fetchbody you get nothing.

What i did is: my recursing function checks if the part/subtype of a particular part is message/rfc822, and only in that case i do not concatenate the .1 to the part number, because as far as i know, this only happens with this kind of message. And for all the other parts (non rfc822) it concats the dot-partnumber.
ac at artcenter dot ac
18-Feb-2002 09:24
Lets say your email has this structure:
1 multipart
   plain
   html
2 x-vcard
and... you can't seem to figure out how to get either plain or HTML part of the multipart message? have you been going through all the trouble looking for a PHP mime parser? well here is a simple solution which SHOULD BE documented in this manual. From example above, if you want to get the plain part of the "multipart" message do this:
$text=imap_fetchbody($mbox,$msg_num,"1.1");
all you do is instead of part number 1, put 1.1 or to get the HTML part put 1.2

EASY!
bubblocity at yahoo dot com
15-Jan-2002 05:58
If you are trying to fetch the body part and don't know what to fetch,
here is a function that will return all the body parts as a single string with each part separated by '|' character.
Followed by a fuction which will retrieve the structure of that part.

You can then parse the return string by explode() and call imap_fetchbody() on each of the parts.

Chunks of this code was stolen from the following site:
http://www.bitsense.net/PHPNotes/IMAP/imap_fetchstructure.asp

<pre>
#Function Gets all the subparts , but does not get part: ''
function GetParts($this_part, $part_no) {

       //If it is type message(2)=msg envelope,
   //Not sure about this assumption : the if statement
   //double check with RFC822 on this one.

   if($this_part->type==2){
       //and there is only 1 subpart of the envelope
       //and that subpart is multipart
       //otherwise will generate excessive '.1' and
       //send wrong part_no to imap_fetchbody
               if($this_part->parts[0]->type==1){
               $this_part=$this_part->parts[0];
               }
       }

       if (count($this_part->parts)>0) {
               for ($i = 0; $i < count($this_part->parts); $i++) {
                       if ($part_no != "") {
               $newpartno = $part_no . '.' . ($i +1);
           }
                       else $newpartno = $i+1;
                      
           $partstringary[$i] =
               GetParts($this_part->parts[$i], $newpartno);
               }//for
               $partstring = implode('|', $partstringary);

       }//if
       $partstring = concat_ws('|', $part_no , $partstring);
       //echo $partstring ;
       return $partstring;
}

#########################################
#Companion function go to GetParts();
#Given the partno - returns the structure
#########################################
function GetSubPartStructure($mbox, $msgno, $partno){
  
       $msg_struct = imap_fetchstructure($mbox, $msgno);
       $partsary = explode ('.', $partno);
       $subpartstruct = $msg_struct;

       for ($i=0; $i< count($partsary); $i++){
       //Not sure about this, if assumption
       // you should probably double check with RFC822 on this one.
               if($subpartstruct->type==2){
                       if($subpartstruct->parts[0]->type==1){
                         $subpartstruct=$subpartstruct->parts[0];
                       }
               }
               $subpartstruct = $subpartstruct->parts[$partsary[$i]-1];
       }
       return $subpartstruct;
}

function decodemsg($msgstring, $encoding){
       //Write your own function to decode,
   //or lots of examples within PHP already.
   return $decodemsg;
}

#Usage:
$msg_struct = imap_fetchstructure($mbox, $msgno);
$msgparts= GetParts($msg_struct, $part_no);
$partsary = explode($msgparts, '|');
for($i=0; $i<count($parsary);$i++){
   $substruct = GetSubPartStructure($mbox, $msgno, $partsary[$i]);
   if($substruct->type==''){ 
       //then this is type TEXT
       $msg = imap_fetchbody($mbox, $msgno, $msgpartsary[$i]);
       //Do the decoding
       $msg = decodemsg($msg, $struct->encoding);
       echo $msg; //or do whatever you want with it.
   }
   else{ //Not TEXT
       //Do whatever you want
       //i.e. write the attachment...etc
   }
}

</pre>
pmzandbergen at yahoo dot com
10-Jul-2001 10:56
Nice function above. However, there are some webmail (normaly plain text) services like you which don't use structure type 'ATTACHMENT' with the attachments, but 'INLINE' (also plain text is called like this). Just replace
if (strtoupper ($structure->disposition) == "ATTACHMENT"){
with

           if (strtoupper ($structure->disposition) == "ATTACHMENT" || strtoupper ($structure->disposition) == "INLINE" && strtoupper ($structure->subtype) != "PLAIN") {

But remember, when uou send a message with plaintext & attachment, also the plaintext is an attachment
rfinnie at kefta dot com
15-Dec-2000 06:23
With regard to message/rfc822 parts, it appears that the IMAP c-client that PHP uses does not follow the IMAP RFC (#2060) correctly.  The RFC specifies that to grab the headers for a message/rfc822 part, you would request part "2.HEADER" (assuming 2 is your part number), and the full text of the part would be "2.TEXT".  Instead, "2.0" will grab the headers, and just "2" will get the text.
sborrill at precedence dot co dot uk
10-Aug-2000 04:35
In reference to the naming and numbering of an IMAP message, the IMAP4 spec can be found at http://www.imap.org/papers/docs/rfc2060.html

The relevant section is 6.4.5
cleong at organic dot com
05-Apr-2000 12:19
Here's a routine that yields the part of a message with a particular MIME type:

<PRE>
function get_mime_type(&$structure)
{
$primary_mime_type = array("TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER");
if($structure->subtype)
 {
 return $primary_mime_type[(int) $structure->type] . '/' . $structure->subtype;
 }
return "TEXT/PLAIN";
}

function get_part($stream, $msg_number, $mime_type, $structure = false, $part_number = false)
{
if(!$structure)
 {
 $structure = imap_fetchstructure($stream, $msg_number);
 }
if($structure)
 {
 if($mime_type == get_mime_type($structure))
  {
  if(!$part_number)
   {
   $part_number = "1";
   }
  $text = imap_fetchbody($stream, $msg_number, $part_number);
  if($structure->encoding == 3)
   {
   return imap_base64($text);
   }
  else if($structure->encoding == 4)
   {
   return imap_qprint($text);
   }
  else
   {
   return $text;
   }
  }
 if($structure->type == 1) /* multipart */
  {
  while(list($index, $sub_structure) = each($structure->parts))
   {
   if($part_number)
   {
   $prefix = $part_number . '.';
   }
   $data = get_part($stream, $msg_number, $mime_type, $sub_structure, $prefix . ($index + 1));
   if($data)
   {
   return $data;
   }
   }
  }
 }
return false;
}

// get plain text
$data = get_part($stream, $msg_number, "TEXT/PLAIN");
// get HTML text
$data = get_part($stream, $msg_number, "TEXT/HTML");

</PRE>

Right now only the first part with the matching MIME type is returned. A more useful version would create an array and return all matching parts (for GIFs, for instance).