使用 PHP 在电子邮件中发送多个附件

Posted

技术标签:

【中文标题】使用 PHP 在电子邮件中发送多个附件【英文标题】:Sending multiple attachment in an email using PHP 【发布时间】:2015-03-01 16:57:25 【问题描述】:

我在发送包含多个附件的电子邮件时遇到问题。代码如下:

<?php

    if(isset($_POST['sendEmail']))
    

        foreach($_FILES['uploadEmail']['error'] as $key=>$value)
            if(!$_FILES['uploadEmail']['error'][$key])
                $target_path = "";
                $target_path = $target_path . basename( $_FILES['uploadEmail']['name'][$key]); 
                if(move_uploaded_file($_FILES['uploadEmail']['tmp_name'][$key], $target_path))
                    $files[] = $_FILES['uploadEmail']['name'][$key];
                
            
        

        $toEmails = explode(",",$_POST['toEmail']);
        $count = count($toEmails);
        $i = 0;    $j = 1;    $k = 100;
        $bcc = '';
        while($i<$count)
            $bcc .= $toEmails[$i].",";
            if($j==$k || $i==$count-1)
                $j=1;
                //echo $bcc.'<br />'.$sub.'<br />'.$message.'<br /><br />';
                $from = 'test@gmail.com';
                $sub = $_POST['subject'];
                $message = $_POST['message'];

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


                // boundary 
                $semi_rand = md5(time()); 
                $mime_boundary = "==Multipart_Boundary_x$semi_randx"; 

                // headers for attachment 
                $headers .= "\nMIME-Version: 1.0\n" . "Content-Type: multipart/mixed;\n" . " boundary=\"$mime_boundary\""; 

                // multipart boundary 
                $message = "This is a multi-part message in MIME format.\n\n" . "--$mime_boundary\n" . "Content-Type: text/html; charset=\"iso-8859-1\"\n" . "Content-Transfer-Encoding: 7bit\n\n" . $message . "\n\n"; 
                $message .= "--$mime_boundary\n";

                // preparing attachments
                for($x=0;$x<count($files);$x++)
                    $file = fopen($files[$x],"rb");
                    //echo "<br>".filesize($files[$x]);
                    $data = fread($file,filesize($files[$x]));
                    fclose($file);
                    $data = chunk_split(base64_encode($data));
                    $message .= "Content-Type: \"application/octet-stream\";\n" . " name=\"$files[$x]\"\n" . 
                    "Content-Disposition: attachment;\n" . " filename=\"$files[$x]\"\n" . 
                    "Content-Transfer-Encoding: base64\n\n" . $data . "\n\n";
                    $message .= "--$mime_boundary\n";
                

                    // send
                    /////////////////////////

                mail('',$sub,$message,$headers);
                $bcc = '';
            else
                $j++;
            
            $i++;
        
    
?>
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>

<script src="http://js.nicedit.com/nicEdit-latest.js" type="text/javascript"></script>
<script type="text/javascript">bkLib.onDomLoaded(function() 
        new nicEditor().panelInstance('message');
       // new nicEditor(fullPanel : true).panelInstance('area2');
  );</script>
</head>
<body>
<form method="POST" enctype="multipart/form-data">
    <table>
        <tr><td><label for="toEmail">Send To : </label></td><td><textarea id="toEmail" name="toEmail" cols="100" rows="10"></textarea></td></tr>
        <tr><td><label for="subject">Subject : </label></td><td><input type="text" name="subject" id="subject" size="98"></td></tr>
        <tr><td><label for="toEmail">Message : </label></td><td><textarea id="message" name="message" cols="100" rows="10"></textarea></td></tr>
        <tr><td><label for="upload[]">Attachments:</label></td><td></td></tr>
        <tr><td><label>1</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>2</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>3</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>4</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>5</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>6</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>7</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>8</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>9</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td><label>10</label></td><td><input type="file" name="uploadEmail[]"></td></tr>
        <tr><td colspan="2" align="center"><input type="submit" value="Send Email" name="sendEmail" id="sendEmail"></td></tr>
    </table>
</form>
<body>
</html>

我收到了邮件,但找不到任何附件。 有谁知道可能出了什么问题?

这是我收到的邮件正文:

MIME 版本:1.0 内容类型:多部分/混合;
边界="==Multipart_Boundary_x2d454346f03d2c19cfefc838ce4d8623x"

这是 MIME 格式的多部分消息。

--==Multipart_Boundary_x2d454346f03d2c19cfefc838ce4d8623x 内容类型:文本/html;字符集="iso-8859-1"
内容传输编码:7bit

ds fsdfsdfsdfsdfsdfsdfsf sffdfsdfsdfs fsdfdf sdf s

--==Multipart_Boundary_x2d454346f03d2c19cfefc838ce4d8623x Content-Type: "application/octet-stream";名称="/tmp/phpHFTvAw"
内容处置:附件;文件名="灯塔.jpg"
内容传输编码:base64




--==Multipart_Boundary_x2d454346f03d2c19cfefc838ce4d8623x Content-Type: "application/octet-stream";名称="/tmp/phpyX67HR"
内容处置:附件;文件名="企鹅.jpg"
内容传输编码:base64




--==Multipart_Boundary_x2d454346f03d2c19cfefc838ce4d8623x

【问题讨论】:

【参考方案1】:

我建议使用PHPMailer 发送带有附件的邮件:

<?php

require 'PHPMailerAutoload.php';                      // If this file is not located in the same directory, use __DIR__ . "/path/to/PHPMailerAutoload.php"

$mail = new PHPMailer;

