本文汇总 PHP 内置 mail() 函数的用法、常见示例(纯文本、HTML、附件)、常见问题排查与安全建议,并给出使用更可靠库(如 PHPMailer)的示例。
1. 基本概念
- 函数签名:
bool mail(string $to, string $subject, string $message, array|string $additional_headers = [], string $additional_parameters = "") - 返回值:成功调用邮件传输代理(MTA)则返回
true,否则false。注意:true并不保证邮件被送达,只表示 PHP 成功将邮件交给本地 MTA(如 sendmail、exim、postfix)或通过配置的 SMTP 发送。 - 行结束符:HTTP/SMTP 协议要求行结束使用
\r\n。在构造头部时请使用\r\n;Windows 与 Linux 上均建议使用\r\n。
2. 简单示例(纯文本)
$to = 'recipient@example.com';
$subject = '测试邮件';
$message = "这是一封测试邮件。\n多行内容示例。";
$headers = "From: sender@example.com\r\n" .
"Reply-To: sender@example.com\r\n" .
"X-Mailer: PHP/" . phpversion();
if (mail($to, $subject, $message, $headers)) {
echo "邮件发送请求已提交。";
} else {
echo "邮件发送请求失败。";
}
注意:若在 additional_parameters 中使用 -f 指定发信人(envelope sender),例如 mail(..., $headers, '-fwebmaster@example.com'),可让 MTA 设置 Return-Path(某些主机需要此项以避免被拒)。
3. 发送 HTML 邮件
$to = 'recipient@example.com';
$subject = 'HTML 邮件示例';
$html = '
<html><body><h1>标题</h1>
<p>这是 <b>HTML</b> 格式的邮件。</p></body></html>';
$headers = "MIME-Version: 1.0\r\n" .
"Content-type: text/html; charset=UTF-8\r\n" .
"From: Sender <sender@example.com>\r\n";
mail($to, $subject, $html, $headers);
4. 附件(通过 MIME multipart)
使用 mail() 发送附件需要自己构建 MIME 边界并进行 base64 编码。示例:
$to = 'recipient@example.com';
$subject = '含附件的邮件';
$message = "这封邮件包含一个附件,请查看。";
$filePath = '/path/to/file.pdf';
$fileName = basename($filePath);
$fileData = chunk_split(base64_encode(file_get_contents($filePath)));
$boundary = md5(time());
$headers = "From: sender@example.com\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/mixed; boundary=\"{$boundary}\"\r\n";
$body = "--{$boundary}\r\n";
$body .= "Content-Type: text/plain; charset=UTf-8\r\n";
$body .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
$body .= $message . "\r\n\r\n";
$body .= "--{$boundary}\r\n";
$body .= "Content-Type: application/octet-stream; name=\"{$fileName}\"\r\n";
$body .= "Content-Transfer-Encoding: base64\r\n";
$body .= "Content-Disposition: attachment; filename=\"{$fileName}\"\r\n\r\n";
$body .= $fileData . "\r\n";
$body .= "--{$boundary}--";
mail($to, $subject, $body, $headers);
提醒:构建 multipart 内容时细节较多,编码、边界、头部必须严格正确,否则邮件客户端无法识别附件或显示错误。
5. 常见问题与排查
- mail() 返回 true 但邮件未送达:检查本机 MTA 日志(例如
/var/log/mail.log、/var/log/maillog)。很多共享主机限制外发或要求验证发信地址。 - 邮件被当作垃圾邮件:配置 SPF、DKIM、DMARC,确保发信域名与邮件服务器匹配;邮件正文和头部不要包含垃圾邮件特征。
- Header 注入攻击:不要把未验证的用户输入直接放入
From、Subject或headers。应剥离换行符(\r、\n)并严格验证邮箱格式。 - 换行问题:务必使用
\r\n,并避免头部末尾多余空行。 - Windows 与 sendmail 不同:Windows 下 PHP 使用 SMTP 配置(
php.ini的SMTP/smtp_port/sendmail_from),Linux 下通常通过 sendmail 程序交由本地 MTA 处理。
6. 安全建议
- 验证并清理所有来自用户的邮件相关输入。
- 使用
filter_var($email, FILTER_VALIDATE_EMAIL)检查邮箱格式。 - 尽可能使用经过验证的 SMTP(带 TLS)和身份验证的方式发送邮件,避免依赖系统 MTA。
7. 更稳健的替代方案:PHPMailer(推荐)
PHPMailer、SwiftMailer(现已由 Symfony Mailer 替代)能更方便地发送 HTML、附件与 SMTP 验证,且处理了许多边界细节。
安装(Composer):
composer require phpmailer/phpmailer
示例(使用 SMTP):
use PHPMailer\\PHPMailer\\PHPMailer;
use PHPMailer\\PHPMailer\\Exception;
require 'vendor/autoload.php';
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'smtp_user';
$mail->Password = 'smtp_pass';
$mail->SMTPSecure = 'tls';
$mail->Port = 587;
$mail->setFrom('sender@example.com', 'Sender Name');
$mail->addAddress('recipient@example.com', 'Recipient');
$mail->Subject = 'PHPMailer 测试';
$mail->isHTML(true);
$mail->Body = '
<h1>测试邮件</h1>
<p>通过 SMTP 发送</p>';
$mail->addAttachment('/path/to/file.pdf');
$mail->send();
echo '邮件已发送';
} catch (Exception $e) {
echo "邮件发送失败: {$mail->ErrorInfo}";
}
PHPMailer 更适合生产环境,支持 SMTP 验证、TLS、OAuth 等高级功能,并且更容易调试。
7.1 TLS 与证书校验(强制验证服务器证书)
在生产环境中应始终使用加密的 SMTP 连接(STARTTLS 或 SSL/TLS),并尽可能验证服务器证书以防止中间人攻击。PHPMailer 提供 SMTPOptions 来控制底层流的验证行为。示例如下:
use PHPMailer\PHPMailer\PHPMailer;
require 'vendor/autoload.php';
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'smtp_user';
$mail->Password = 'smtp_pass';
// 使用 STARTTLS
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // 或 'tls'
$mail->Port = 587;
// 强制证书校验
$mail->SMTPOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
// 指定 CA 文件(可选),例如在某些系统上需要指定完整路径
'cafile' => '/etc/ssl/certs/ca-certificates.crt',
],
];
$mail->setFrom('sender@example.com', 'Sender');
$mail->addAddress('recipient@example.com');
$mail->Subject = '带 TLS 与证书校验的邮件';
$mail->Body = '测试 TLS 与证书校验';
$mail->send();
要点说明:
verify_peer与verify_peer_name都应设为true以确保服务器证书由受信任的 CA 签发且主机名匹配。allow_self_signed设为false可避免接受自签名证书(除非你明确控制并信任该证书)。- 在某些托管或开发环境中,系统默认的 CA 列表不全,可以通过
cafile指定 CA 证书文件(PEM 格式)。
如果需要允许“机会性 TLS”(由服务器协商 STARTTLS),可保持 SMTPAutoTLS 为 true(PHPMailer 默认),但仍建议在生产环境明确要求 SMTPSecure 并开启证书检验。
7.2 使用 OAuth2(以 Gmail 为例)
许多邮件服务(如 Gmail)推荐或要求使用 OAuth2 而非传统用户名/密码登录。PHPMailer 支持 XOAUTH2:你需要安装 league/oauth2-client 及对应提供者(例如 league/oauth2-google),并在 Google Cloud Console 中为你的应用创建 OAuth 凭据与刷新令牌(refresh token)。
示例(使用刷新令牌):
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\OAuth;
use League\OAuth2\Client\Provider\Google;
require 'vendor/autoload.php';
$provider = new Google([
'clientId' => 'GOOGLE_CLIENT_ID',
'clientSecret' => 'GOOGLE_CLIENT_SECRET',
]);
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->Port = 587;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->SMTPAuth = true;
// 使用 XOAUTH2
$mail->AuthType = 'XOAUTH2';
$mail->setOAuth(new OAuth([
'provider' => $provider,
'clientId' => 'GOOGLE_CLIENT_ID',
'clientSecret' => 'GOOGLE_CLIENT_SECRET',
'refreshToken' => 'REFRESH_TOKEN_FROM_OAUTH_FLOW',
'userName' => 'your-email@gmail.com',
]));
$mail->setFrom('your-email@gmail.com', 'Your Name');
$mail->addAddress('recipient@example.com');
$mail->Subject = '通过 OAuth2 发送的邮件';
$mail->Body = '这是通过 OAuth2 授权发送的邮件示例。';
$mail->send();
注意事项与准备工作:
- 需要在 Google API 控制台启用 Gmail API 并创建 OAuth 客户端,获取
clientId与clientSecret,并用授权流程拿到refreshToken。 - 安装依赖:
composer require phpmailer/phpmailer league/oauth2-client league/oauth2-google
- OAuth2 的 token 刷新与管理可交由
league/oauth2-client处理,PHPMailer 的OAuth封装会在发送时使用refreshToken自动获取或刷新访问令牌。 - 对于其他邮件服务(非 Gmail),请参考对应服务的 OAuth2 提供者或实现自定义 Provider。
7.3 证书指纹(可选的“固定”验证)
在对安全要求极高的场景,可以在建立连接后对服务器证书进行指纹校验(certificate pinning)。PHPMailer 本身不直接提供指纹校验接口,但可以通过 SMTP 子类化或在应用层用 stream_socket_enable_crypto() 之类的底层函数检索对端证书并验证指纹。这类方法更复杂且易出错,通常只在你能控制服务器证书且需要更强保障时使用。
示例思路(伪代码):
// 建立 socket/SMTP 连接后,通过 stream_get_meta_data / stream_context 获取证书信息
$cert = /* 获取对端证书信息 */;
$fingerprint = hash('sha256', $cert);
if ($fingerprint !== '期望的指纹值') {
// 中止连接
}
如非必要,优先使用 CA 验证 + cafile/capath 的方式来保证连接安全。
8. 调试小技巧
- 使用本地 SMTP 捕获工具(MailHog、Mailtrap、MailCatcher)进行开发测试,避免真实外发。
- 在 Linux 上通过
telnet smtp.example.com 25手工测试 SMTP 交互。 - 查看 PHP 错误日志与 MTA 日志获取更多信息。
9. 总结
mail()适合发送简单通知或测试,但在生产环境建议使用PHPMailer或其他库通过认证 SMTP 发送,以提高可交付性和安全性。- 发送附件与 HTML 时要特别小心 MIME 构造与编码,避免直接拼接用户输入到头部以防注入攻击。


