# 專案根目錄執行
composer require phpmailer/phpmailer
或手動下載放入 extend/PHPMailer(不推薦,難維護)。
<!-- config/mail.php -->
<?php
return [
'host' => 'smtp.qq.com', // SMTP 服務器
'port' => 465, // SSL 端口 465 / TLS 端口 587
'username' => 'your@qq.com', // 郵箱賬號
'password' => 'auth_code', // 授權碼(非登錄密碼)
'from_email' => 'your@qq.com', // 發件人郵箱
'from_name' => '我的應用', // 發件人顯示名
'charset' => 'UTF-8',
'timeout' => 30,
];
<?php
// app/service/MailService.php
namespace app\service;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use think\facade\Config;
class MailService {
protected static ?self $instance = null;
protected PHPMailer $mailer;
private function __construct() {
$this->mailer = new PHPMailer(true);
$this->configure();
}
public static function instance(): self {
return self::$instance ??= new self();
}
protected function configure(): void {
$cfg = Config::get('mail');
$m = $this->mailer;
$m->isSMTP();
$m->Host = $cfg['host'];
$m->Port = $cfg['port'];
$m->SMTPAuth = true;
$m->Username = $cfg['username'];
$m->Password = $cfg['password'];
$m->SMTPSecure = $cfg['port'] === 465 ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
$m->CharSet = $cfg['charset'];
$m->Timeout = $cfg['timeout'];
$m->setFrom($cfg['from_email'], $cfg['from_name']);
// 開發環境可關閉驗證
if (app()->isDebug()) {
$m->SMTPOptions = [
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]
];
}
}
/**
* 發送郵件
* @param string|array $to 收件人郵箱或 [email => name]
* @param string $subject 主題
* @param string $htmlBody HTML 正文
* @param string|null $altBody 純文本備用正文
* @param array $attachments 附件 [['path'=>..., 'name'=>...], ...]
* @return bool
*/
public function send($to, string $subject, string $htmlBody, ?string $altBody = null, array $attachments = []): bool {
try {
$m = $this->mailer;
$m->clearAddresses();
$m->clearAttachments();
foreach ((array)$to as $email => $name) {
is_int($email) ? $m->addAddress($name) : $m->addAddress($email, $name);
}
$m->Subject = $subject;
$m->msgHTML($htmlBody);
$m->AltBody = $altBody ?? strip_tags($htmlBody);
foreach ($attachments as $att) {
$m->addAttachment($att['path'], $att['name'] ?? basename($att['path']));
}
return $m->send();
} catch (Exception $e) {
think\facade\Log::error('Mail send failed: ' . $e->getMessage());
return false;
}
}
}
<?php
// app/controller/UserController.php
namespace app\controller;
use app\service\MailService;
use think\facade\View;
class UserController {
public function register() {
// ... 驗證、入庫 ...
$userEmail = 'newuser@example.com';
$html = View::fetch('mail/welcome', ['username' => '張三']);
$ok = MailService::instance()->send(
$userEmail,
'歡迎註冊!',
$html
);
return json(['code' => $ok ? 0 : 1, 'msg' => $ok ? '郵件發送成功' : '郵件發送失敗']);
}
}
<!-- view/mail/welcome.html -->
<div style="font-family:Microsoft YaHei,sans-serif;line-height:1.6;color:#333">
<h2>歡迎加入,{$username}!</h2>
<p>您的賬號已成功註冊,現在可以開始使用我們的服務。</p>
<p><a href="{:url('index/index')}" style="color:#007bff">立即體驗</a></p>
<hr>
<small>此郵件由系統自動發送,請勿直接回覆。</small>
</div>
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 連接超時 | 端口錯誤/防火牆/雲服務商封禁 25/465/587 | 用 465 (SSL) 或 587 (TLS);檢查安全組;改用 API 發信 |
| 認證失敗 | 用了登錄密碼而非授權碼 | 郵箱設置中生成 SMTP 授權碼 |
| 進垃圾箱 | 缺 SPF/DKIM/DMARC、內容觸發規則 | 配置域名解析記錄;優化內容;使用企業郵箱/發信服務商 |
| 中文亂碼 | CharSet 未設為 UTF-8 | $mail->CharSet = 'UTF-8'; |