首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 發送帶附件郵件完整實現指南

     編輯:PHP 相關知識

前言:理解 MIME 多部件結構是關鍵

PHP 內建 mail() 不直接支援附件,需手動構建符合 RFC 2045/2046 標準的 MIME 多部件郵件。本文從原理解析到完整代碼封裝,助你實現純文本/HTML 正文、多附件、內嵌圖片、編碼兼容的健壯發送能力。

一、附件工作原理:MIME 多部件結構

帶附件郵件本質是單個文本文件,包含多個「部件」,通過 邊界字符串 分隔:

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===--
  • multipart/mixed: 郵件包含獨立部件 (正文 + 附件)。
  • boundary: 隨機唯一字符串,不出現在內容中。
  • 每部件含: Content-Type、Content-Transfer-Encoding、Content-Disposition (附件時)、內容體。
  • 編碼: 二進制附件必須 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 "發送失敗,請檢查郵件日志"; }

四、進階:內嵌圖片 (HTML 郵件引用附件圖片)

使用 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 = 'Logo

內容...

'; // 函數內部需根據 cid/disposition 調整部件 Header

五、生產環境建議:棄用 mail(),改用專業庫

方案優點適用場景
PHPMailer功能全、SMTP 認證、TLS/SSL、多附件、內嵌圖片、異步隊列支援、活躍維護✅ 所有生產項目首選
Symfony Mailer現代組件化、支援多種傳輸 (SMTP、Sendmail、Mailgun、SendGrid)、模板引擎整合Symfony/Laravel 項目
Swift Mailer成熟穩定 (已停止維護,併入 Symfony Mailer)存量項目維護
原生 mail()無依賴、簡單場景⚠️ 僅測試/極簡環境,無 SMTP 認證極易進垃圾箱

PHPMailer 快速上手

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}";
}

六、常見坑與解決

  • 郵件進垃圾箱: 配置 SPF/DKIM/DMARC、使用可信 SMTP 服務商、避免垃圾詞彙、提供退訂鏈接。
  • 大附件失敗: mail() 受限於 post_max_size、memory_limit、MTA 限制。改用 SMTP 分塊或雲郵件服務 (SendGrid, Mailgun, AWS SES)。
  • 中文文件名亂碼: RFC 2231 編碼:filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf,PHPMailer 自動處理。
  • Windows 換行符: Header 必須用 \r\n,Body 部件間亦用 \r\n。
  • 並發發送: 使用隊列 異步處理,避免阻塞 Web 進程。

結語

理解 MIME 結構能讓你在無依賴環境下實現附件發送,但生產系統強烈建議使用 PHPMailer 等成熟庫,獲得 SMTP 認證、加密、異步、模板、彈性傳輸等企業級能力。郵件發送看似簡單,送達率、合規性、安全性才是核心挑戰——選對工具,專注業務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved