標準郵件格式 multipart/mixed 包含 multipart/alternative (純文字/HTML 版本) 與 multipart/related (HTML 內嵌圖片) 及附件。html_mime_mail 類別封裝此複雜結構。
<?php
class html_mime_mail {
var $headers = [];
var $body = '';
var $multipart = '';
var $mime = '';
var $html = '';
var $html_text = '';
var $html_images = []; // ['name'=>'logo.png', 'cid'=>'...', 'path'=>'/path/logo.png']
var $cids = [];
var $do_html = false;
var $parts = []; // 附件
function __construct($headers = '') {
$this->headers = $headers;
}
// 新增 HTML 版本與純文字備選
function add_html($html, $text) {
$this->do_html = 1;
$this->html = $html;
$this->html_text = $text;
// 將 HTML 中的圖片 src 替換為 cid:
if (is_array($this->html_images) && count($this->html_images) > 0) {
foreach ($this->html_images as $img) {
$this->html = str_replace($img['name'], 'cid:'.$img['cid'], $this->html);
}
}
}
// 新增內嵌圖片
function add_html_image($path, $name, $cid = '') {
$cid = $cid ?: md5(uniqid(time()));
$this->html_images[] = ['path'=>$path, 'name'=>$name, 'cid'=>$cid];
$this->cids[$name] = $cid;
}
// 新增附件
function add_attachment($path, $name, $ctype = 'application/octet-stream', $disposition = 'attachment') {
$this->parts[] = ['path'=>$path, 'name'=>$name, 'ctype'=>$ctype, 'disposition'=>$disposition];
}
// 建構完整郵件本體
function build_message() {
$boundary = '=_'.md5(uniqid(time()));
$this->mime = "This is a MIME encoded message.\n";
$this->mime .= "--$boundary\n";
if ($this->do_html) {
$this->build_html($boundary);
} else {
$this->mime .= "Content-Type: text/plain; charset=utf-8\n";
$this->mime .= "Content-Transfer-Encoding: 7bit\n\n";
$this->mime .= $this->body . "\n";
}
// 附件
foreach ($this->parts as $part) {
$this->mime .= "--$boundary\n";
$this->mime .= "Content-Type: {$part['ctype']}; name=\"{$part['name']}\"\n";
$this->mime .= "Content-Transfer-Encoding: base64\n";
$this->mime .= "Content-Disposition: {$part['disposition']}; filename=\"{$part['name']}\"\n\n";
$this->mime .= chunk_split(base64_encode(file_get_contents($part['path']))) . "\n";
}
$this->mime .= "--$boundary--\n";
// 標頭加入 MIME 版本與邊界
$this->headers[] = "MIME-Version: 1.0";
$this->headers[] = "Content-Type: multipart/mixed; boundary=\"$boundary\"";
}
// 建構 HTML 部分 (alternative + related)
function build_html($outerBoundary) {
$altBoundary = '=_'.md5(uniqid(time()));
$relBoundary = '=_'.md5(uniqid(time()));
// outer -> alternative
$this->mime .= "Content-Type: multipart/alternative; boundary=\"$altBoundary\"\n\n";
// alternative -> text/plain
$this->mime .= "--$altBoundary\n";
$this->mime .= "Content-Type: text/plain; charset=utf-8\n";
$this->mime .= "Content-Transfer-Encoding: 7bit\n\n";
$this->mime .= $this->html_text . "\n";
// alternative -> related (html + images)
$this->mime .= "--$altBoundary\n";
$this->mime .= "Content-Type: multipart/related; boundary=\"$relBoundary\"\n\n";
// related -> text/html
$this->mime .= "--$relBoundary\n";
$this->mime .= "Content-Type: text/html; charset=utf-8\n";
$this->mime .= "Content-Transfer-Encoding: 7bit\n\n";
$this->mime .= $this->html . "\n";
// related -> 內嵌圖片
foreach ($this->html_images as $img) {
$this->mime .= "--$relBoundary\n";
$this->mime .= "Content-Type: " . $this->get_mime_type($img['name']) . "\n";
$this->mime .= "Content-Transfer-Encoding: base64\n";
$this->mime .= "Content-ID: <{$img['cid']}>\n";
$this->mime .= "Content-Disposition: inline; filename=\"{$img['name']}\"\n\n";
$this->mime .= chunk_split(base64_encode(file_get_contents($img['path']))) . "\n";
}
$this->mime .= "--$relBoundary--\n";
$this->mime .= "--$altBoundary--\n";
}
function get_mime_type($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$types = ['png'=>'image/png','jpg'=>'image/jpeg','jpeg'=>'image/jpeg','gif'=>'image/gif','pdf'=>'application/pdf'];
return $types[$ext] ?? 'application/octet-stream';
}
// 發送
function send($to, $subject, $from) {
$this->build_message();
$headers = implode("\r\n", $this->headers);
return mail($to, $subject, $this->mime, $headers, "-f$from");
}
}
?>
<?php
require 'html_mime_mail.php';
$mail = new html_mime_mail();
$mail->add_html(
'<h1>歡迎</h1><p>這是 <img src="logo.png"> 內嵌圖片</p>',
'歡迎\n這是純文字備選版本'
);
$mail->add_html_image('/var/www/images/logo.png', 'logo.png');
$mail->add_attachment('/var/www/files/report.pdf', 'report.pdf', 'application/pdf');
$sent = $mail->send('user@example.com', '測試 HTML 郵件', 'system@example.com');
echo $sent ? '發送成功' : '發送失敗';
?>
| 特性 | html_mime_mail | PHPMailer | Symfony Mailer |
|---|---|---|---|
| SMTP 驗證 | 不支援 (僅 mail()) | 完整支援 (SSL/TLS/STARTTLS) | 完整支援 |
| 多重收件人/CC/BCC | 手動處理 | 內建 | 內建 |
| DKIM 簽名 | 無 | 支援 | 支援 |
| 異步佇列 | 無 | 需自行實作 | 整合 Messenger |
| 模板引擎 | 無 | 無 (可整合 Twig/Blade) | 原生 Twig 整合 |
| 維護狀態 | 停止更新 | 活躍 | 活躍 |
<?php
// PHPMailer 範例
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'user';
$mail->Password = 'pass';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('system@example.com', '系統');
$mail->addAddress('user@example.com');
$mail->Subject = '測試';
$mail->isHTML(true);
$mail->Body = '<h1>HTML 內容</h1>';
$mail->AltBody = '純文字備選';
$mail->addEmbeddedImage('/path/logo.png', 'logo_cid');
$mail->addAttachment('/path/report.pdf');
$mail->send();
echo '發送成功';
} catch (Exception $e) {
echo "發送失敗: {$mail->ErrorInfo}";
}
?>
html_mime_mail 展示了 MIME 多重替代結構的手工構建邏輯,理解其原理有助於除錯現代郵件庫的發送問題。生產環境請直接使用 PHPMailer 或 Symfony Mailer。