January 30, 2009

Sending mail Attachment(s) with PHP mail function (PART - II) : The Code

In my previous post we saw the thumb rules for emailing using php mail function. Now we will implement those rules in code.

I have written a function sendMail, which receives mail subject, message, senders email id and destination id. I also accept three optional parameters, namely cc, bcc and attachment file list. The function prepares appropriate mail headers and call mail function to send mail via SMTP server. The function returns true for mailing success and otherwise it will return false.


function
sendMail( $subject, $message, $from, $to, $cc=null, $bcc=null, $attachment=null)

{

$headers = 'From: '.$from."\r\n";

if(!is_null($cc)){

$headers .= 'Cc: '.$cc ."\r\n";

}

if(!is_null($bcc)){

$headers .= 'Bcc: '.$bcc ."\r\n";

}

$headers .= 'MIME-Version: 1.0'."\r\n";

if(is_array($attachment) && count($attachment)>0){

$headers .= mailAttachmentHeader ($attachment, $message);

} else {

$headers .= 'Content-type: text/html; charset=iso-8859-1'."\r\n";

}

if(is_array($attachment) && count($attachment)>0){

$message = '';

}

$mailStatus = mail($to,$subject,$message,$headers);

echo "\nNew Mailer\n";

if($mailStatus){

return true;

}else{

return false;

}

}


In mail function "Form ", "Cc " , "Bcc", "'MIME-Version" set without checking if attachment is present or not – Rule 1.

Next we check is if attachments are present.
In case of no attachment we set 'Content-type: text/html; charset=iso-8859-1'."\r\n" Rule 2.a. Otherwise we call the mailAttachmentHeader function to prepare the attachment header

The massage is set to blank Rule 3.b.

function mailAttachmentHeader($attachment, $message )

{

$mime_boundary = md5(time());

$xMessage = "Content-Type: multipart/mixed; boundary=\"".$mime_boundary."\"\r\n\r\n";

$xMessage .= "--".$mime_boundary."\r\n\r\n";

$xMessage .= "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n";

$xMessage .= "Content-Transfer-Encoding: 7bit\r\n";

$xMessage .= $message."\r\n\r\n";

foreach($attachment as $file)

{

$xMessage .= "--".$mime_boundary."\r\n";

$xMessage .= "Content-Type: application/octet-stream; name=\"".basename($file)."\"\r\n";

$xMessage .= "Content-Transfer-Encoding: base64\r\n";

$xMessage .= "Content-Disposition: attachment; filename=\"".basename($file)."\"\r\n";

$content = file_get_contents($file);

$xMessage.= chunk_split(base64_encode($content));

$xMessage .= "\r\n\r\n";

}

$xMessage .= "--".$mime_boundary."--\r\n\r\n";

return $xMessage;

}


Parameters passed to mailAttachmentHeader function are list of attachments and the functions returns headers string.

In mailAttachmentHeader function we set "Content-Type: multipart/mixed; bundary=\"".$mime_boundary."\"\r\n\r\n"; where value of $mime_boundary is the MD5 of current timestamp. Rule 2.b.

To denote starting of message and each attachment following code is added
$xMessage .= "--".$mime_boundary."\r\n\r\n"; Rule 3.b.

We add Content-type “text/html” for message and “application/octet-stream” for attachments – Rule 4

For each attachment we add "Content-Transfer-Encoding: base64\r\n"; Rule 5 and "Content-Disposition: attachment; filename=\"".basename($file)."\"\r\n"; Rule 6

At the end of the function we add "--".$mime_boundary."--\r\n\r\n"; to denote the end of attachment mail header Rule 7

Now you can use the send mail function to send mail with or without any attachment. Cheers!!

(c) Sourav Ray Creative Commons License

5 comments:

  1. Can you show an example of the sendMail function when using attachments? Thank you!

    ReplyDelete
  2. Thank you for this code. However it didn't work for me as is due to line feeds being in the wrong places. Specifically, there was one too many linefeeds after the first mime boundary, one too few line feeds after the 7-bit content transfer encoding header line, and one too few line feeds after the Content disposition header line. I changed it to the following and it worked:

    $mime_boundary = md5(time());
    $xMessage = "Content-Type: multipart/mixed; boundary=\"".$mime_boundary."\"\r\n\r\n";
    $xMessage .= "--".$mime_boundary."\r\n";
    $xMessage .= "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n";
    $xMessage .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
    $xMessage .= $message."\r\n\r\n";

    foreach($attachment as $file)
    {
    $xMessage .= "--".$mime_boundary."\r\n";
    $xMessage .= "Content-Type: application/octet-stream; name=\"".basename($file)."\"\r\n";
    $xMessage .= "Content-Transfer-Encoding: base64\r\n";
    $xMessage .= "Content-Disposition: attachment; filename=\"".basename($file)."\"\r\n\r\n";
    $content = file_get_contents($file);
    $xMessage.= chunk_split(base64_encode($content));
    $xMessage .= "\r\n\r\n";
    }

    ReplyDelete
  3. @Jim ... Your problems seems to be a strange one because I can see what you have done is mostly manipulate the line breaks in the headers
    1. you reduce 1 \r\n when we first time starts mime boundary
    2. you increase 1 \r\n after adding the Content-Transfer-Encoding for actual message
    3. you you also put an addition \r\n after Content-Disposition of each file

    I don't believe these line breaks can fail your code. The given code is running as mail attachment dispatcher for one big site and that is running without any trouble for long enough. Any way I want to see the mail header that was generated by the original code. you can mail me the header to my mail address ray.on.php@gmail.com

    @Hayley hi... One my friend is actually working to implement those Rules of Thumb with sendMail , if he puts it on his Linux Blog then I will put a link to his post :)

    ReplyDelete
  4. Hi Souray,

    Not sure if it depends on the email client? I used your code to mail Outlook and the way the line breaks were being outputted totally confused Outlook.

    Specifically, the Content-Type: text/plain; charset=iso-8859-1 and Content-Transfer-Encoding: 7bit headers showed up INSIDE the message, and the 100k attachment showed up as 137 bytes.

    It appears to me, at least for Outlook's parsing of email, that the headers for each section must start immediately after the boundary string. If there is an extra line break after the boundary string, the headers are not interpreted as headers but as part of the message, and that after the last header, there needs to be a line break.

    The modified version of your code now outputs the following clean headers, which Outlook likes very much. Here is an example..

    -Jim

    From: someone
    MIME-Version: 1.0
    Content-Type: multipart/mixed; boundary="917b1460c3a3e9247da2eec5df1225ff"

    --917b1460c3a3e9247da2eec5df1225ff
    Content-Type: text/plain; charset="iso-8859-1"
    Content-Transfer-Encoding: 7bit

    Here is your file

    --917b1460c3a3e9247da2eec5df1225ff
    Content-Type: application/octet-stream; name="0408-173751-Jim1.pdf"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename="0408-173751-Jim1.pdf"

    JVBERi0xLjQKJeLjz9MKCjEgMCBvYmoKPDwvVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKL091
    ...

    ReplyDelete
  5. From Tom

    Hi Souray
    first of all thank you so much for posting this script, however i have a little problem all attachments i sent get somehow corrupted the files ( zip format) shows up as 0KB, when I try to unzip them there is no content inside, i tried with many files types same problem.

    Not sure what I am doing wrong?

    Thank you.

    ReplyDelete