PHP 內建 mail() 函數依賴本地 MTA (sendmail/postfix),且不原生支援附件。本文提供一個純 PHP 實現的 CMailFile 類,手動構建 MIME multipart/mixed 結構,支援 Base64 編碼附件、自定義檔名、MIME 類型,無需外部依賴即可發送帶附件郵件。
<?php
class CMailFile {
var $subject; // 郵件主旨
var $addr_to; // 收件人
var $text_body; // 純文字內容部份
var $text_encoded; // 附件編碼部份
var $mime_headers; // MIME 標頭
var $mime_boundary = "--==================_846811060==_"; // 邊界分隔符 (唯一隨機字串更佳)
var $smtp_headers; // SMTP 標頭
/**
* @param string $subject 主旨
* @param string $to 收件人
* @param string $from 寄件人
* @param string $msg 郵件純文字內容
* @param string $filename 附件實體路徑
* @param string $downfilename 附件下載顯示檔名
* @param string $mimetype MIME 類型 (預設 application/octet-stream)
* @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 '';
}
/** 組裝附件 MIME 部份 */
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);
}
/** 純文字內容 MIME 部份 */
function write_body($msgtext) {
$out = "--" . $this->mime_boundary . "\n";
$out .= "Content-Type: text/plain; charset=\"us-ascii\"\n\n";
$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; 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 'CMailFile.php';
// 參數設定
$subject = "測試帶附件郵件";
$sendto = 'recipient@example.com';
$replyto = 'sender@example.com';
$message = "這是一封測試郵件,包含圖片附件。";
$filename = __DIR__ . '/test.jpg'; // 伺服器實體路徑
$downfilename = 'photo.jpg'; // 收件者看到的檔名
$mimetype = "image/jpeg";
$mail = new CMailFile($subject, $sendto, $replyto, $message, $filename, $downfilename, $mimetype);
if ($mail->sendfile()) {
echo "郵件發送成功!";
} else {
echo "郵件發送失敗,請檢查 mail() 設定與錯誤日誌。";
}
?>
原類僅支援單一附件與純文字。實務上常需「多附件」+「HTML 郵件」。建議改用 PHPMailer 或 Symfony Mailer 等成熟庫,但若必須零依賴,可參考以下擴展思路:
attach_file() 回傳陣列,sendfile() 迴圈拼接多個附件部份。write_body() 新增 text/html 部份,結構改為 multipart/alternative 包裹 text/plain + text/html,最外層為 multipart/mixed 掛載附件。$mime_boundary 建議使用 md5(uniqid(time())) 動態生成,避免衝突。| 現象 | 原因 | 解決 |
|---|---|---|
| 收不到信 | 本地無 MTA、sendmail_path 錯誤、IP 被封、進垃圾桶 | 檢查 php.ini sendmail_path、使用 SMTP 直發庫、設定 SPF/DKIM |
| 附件損壞/無法開啟 | Base64 編碼錯誤、二進位讀取模式缺失、邊界格式錯誤 | Windows 下 fopen 用 "rb"、確認 chunk_split、結尾 --boundary-- |
| 中文檔名亂碼 | RFC 2231 編碼缺失 | 檔名參數用 =?UTF-8?B?base64?= 格式 |
| 發送超時 | SMTP 伺服器回應慢、防火牆阻擋 25/465/587 埠 | 增加 max_execution_time、檢查網路連通性 |
CMailFile 展示了 MIME 郵件構建的核心原理,適合學習或極簡環境。正式專案強烈建議採用 PHPMailer (SMTP 直發、多附件、HTML、內嵌圖片、DKIM 簽名、異步佇列) 等成熟方案,避免重複造輪子踩坑。