$mail->From = 'from@example.com';
$mail->FromName = 'Mailer';
$mail->addAddress('joe@example.net', 'Joe User');     // Add a recipient
$mail->addAddress('ellen@example.com');               // Name is optional
$mail->addReplyTo('info@example.com', 'Information');
$mail->addCC('cc@example.com');
$mail->addBCC('bcc@example.com');

$mail->addAttachment('/var/tmp/file.tar.gz');         // Add attachments
$mail->addAttachment('/tmp/image.jpg', 'new.jpg');    // Optional name
$mail->isHTML(true);                                  // Set email format to HTML

$mail->Subject = 'Here is the subject';
$mail->Body    = 'This is the HTML message body <b>in bold!</b>';
$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

if(!$mail->send()) 
    echo 'Message could not be sent.';
    echo 'Mailer Error: ' . $mail->ErrorInfo;
 else 
    echo 'Message has been sent';


?>

下载和文档:here。

【讨论】:

感谢您的回答,但要求是仅使用邮件功能而不使用 phpmailer 发送电子邮件。【参考方案2】:

回答

我在下面详细说明了您的代码存在一些问题。

行尾

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

...

// headers for attachment 
$headers .= "\nMIME-Version: 1.0\n"
         .  "Content-Type: multipart/mixed;\n"
         .  " boundary=\"$mime_boundary\""; 
// multipart boundary 
$message = "This is a multi-part message in MIME format.\n\n"
         . "--$mime_boundary\n"
         . "Content-Type: text/html; charset=\"iso-8859-1\"\n"
         . "Content-Transfer-Encoding: 7bit\n\n"
         . $message
         . "\n\n"; 
$message .= "--$mime_boundary\n";

电子邮件中的行由CRLF (\r\n) 序列分隔。尚不清楚mail() 函数是否将\n 转换为\r\n,但考虑到您的From:Bcc: 标头使用\r\n,这些可能应该使用相同的。您的输出还表明行尾可能丢失或格式错误。

来自PHP Manual:

如果未收到消息,请尝试仅使用 LF (\n)。一些 Unix 邮件传输代理(尤其是 » qmail)会自动将 LF 替换为 CRLF(如果使用 CRLF,则会导致 CR 加倍)。这应该是最后的手段,因为它不符合» RFC 2822。

标题语法

$message .= "Content-Type: \"application/octet-stream\";\n"
         .  " name=\"$files[$x]\"\n" . 

去掉大括号和引号:

$message .= "Content-Type: application/octet-stream\n"
         .  " name=\"$files[$x]\"\n" . 

此外,name 参数已被弃用,取而代之的是 Content-Disposition 标头中的 filename 参数。如果要保留它以实现向后兼容性,则应从中删除路径。 (您的输出表明您使用的是tmp_name 而不是name)。

分隔符

$message .= "--$mime_boundary\n";

// preparing attachments
for($x=0;$x<count($files);$x++)
  ...
  $message .= /* body part */;
  $message .= "--$mime_boundary\n";

请注意,最后的分隔符必须有两个尾随破折号。在循环的开头插入分隔分隔符,在循环后添加关闭分隔符:

// preparing attachments
for($x=0;$x<count($files);$x++)
  $message .= "--$mime_boundary\n";
  ...
  $message .= /* body part */;


$message .= "--$mime_boundary--\n";

请参阅下面的电子邮件语法部分。

行长

$k = 100;
...
while($i<$count)
  $bcc .= $toEmails[$i].",";
  if($j==$k || $i==$count-1)
    ...
    $headers .= "Bcc:". $bcc . "\r\n";

请注意,电子邮件中存在行长限制。 RFC 5322:

 ... 每行字符不得超过
   998 个字符,并且应该不超过 78 个字符,不包括
   CRLF。

您可能希望缩短您的 Bcc 或介绍 FWS (Folding White Space):

$bcc .= $toEmails[$i].",\r\n ";  /* FWS */

其他问题

一些可能有用也可能没用的进一步问题或通知:


foreach($_FILES['uploadEmail']['error'] as $key=>$value)
    if(!$_FILES['uploadEmail']['error'][$key])

最后一行同:

    if(!$value)

$target_path = "";
$target_path = $target_path . basename( $_FILES['uploadEmail']['name'][$key]); 

我假设$target_path 应该被初始化为一个上传目录。


$toEmails = explode(",",$_POST['toEmail']);

通常,您不应允许随机用户提供外发电子邮件地址,但我怀疑这是受信任用户的内部应用程序。


电子邮件语法

这是根据 RFC 2046 的多部分消息正文结构的摘录。(BNF 语法,有些简化。)

multipart-body := [序言 CRLF] 虚线边界CRLF 身体部分 *封装 闭合分隔符 [CRLF 结语] dash-boundary := "--" 边界 body-part := MIME-part-headers [CRLF *OCTET] 封装 := 分隔符 CRLF 身体部位 delimiter := CRLF 破折号边界 close-delimiter := 分隔符“--”

参考文献

RFC 2822 Internet Message Format(已被 RFC 5322 废弃) RFC 5322 Internet Message Format RFC 2045 (MIME) Part One: Format of Internet Message Bodies RFC 2046 (MIME) Part Two: Media Types

【讨论】:

以上是关于使用 PHP 在电子邮件中发送多个附件的主要内容,如果未能解决你的问题,请参考以下文章

php : 电子邮件发送失败,附件不止一个

Python - 发送带有多个图像附件的电子邮件

使用 S/MIME (PHP) 发送带有附件的电子邮件

PHP不发送带有附件的邮件

使用命令行和 sendmail 发送带有多个附件的电子邮件

在 PHP 中,如何发送带有附件的 HTML 电子邮件?