Mail 扩展
PHP 的 mail() 函数是最基本的邮件发送方式,适合从 PHP 脚本发送简单邮件。虽然功能有限,但在特定场景下(如系统通知、表单邮件)仍然常用。本节将讲解 mail() 函数的用法以及更推荐使用的 PHPMailer 替代方案。
前置知识
阅读本节前,建议先了解:IMAP / POP3 / SMTP、字符串处理
基础概念
mail() 函数
PHP 的 mail() 函数使用系统默认的邮件发送程序(sendmail、postfix 等)来发送邮件。
安装
bash
# mail() 函数通常是 PHP 默认启用的
# 需要系统安装 sendmail 或 postfix
# Ubuntu/Debian
sudo apt-get install sendmail
# 或
sudo apt-get install postfix
# php.ini 配置
; sendmail_path = /usr/sbin/sendmail -t -imail() 基本用法
发送纯文本邮件
php
<?php
declare(strict_types=1);
/**
* 使用 mail() 发送纯文本邮件
*/
function sendPlainTextEmail(
string $to,
string $subject,
string $message,
string $from = 'noreply@example.com'
): bool {
$headers = "From: {$from}\r\n";
$headers .= "Reply-To: {$from}\r\n";
$headers .= "X-Mailer: PHP/" . PHP_VERSION . "\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$result = mail($to, $subject, $message, $headers);
if (!$result) {
throw new RuntimeException("邮件发送失败");
}
return true;
}
// 使用示例
sendPlainTextEmail(
'recipient@example.com',
'测试邮件',
"你好,\n\n这是一封测试邮件。\n\n谢谢。"
);发送 HTML 邮件
php
<?php
declare(strict_types=1);
/**
* 发送 HTML 邮件
*/
function sendHtmlEmail(
string $to,
string $subject,
string $htmlContent,
string $textContent = '',
string $from = 'noreply@example.com'
): bool {
$boundary = '----=_Part_' . md5(uniqid((string) time()));
$headers = "From: {$from}\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n";
$body = "--{$boundary}\r\n";
$body .= "Content-Type: text/plain; charset=UTF-8\r\n";
$body .= "Content-Transfer-Encoding: base64\r\n\r\n";
$body .= chunk_split(base64_encode($textContent ?: strip_tags($textContent))) . "\r\n";
$body .= "--{$boundary}\r\n";
$body .= "Content-Type: text/html; charset=UTF-8\r\n";
$body .= "Content-Transfer-Encoding: base64\r\n\r\n";
$body .= chunk_split(base64_encode($htmlContent)) . "\r\n";
$body .= "--{$boundary}--\r\n";
return mail($to, $subject, $body, $headers);
}
// 使用示例
$html = <<<HTML
<html>
<body>
<h1>你好,用户</h1>
<p>这是一封 <strong>HTML</strong> 邮件。</p>
<table style="border: 1px solid #ccc; width: 100%;">
<tr><td>产品</td><td>价格</td></tr>
<tr><td>Widget</td><td>99.00</td></tr>
</table>
</body>
</html>
HTML;
$text = "你好,用户\n这是一封 HTML 邮件的纯文本版本。";
sendHtmlEmail(
'recipient@example.com',
'HTML 测试邮件',
$html,
$text
);发送带附件的邮件
php
<?php
declare(strict_types=1);
/**
* 发送带附件的邮件
*/
function sendEmailWithAttachment(
string $to,
string $subject,
string $body,
array $attachments = [],
string $from = 'noreply@example.com'
): bool {
$boundary = '----=_Mixed_' . md5(uniqid((string) time()));
$headers = "From: {$from}\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/mixed; boundary=\"{$boundary}\"\r\n";
$message = "--{$boundary}\r\n";
$message .= "Content-Type: text/plain; charset=UTF-8\r\n";
$message .= "Content-Transfer-Encoding: base64\r\n\r\n";
$message .= chunk_split(base64_encode($body)) . "\r\n";
foreach ($attachments as $attachment) {
$filePath = $attachment['path'];
$fileName = $attachment['name'] ?? basename($filePath);
$fileType = mime_content_type($filePath);
if (!file_exists($filePath)) {
continue;
}
$fileContent = file_get_contents($filePath);
$message .= "--{$boundary}\r\n";
$message .= "Content-Type: {$fileType}; name=\"{$fileName}\"\r\n";
$message .= "Content-Transfer-Encoding: base64\r\n";
$message .= "Content-Disposition: attachment; filename=\"{$fileName}\"\r\n\r\n";
$message .= chunk_split(base64_encode($fileContent)) . "\r\n";
}
$message .= "--{$boundary}--\r\n";
return mail($to, $subject, $message, $headers);
}
// 使用示例
sendEmailWithAttachment(
'recipient@example.com',
'带附件的邮件',
'请查收附件中的文件。',
[
['path' => '/path/to/report.pdf'],
['path' => '/path/to/data.csv', 'name' => 'report-data.csv'],
]
);附加参数
额外命令行参数
php
<?php
declare(strict_types=1);
// mail() 的第五个参数:额外的 sendmail 命令行参数
// -f sender@example.com 指定发件人地址(envelope sender)
$result = mail(
$to,
$subject,
$message,
$headers,
'-f sender@example.com'
);
// 设置 Return-Path 头
$headers .= "Return-Path: bounces@example.com\r\n";发送给多个收件人
php
<?php
declare(strict_types=1);
// 多个收件人(逗号分隔)
$to = 'user1@example.com, user2@example.com';
// 使用 Cc 和 Bcc
$headers = "From: sender@example.com\r\n";
$headers .= "Cc: cc1@example.com, cc2@example.com\r\n";
$headers .= "Bcc: bcc@example.com\r\n";
mail($to, $subject, $message, $headers);使用 PHPMailer(推荐)
为什么推荐 PHPMailer
mail() 函数的局限性:
- 依赖系统 sendmail 程序
- SMTP 认证支持有限
- HTML 邮件构建复杂
- 附件处理繁琐
- 错误处理不够详细
- 缺少邮件模板支持
安装 PHPMailer
bash
composer require phpmailer/phpmailerPHPMailer 示例
php
<?php
declare(strict_types=1);
use PHPMailer\PHPMailer\PHPMailer;
/**
* 基于 PHPMailer 的邮件发送器
*/
class MailSender
{
private PHPMailer $mailer;
public function __construct(
string $host,
int $port = 587,
string $username = '',
string $password = '',
string $fromName = '',
bool $smtpAuth = true
) {
$this->mailer = new PHPMailer(true);
// SMTP 配置
$this->mailer->isSMTP();
$this->mailer->Host = $host;
$this->mailer->Port = $port;
$this->mailer->SMTPAuth = $smtpAuth;
$this->mailer->Username = $username;
$this->mailer->Password = $password;
// 加密方式
$this->mailer->SMTPSecure = match ($port) {
465 => PHPMailer::ENCRYPTION_SMTPS,
587 => PHPMailer::ENCRYPTION_STARTTLS,
default => '',
};
// 字符编码
$this->mailer->CharSet = 'UTF-8';
// 发件人
$this->mailer->setFrom($username, $fromName);
}
/**
* 发送 HTML 邮件
*/
public function sendHtml(
string $to,
string $subject,
string $html,
string $text = ''
): bool {
$this->mailer->clearAddresses();
$this->mailer->addAddress($to);
$this->mailer->Subject = $subject;
$this->mailer->Body = $html;
$this->mailer->AltBody = $text;
$this->mailer->isHTML(true);
try {
return $this->mailer->send();
} catch (\Exception $e) {
throw new RuntimeException("邮件发送失败: " . $e->getMessage());
}
}
/**
* 发送带附件的邮件
*/
public function sendWithAttachments(
string $to,
string $subject,
string $body,
array $files = []
): bool {
$this->mailer->clearAddresses();
$this->mailer->clearAttachments();
$this->mailer->addAddress($to);
$this->mailer->Subject = $subject;
$this->mailer->Body = $body;
foreach ($files as $file) {
if ($file instanceof \CURLFile) {
$this->mailer->addAttachment($file->getFilename());
} elseif (is_string($file)) {
$this->mailer->addAttachment($file);
} elseif (is_array($file)) {
$this->mailer->addAttachment(
$file['path'],
$file['name'] ?? '',
$file['encoding'] ?? PHPMailer::ENCODING_BASE64
);
}
}
try {
return $this->mailer->send();
} catch (\Exception $e) {
throw new RuntimeException("邮件发送失败: " . $e->getMessage());
}
}
/**
* 发送模板邮件
*/
public function sendTemplate(
string $to,
string $templatePath,
array $variables = []
): bool {
$html = file_get_contents($templatePath);
foreach ($variables as $key => $value) {
$html = str_replace('{{' . $key . '}}', (string) $value, $html);
}
$subject = $variables['subject'] ?? '通知';
return $this->sendHtml($to, $subject, $html);
}
/**
* 获取错误信息
*/
public function getError(): string
{
return $this->mailer->ErrorInfo;
}
}
// 使用示例
$mailer = new MailSender(
'smtp.example.com',
587,
'user@example.com',
'password',
'应用通知'
);
$mailer->sendHtml(
'recipient@example.com',
'欢迎注册',
'<h1>欢迎</h1><p>感谢你的注册!</p>',
'欢迎感谢你的注册!'
);注意事项
mail() 常见问题
php
<?php
declare(strict_types=1);
// 1. 邮件可能进入垃圾箱
// - 正确配置 SPF、DKIM、rDNS
// - 使用 PHPMailer 代替 mail()
// 2. 换行符必须是 \r\n
$headers = "From: sender@example.com\r\n"; // 正确
// $headers = "From: sender@example.com\n"; // 可能导致问题
// 3. 主题行编码
$subject = '=?UTF-8?B?' . base64_encode('中文主题') . '?=';
// 4. 收件人格式
$to = 'User Name <user@example.com>'; // 正确
$to = '"User Name" <user@example.com>'; // 含特殊字符时调试邮件发送
php
<?php
declare(strict_types=1);
// 启用 sendmail 详细日志(php.ini)
// sendmail_path = /usr/sbin/sendmail -t -i -v
// 使用 PHPMailer 调试
$mail = new PHPMailer(true);
$mail->SMTPDebug = PHPMailer::DEBUG_SERVER;
$mail->Debugoutput = function ($str, $level) {
echo "DEBUG [{$level}]: {$str}" . PHP_EOL;
};最佳实践
- 使用 PHPMailer:生产环境推荐使用 PHPMailer 代替 mail()
- 正确设置换行符:邮件头使用
\r\n - HTML + 纯文本双版本:确保纯文本客户端也能阅读
- SPF/DKIM 配置:正确配置域名的邮件认证记录
- 错误处理:检查发送结果,记录失败日志
- 异步发送:大量邮件使用队列系统异步发送
下一节
继续学习:Zip 扩展