方案對比總覽
| 方案 | 適用場景 | 優點 | 缺點 | 推薦指數 |
| mail() | 簡單通知、無附件、低併發 | 零依賴、一行代碼 | 無 SMTP 認證、易進垃圾箱、無附件/HTML 支援差、無錯誤細節 | ⭐ |
| qmail/sendmail 管道 | 本地 MTA 已配置、高吞吐 | 直接交給本地郵件隊列、效能極佳、支援管道流式寫入 | 依賴系統 MTA 配置、Windows 不支援、除錯困難、需 root 權限管理隊列 | ⭐⭐⭐ (內網/高併發) |
| PHPMailer | 通用 Web 應用、SMTP 認證、附件、HTML、多語言 | 功能最全、社區活躍、Composer 安裝、支援 OAuth2、DKIM 簽名、異步隊列整合 | 類庫體積較大 (含 SMTP/POP3/IMAP)、舊版有安全漏洞需更新 | ⭐⭐⭐⭐⭐ (首選) |
| PEAR Net_SMTP | 輕量、不想引入大型框架、純 SMTP 協議控制 | 代碼精簡、PEAR 標準、底層協議可見、適合教學/嵌入式 | 需手動處理 MIME、Base64、附件編碼、無現代功能 (OAuth2、DKIM)、PEAR 維護緩慢 | ⭐⭐ |
PHPMailer 現代最佳實踐 (Composer 安裝)
composer require phpmailer/phpmailer
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
$mail = new PHPMailer(true);
try {
// SMTP 設定
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com'; // 或 sendgrid, mailgun, outlook
$mail->SMTPAuth = true;
$mail->Username = 'your@email.com';
$mail->Password = 'app_specific_password'; // 不要用主密碼
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // 587 port
$mail->Port = 587;
// 發件/收件
$mail->setFrom('no-reply@example.com', '系統通知');
$mail->addAddress('user@example.com', '用戶名稱');
$mail->addReplyTo('support@example.com', '客服');
// 內容
$mail->isHTML(true);
$mail->Subject = '訂單確認 #' . $orderId;
$mail->Body = view('emails.order_confirm', ['order' => $order])->render(); // Blade/Twig 模板
$mail->AltBody = strip_tags($mail->Body); // 純文本備選
// 附件
$mail->addAttachment(storage_path('app/invoices/' . $orderId . '.pdf'), "訂單{$orderId}.pdf");
// 進階
$mail->DKIM_domain = 'example.com';
$mail->DKIM_private = file_get_contents('/etc/dkim/private.key');
$mail->DKIM_selector = 'mail';
$mail->DKIM_passphrase = '';
$mail->DKIM_identity = $mail->From;
$mail->send();
Log::info("郵件發送成功: {$orderId}");
} catch (Exception $e) {
Log::error("郵件發送失敗: {$mail->ErrorInfo}");
// 丟入重試隊列 (Laravel Queue, Symfony Messenger, etc.)
}
qmail 管道發送 (高併發內網通知)
<?php
function sendViaQmail(string $to, string $subject, string $htmlBody, string $from = 'system@example.com'): bool {
$cmd = '/var/qmail/bin/qmail-inject ' . escapeshellarg($to);
$fp = popen($cmd, 'w');
if (!$fp) return false;
$headers = [
"From: $from",
"Return-Path: $from",
"To: $to",
"Subject: $subject",
"Mime-Version: 1.0",
"Content-Type: text/html; charset=UTF-8",
"", // 空行分隔 Header/Body
$htmlBody,
];
fwrite($fp, implode("\r\n", $headers));
return pclose($fp) === 0;
}
// 批量發送範例 (配合 pcntl_fork 或隊列)
foreach ($users as $user) {
$pid = pcntl_fork();
if ($pid === 0) { // 子進程
sendViaQmail($user->email, '通知', $html);
exit(0);
}
}
// 父進程回收
while (pcntl_waitpid(0, $status) != -1) {}
PEAR Net_SMTP 手動協議控制 (教學/極簡環境)
<?php
require_once 'Net/SMTP.php';
$smtp = new Net_SMTP('smtp.example.com', 587);
$smtp->connect();
$smtp->helo('example.com');
$smtp->auth('your@email.com', 'password', 'LOGIN'); // 或 PLAIN
$smtp->mailFrom('your@email.com');
$smtp->rcptTo('target@example.com');
$date = date('r');
$headers = "Date: $date\r\nFrom: your@email.com\r\nTo: target@example.com\r\nSubject: Test\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n";
$body = "Hello World\r\n";
$smtp->data($headers . $body);
$smtp->disconnect();
選型決策樹
- **需要附件、HTML、SMTP 認證、DKIM、OAuth2、隊列整合** → **PHPMailer** (或 Symfony Mailer、Laravel Mail 封裝層)。
- **本地已有 Postfix/qmail、極高吞吐 (萬封/分)、無需附件、內網通知** → **管道/送遞程式** (配合多進程/隊列)。
- **極簡環境、無 Composer、僅需純文本 SMTP、學習協議** → **PEAR Net_SMTP** 或自寫
fsockopen SMTP 客戶端。
- **僅測試、臨時腳本、無生產需求** → **mail()** (但請設定
sendmail_path 指向真實 MTA)。
通用避坑指南
- SPF/DKIM/DMARC:網域必須配置,否則進垃圾箱或被拒收。
- 專用發信 IP/第三方服務:SendGrid、Mailgun、Amazon SES、Postmark 等,提升送達率、提供退信/打開追蹤 Webhook。
- 非同步發送:Web 請求**不可**同步等待 SMTP 回應,必須寫入 Redis/RabbitMQ/資料庫隊列,Worker 後台發送。
- 退信處理:訂閱 List-Unsubscribe Header、處理 Bounce Webhook、定期清理無效郵箱。