PHP+Ajax带附件邮件发送失败问题排查与解决
带附件邮件发送失败的问题与解决方法
问题现象
无附件的邮件可以正常发送,但带附件的邮件无法发送,排查后怀疑是邮件Header配置问题,已耗费数小时仍未解决。
原代码问题分析
原代码存在几个关键错误:
- Header被意外覆盖:构建带附件的Header时,第二行代码直接用
=覆盖了之前定义的MIME-Version,导致邮件客户端无法识别多部分邮件格式 - 邮件内容与Header混淆:把邮件正文、附件编码内容都塞进了
$headers变量,而PHP的mail()函数要求邮件内容单独作为第三个参数传入,这种结构会完全打乱邮件格式 - 固定边界符易冲突:使用固定字符串的MD5值作为边界符,可能和邮件内容中的字符重复,导致分界失效
- 冗余嵌套结构:不必要的
multipart/alternative嵌套增加了结构复杂度,容易引发格式错误
原代码
<?php if($_POST) { $to_email = "xxxx@icloud.com"; $from_email = "noreply@xxx.pl"; $subject = "subject"; $cnc = $_POST["cnc_wheels"]; $pain = $_POST["new_paint_wheels"]; $message_body = "email data"; $file_attached = false; if(isset($_FILES["font_img_wheels"])) { $file_tmp_name = $_FILES['font_img_wheels']['tmp_name']; $file_name = $_FILES['font_img_wheels']['name']; $file_size = $_FILES['font_img_wheels']['size']; $file_type = $_FILES['font_img_wheels']['type']; $file_error = $_FILES['font_img_wheels']['error']; if($file_error>0) { $mymsg = array( 1=>"上传的文件超过了php.ini中upload_max_filesize的限制", 2=>"上传的文件超过了HTML表单中指定的MAX_FILE_SIZE限制", 3=>"文件仅部分上传", 4=>"未上传任何文件", 6=>"缺少临时文件夹" ); $output = json_encode(array('type'=>'error', 'text' => $mymsg[$file_error])); die($output); } $handle = fopen($file_tmp_name, "r"); $content = fread($handle, $file_size); fclose($handle); $encoded_content = chunk_split(base64_encode($content)); $file_attached = true; } if($file_attached) { # 邮件头应适配多数客户端 $headers = "MIME-Version: 1.0\r\n"; $headers = "X-Mailer: PHP/" . phpversion()."\r\n"; $headers .= "From: ".$from_email."\r\n"; $headers .= "Subject: ".$subject."\r\n"; $headers .= "Reply-To: ".$_POST["email_client"]."" . "\r\n"; $headers .= "Content-Type: multipart/mixed; boundary=".md5('boundary1')."\r\n\r\n"; $headers .= "--".md5('boundary1')."\r\n"; $headers .= "Content-Type: multipart/alternative; boundary=".md5('boundary2')."\r\n\r\n"; $headers .= "--".md5('boundary2')."\r\n"; $headers .= "Content-Type: text/plain; charset=utf-8\r\n\r\n"; $headers .= $message_body."\r\n\r\n"; $headers .= "--".md5('boundary2')."--\r\n"; $headers .= "--".md5('boundary1')."\r\n"; $headers .= "Content-Type: ".$file_type."; "; $headers .= "name=\"" . $file_name . "\"\r\n"; $headers .= "Content-Transfer-Encoding:base64\r\n"; $headers .= "Content-Disposition:attachment; "; $headers .= "filename=\"" . $file_name . "\"\r\n"; $headers .= "X-Attachment-Id:".rand(1000,9000)."\r\n\r\n"; $headers .= $encoded_content."\r\n"; $headers .= "--".md5('boundary1')."--"; }else{ $headers = 'From: '.$_POST["email_client"].'' . "\r\n" . 'Reply-To: '.$_POST["email_client"].'' . "\r\n" . 'X-Mailer: PHP/' . phpversion(); } $send_mail = mail($to_email, $subject, $message_body, $headers); if(!$send_mail) { $output = json_encode(array('type'=>'error', 'text' => '邮件发送失败。')); die($output); }else { $output = json_encode(array('type'=>'message', 'text' => '感谢您的咨询,我们会尽快与您联系。')); die($output); } }
修复后的代码
修复方案核心是正确分离Header和邮件内容,规范MIME多部分邮件结构,同时支持多附件上传:
<?php if($_POST) { $to_email = "xxxx@icloud.com"; $from_email = "noreply@xxx.pl"; $subject = "subject"; $message = "email data"; $boundary = md5(time()); $headers = "From: $from_email\r\n"; $headers .= "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: multipart/mixed; boundary=\"" . $boundary . "\"\r\n"; $body = "--" . $boundary . "\r\n"; $body .= "Content-Type: text/plain; charset=\"UTF-8\"\r\n"; $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n"; $body .= $message . "\r\n"; if ($_FILES["font_img_wheels"]["size"] > 0) { $attachment_name = $_FILES["font_img_wheels"]["name"]; $attachment_tmp_name = $_FILES["font_img_wheels"]["tmp_name"]; $attachment_data = file_get_contents($attachment_tmp_name); $attachment_encoded = chunk_split(base64_encode($attachment_data)); $body .= "--" . $boundary . "\r\n"; $body .= "Content-Type: application/octet-stream; name=\"" . $attachment_name . "\"\r\n"; $body .= "Content-Transfer-Encoding: base64\r\n"; $body .= "Content-Disposition: attachment\r\n\r\n"; $body .= $attachment_encoded . "\r\n"; } if ($_FILES["second_img_wheels"]["size"] > 0) { $attachment_name = $_FILES["second_img_wheels"]["name"]; $attachment_tmp_name = $_FILES["second_img_wheels"]["tmp_name"]; $attachment_data = file_get_contents($attachment_tmp_name); $attachment_encoded = chunk_split(base64_encode($attachment_data)); $body .= "--" . $boundary . "\r\n"; $body .= "Content-Type: application/octet-stream; name=\"" . $attachment_name . "\"\r\n"; $body .= "Content-Transfer-Encoding: base64\r\n"; $body .= "Content-Disposition: attachment\r\n\r\n"; $body .= $attachment_encoded . "\r\n"; } $body .= "--" . $boundary . "--"; if(mail($to_email, $subject, $body, $headers)) { $output = json_encode(array('type'=>'message', 'text' => '感谢您的咨询,我们会尽快与您联系。')); die($output); }else { $output = json_encode(array('type'=>'error', 'text' => '邮件发送失败。')); die($output); } }
修复说明
- 动态唯一边界符:用
md5(time())生成随时间变化的边界符,避免和内容冲突 - 分离Header与邮件内容:Header只保留发件人、MIME版本、内容类型等元信息,邮件正文和附件都放在
$body中 - 简化MIME结构:去掉冗余的嵌套结构,直接使用
multipart/mixed处理带附件的邮件 - 简化文件读取:用
file_get_contents()替代手动打开/读取文件的操作,代码更简洁 - 支持多附件:增加了第二个附件的处理逻辑,可扩展更多附件
内容的提问来源于stack exchange,提问作者Tomek
相关产品推荐
相关产品推荐

