PHP 內建 mail() 不直接支援附件,需手動構建符合 RFC 2045/2046 標準的 MIME 多部件郵件。本文從原理解析到完整代碼封裝,助你實現純文本/HTML 正文、多附件、內嵌圖片、編碼兼容的健壯發送能力。
帶附件郵件本質是單個文本文件,包含多個「部件」,通過 邊界字符串 分隔:
From: sender@example.com
To: recipient@example.com
Subject: Test with Attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===BOUNDARY==="
---===BOUNDARY===
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
SGVsbG8gV29ybGQh (base64 編碼的純文本正文)
---===BOUNDARY===
Content-Type: application/pdf; name="report.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0xLjQK... (base64 編碼的文件二進制內容)
---===BOUNDARY===--
base64_encode;文本可用 quoted-printable 或 base64。<?php
/**
* 發送帶附件郵件
* @param string|array $to 收件人
* @param string $subject 主題
* @param string $bodyText 純文本正文
* @param string $bodyHtml HTML 正文 (可選)
* @param array $attachments 附件陣列 [
* ['path' => '/path/file.pdf', 'name' => 'report.pdf', 'type' => 'application/pdf'],
* ['content' => $binary, 'name' => 'data.csv', 'type' => 'text/csv']
* ]
* @param string $from 發件人
* @param string $replyTo 回覆地址
* @return bool
*/
function sendMailWithAttachments($to, $subject, $bodyText, $bodyHtml = '', $attachments = [], $from = '', $replyTo = '') {
$from = $from ?: ini_get('sendmail_from') ?: 'noreply@' . $_SERVER['SERVER_NAME'];
$to = is_array($to) ? implode(', ', $to) : $to;
$boundary = '===PHP_MAIL_' . md5(uniqid(microtime(true), true)) . '===';
$eol = "\r\n";
// 構建 Header
$headers = "From: $from$eol";
$headers .= "Reply-To: $replyTo$eol" . ($replyTo ? '' : "Reply-To: $from$eol");
$headers .= "MIME-Version: 1.0$eol";
$headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"$eol";
$headers .= "X-Mailer: PHP/" . phpversion() . "$eol";
// 構建 Body
$body = "--$boundary$eol";
// 純文本部件
$body .= "Content-Type: text/plain; charset=UTF-8$eol";
$body .= "Content-Transfer-Encoding: base64$eol$eol";
$body .= base64_encode($bodyText) . "$eol";
// HTML 部件 (若有)
if ($bodyHtml) {
$body .= "--$boundary$eol";
$body .= "Content-Type: text/html; charset=UTF-8$eol";
$body .= "Content-Transfer-Encoding: base64$eol$eol";
$body .= base64_encode($bodyHtml) . "$eol";
}
// 附件部件
foreach ($attachments as $att) {
$content = isset($att['content']) ? $att['content'] : @file_get_contents($att['path']);
if ($content === false) continue; // 跳過讀取失敗
$name = $att['name'] ?? basename($att['path']);
$type = $att['type'] ?? mime_content_type($att['path']) ?? 'application/octet-stream';
$body .= "--$boundary$eol";
$body .= "Content-Type: $type; name=\"$name\"$eol";
$body .= "Content-Transfer-Encoding: base64$eol";
$body .= "Content-Disposition: attachment; filename=\"$name\"$eol$eol";
$body .= chunk_split(base64_encode($content)) . "$eol"; // chunk_split 符合 RFC 76 行長限制
}
// 結束邊界
$body .= "--$boundary--$eol";
// 發送
return mail($to, $subject, $body, $headers);
}
<?php
require 'mail_attachment.php';
$to = 'user@example.com';
$subject = '月度報表';
$text = "您好,\n請查收附件中的月度報表。\n\n系統自動發送";
$html = '您好,
請查收附件中的月度報表。
系統自動發送
';
$attachments = [
['path' => '/var/www/reports/monthly.pdf', 'name' => 'monthly_report.pdf', 'type' => 'application/pdf'],
['path' => '/var/www/reports/data.xlsx', 'name' => 'monthly_data.xlsx', 'type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
];
if (sendMailWithAttachments($to, $subject, $text, $html, $attachments)) {
echo "發送成功";
} else {
echo "發送失敗,請檢查郵件日志";
}
使用 multipart/related 結構,附件 Content-Disposition: inline、Content-ID: <unique@id>,HTML 中 <img src="cid:unique@id">。
$cid = 'logo_' . md5('logo.png') . '@example.com';
$attachments[] = [
'path' => '/var/www/images/logo.png',
'name' => 'logo.png',
'type' => 'image/png',
'cid' => $cid,
'disposition' => 'inline',
];
// HTML 正文
$html = '
內容...
';
// 函數內部需根據 cid/disposition 調整部件 Header
| 方案 | 優點 | 適用場景 |
|---|---|---|
| PHPMailer | 功能全、SMTP 認證、TLS/SSL、多附件、內嵌圖片、異步隊列支援、活躍維護 | ✅ 所有生產項目首選 |
| Symfony Mailer | 現代組件化、支援多種傳輸 (SMTP、Sendmail、Mailgun、SendGrid)、模板引擎整合 | Symfony/Laravel 項目 |
| Swift Mailer | 成熟穩定 (已停止維護,併入 Symfony Mailer) | 存量項目維護 |
| 原生 mail() | 無依賴、簡單場景 | ⚠️ 僅測試/極簡環境,無 SMTP 認證極易進垃圾箱 |
composer require phpmailer/phpmailer
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'user@example.com';
$mail->Password = 'secret';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('sender@example.com', '系統');
$mail->addAddress('recipient@example.com');
$mail->Subject = '測試附件';
$mail->Body = 'HTML 正文';
$mail->AltBody = '純文本正文';
$mail->addAttachment('/path/file.pdf', 'rename.pdf');
$mail->send();
echo '發送成功';
} catch (Exception $e) {
echo "發送失敗: {$mail->ErrorInfo}";
}
mail() 受限於 post_max_size、memory_limit、MTA 限制。改用 SMTP 分塊或雲郵件服務 (SendGrid, Mailgun, AWS SES)。filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf,PHPMailer 自動處理。\r\n,Body 部件間亦用 \r\n。理解 MIME 結構能讓你在無依賴環境下實現附件發送,但生產系統強烈建議使用 PHPMailer 等成熟庫,獲得 SMTP 認證、加密、異步、模板、彈性傳輸等企業級能力。郵件發送看似簡單,送達率、合規性、安全性才是核心挑戰——選對工具,專注業務。