PHP 內置 mail() 函數不直接支持附件,需手動構造符合 MIME 標準的多部件郵件體。本文提供一個完整的 CMailFile 類,演示如何構建包含文本內容與 Base64 編碼附件的郵件,並通過 mail() 發送。
帶附件郵件本質是 multipart/mixed 類型,包含兩個部分:
text/plain 或 text/html,郵件正文。application/octet-stream 或具體 MIME 類型,內容經 Base64 編碼,並聲明 Content-Disposition: attachment。各部分通過 Boundary(邊界字符串) 分隔,Boundary 需在 Header 中聲明且正文中唯一。
<?php
class CMailFile {
var $subject;
var $addr_to;
var $text_body;
var $text_encoded;
var $mime_headers;
var $mime_boundary = "--==================_846811060==_"; // 唯一邊界標識
var $smtp_headers;
/**
* 構造函數:組裝郵件各部分
* @param string $subject 主題
* @param string $to 收件人
* @param string $from 發件人
* @param string $msg 正文內容
* @param string $filename 附件本地路徑
* @param string $downfilename 附件下載顯示名
* @param string $mimetype 附件 MIME 類型
* @param string $mime_filename 附件在郵件中顯示的文件名(可選)
*/
function CMailFile($subject, $to, $from, $msg, $filename, $downfilename, $mimetype = "application/octet-stream", $mime_filename = false) {
$this->subject = $subject;
$this->addr_to = $to;
$this->smtp_headers = $this->write_smtpheaders($from);
$this->text_body = $this->write_body($msg);
$this->text_encoded = $this->attach_file($filename, $downfilename, $mimetype, $mime_filename);
$this->mime_headers = $this->write_mimeheaders($filename, $mime_filename);
}
// 讀取文件並 Base64 編碼
function encode_file($sourcefile) {
if (is_readable($sourcefile)) {
$fd = fopen($sourcefile, "r");
$contents = fread($fd, filesize($sourcefile));
$encoded = chunk_split(base64_encode($contents)); // 按 RFC 2045 每 76 字符換行
fclose($fd);
return $encoded;
}
return '';
}
// 構建附件部分
function attach_file($filename, $downfilename, $mimetype, $mime_filename) {
$encoded = $this->encode_file($filename);
if ($mime_filename) $filename = $mime_filename;
$out = "--" . $this->mime_boundary . "\n";
$out .= "Content-Type: " . $mimetype . "; name=\"$filename\"\n";
$out .= "Content-Transfer-Encoding: base64\n";
$out .= "Content-Disposition: attachment; filename=\"$downfilename\"\n\n";
$out .= $encoded . "\n";
$out .= "--" . $this->mime_boundary . "--\n";
return $out;
}
// 發送郵件
function sendfile() {
$headers = $this->smtp_headers . $this->mime_headers;
$message = $this->text_body . $this->text_encoded;
return mail($this->addr_to, $this->subject, $message, $headers);
}
// 構建文本正文部分
function write_body($msgtext) {
$out = "--" . $this->mime_boundary . "\n";
$out .= "Content-Type: text/plain; charset=\"utf-8\"\n\n"; // 建議使用 UTF-8
$out .= $msgtext . "\n";
return $out;
}
// 構建 MIME 頭部
function write_mimeheaders($filename, $mime_filename) {
if ($mime_filename) $filename = $mime_filename;
$out = "MIME-Version: 1.0\n";
$out .= "Content-Type: multipart/mixed;\n";
$out .= " boundary=\"$this->mime_boundary\"\n";
$out .= "Content-Transfer-Encoding: 7BIT\n";
$out .= "X-Attachments: $filename\n\n";
return $out;
}
// 構建 SMTP 基礎頭部
function write_smtpheaders($addr_from) {
$out = "From: $addr_from\n";
$out .= "Reply-To: $addr_from\n";
$out .= "X-Mailer: PHP/" . phpversion() . "\n";
$out .= "X-Sender: $addr_from\n";
return $out;
}
}
?>
<?php
require_once('emailclass.php');
// 郵件參數
$subject = "測試帶附件郵件";
$sendto = 'recipient@example.com';
$replyto = 'sender@example.com';
$message = "這是郵件正文內容,支持中文。";
$filename = 'test.jpg'; // 服務器上的附件路徑
$downfilename = 'photo.jpg'; // 用戶下載時顯示的文件名
$mimetype = "image/jpeg"; // 附件 MIME 類型
$mail = new CMailFile($subject, $sendto, $replyto, $message, $filename, $downfilename, $mimetype);
if ($mail->sendfile()) {
echo "郵件發送成功";
} else {
echo "郵件發送失敗";
}
?>
| 項目 | 說明 | 建議 |
|---|---|---|
| Boundary 生成 | 固定字符串可能衝突 | 使用 md5(uniqid(time())) 動態生成 |
| 字符編碼 | 正文 charset 為 us-ascii | 改為 UTF-8,中文不會亂碼 |
| 大附件處理 | 全量讀入內存 | 大文件建議分塊讀取編碼,或改用 PHPMailer 流式處理 |
| 錯誤處理 | mail() 返回 bool | 生產環境應記錄錯誤日志,配合 SPF/DKIM 提高送達率 |
| 多附件支持 | 類僅支持單附件 | 擴展 attach_file 為循環調用,累加到 text_encoded |
雖然原生實現有助於理解 MIME 結構,但生產環境強烈建議使用成熟庫:
<?php
// PHPMailer 發送帶附件郵件(簡單幾行)
use PHPMailer\PHPMailer\PHPMailer;
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'user';
$mail->Password = 'pass';
$mail->setFrom('from@example.com', 'Sender');
$mail->addAddress('to@example.com');
$mail->Subject = 'Subject';
$mail->Body = 'HTML body';
$mail->addAttachment('/path/to/file.jpg', 'photo.jpg');
$mail->send();
?>
理解原生 MIME 封裝原理有助於排查郵件發送問題,但實際項目中請優先選擇成熟庫,避免重複造輪子並踩坑。