本文目录导读:

在 PHP 中实现“群发单显”(即每位收件人只能看到自己的邮箱地址,看不到其他人的),主要取决于你使用的邮件发送方式。
以下是几种常见方案,按推荐程度排序:
使用 SMTP 协议(推荐,最标准)
这是最标准的做法。关键在于设置 Rcpt-To(收件人列表)和 To(显示在邮件头部的地址)。
在 SMTP 协议中,Rcpt-To 决定了邮件实际发给谁,而 To 头字段决定了收件人客户端显示给谁看。
使用 PHPMailer(最流行)
这是最推荐的库,因为它自动处理了 SMTP 的细节。
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php'; // 如果有 Composer
$mail = new PHPMailer(true);
try {
// 服务器设置
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'sender@example.com';
$mail->Password = 'secret';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
// 发件人
$mail->setFrom('sender@example.com', '发件人名称');
// 收件人列表(实际发送给这些地址)
$recipients = [
'user1@example.com' => '用户一',
'user2@example.com' => '用户二',
'user3@example.com' => '用户三',
];
foreach ($recipients as $email => $name) {
// 1. 清空之前的收件人(避免重复添加)
$mail->clearAddresses();
$mail->clearAttachments(); // 如有附件也需清空
// 2. 关键:添加实际收件人(这会设置 Rcpt-To)
$mail->addAddress($email, $name);
// 3. 关键:设置邮件头部的 "To" 字段(往往设为发件人自己,或者留空)
// 这里设置为发件人自己的地址,收件人看到“发给”的是发件人
$mail->addCustomHeader('To: 保密收件人 <sender@example.com>');
// 或者更简单:不设置,让 PHPMailer 默认取第一个 addAddress,但这样会暴露。
// 更优雅的写法是覆盖默认值:
// $mail->addAddress($email); // 先添加
// $mail->__set('to', []); // 清空内部数组(不推荐,太底层)
// 官方推荐:直接修改属性
$mail->clearAddresses();
// 在循环内 重新初始化 To 头部
$mail->addAddress($email, $name);
$mail->addCustomHeader("To: 收件人 <{$email}>"); // 这行注释掉即可
// 更简单的做法(PHPMailer 官方推荐):
// 直接设置 $mail->to = []; addAddress
// 但为了确保绝对正确,我们使用以下方法:
// 方法:强制覆盖 To 头
$mail->addCustomHeader('To: 所有收件人 <sender@example.com>'); // 显示成发件人自己
// 4. 邮件内容
$mail->Subject = "您的密码已重置";
$mail->Body = "亲爱的 {$name},这是您的专属邮件。";
// 5. 发送
$mail->send();
echo "已发送给: {$email}\n";
}
echo "群发完成!";
} catch (Exception $e) {
echo "邮件发送失败: {$mail->ErrorInfo}";
}
注意:上面的代码有个问题,addCustomHeader 如果重复添加 To 会报错。更标准的 PHPMailer 写法是清空 to 数组后重新设置。
PHPMailer 最正确的单显写法(清理版):
foreach ($recipients as $email => $name) {
// 重置收件人(关键步骤)
$mail->clearAddresses();
// 添加实际发送的 SMTP 收件人
$mail->addAddress($email, $name);
// 单独设置可见的 To 头部 - 指向自己(或某个公用地址)
// 注释掉默认的 To,添加自定义
$mail->set('to', [[
'email' => 'sender@example.com', // 这里换成你自己的发件地址
'name' => '系统通知'
]]); // 或者直接用 $mail->clearAllRecipients();
// PHPMailer 更简单的做法:
// $mail->clearAddresses();
// $mail->addAddress($email); // 这会把 Rcpt-To 和 To 都设为这个邮箱
// 要单显,需要手动覆盖邮件头部
$mail->addCustomHeader('To: 系统通知 <sender@example.com>');
// 注意:addCustomHeader 在一个循环里如果调用了两次,之前设置会被覆盖吗?会报错。
// 所以更好的做法是:
$mail->clearCustomHeaders(); // 如果需要清理
// 最终推荐最简洁方式(老外常用):
$mail->clearAddresses();
$mail->addAddress($email); // 这会同时设置 To 和 Rcpt
$mail->addCustomHeader('To: 系统通知 <sender@example.com>'); // 覆盖显示
$mail->send();
}
注意事项:addCustomHeader('To: ...') 在循环中必须在每次循环前调用 clearCustomHeaders() 或手动避免重复添加,否则报错。
使用 mail() 函数(不推荐,但简单)
如果你必须使用内置的 mail() 函数,需要通过 追加参数 或 \nBcc: 头部实现。
重点:使用 Bcc(密送)或 Rcpt-To。
内置 mail() 函数的签名是 mail($to, $subject, $message, $headers)。
$to参数:必须设置为单个收件人,或者你自己。$headers:通过添加Bcc:头部来添加其他收件人,但 PHP 的mail()函数依赖底层 sendmail,sendmail 配置不当,Bcc 可能会暴露。
最稳妥的做法(循环发送):
$recipients = ['user1@example.com', 'user2@example.com'];
foreach ($recipients as $email) {
$to = $email; // 只放一个收件人
$subject = '测试单显';
$message = "你好,这是一封给 {$email} 的测试邮件。";
$headers = "From: sender@example.com\r\n";
$headers .= "Reply-To: sender@example.com\r\n";
// 注意:这里不要加 To 头部,让 sendmail 自动处理
// 关键:这里必须确保只发给 $email。
// 如果直接设定 $headers 中的 To 为发件人,而 $to 为收件人,
// 可能会被某些 MTA 忽略。
mail($to, $subject, $message, $headers);
}
重要提示:
- 无论你用什么库,不要将多个收件人用逗号分隔放在
$to变量里(除非你特意要让他们看到彼此)。 - 使用
Bcc是有风险的,因为有些垃圾邮件过滤器会将其暴露,或者如果服务器配置sendmail不当,Bcc信息可能被解析到日志中。
使用第三方 API(如 SendGrid, SES 等)
这是最省心且最稳定的方式,几乎所有邮件服务商都支持“单发”或“模板邮件”。
以 SendGrid 为例(PHP):
$email = new \SendGrid\Mail\Mail();
$email->setFrom("sender@example.com", "系统通知");
$email->setSubject("群发单显测试");
$recipients = ['user1@example.com', 'user2@example.com'];
foreach ($recipients as $recipient) {
// 创建新邮件对象(或清空 dynamic_template_data)
$personalization = new \SendGrid\Mail\Personalization();
$personalization->addTo(new \SendGrid\Mail\To($recipient, "User")); // 只添加一个 To
$personalization->addSubstitution("-name-", "用户".$recipient);
$email->addPersonalization($personalization);
}
// 发送
$sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY'));
$response = $sendgrid->send($email);
在 SendGrid 中,Personalization 对象天然支持一人一个 To,从而实现了单显。
总结与最佳实践
| 方案 | 实现方式 | 推荐指数 |
|---|---|---|
| PHPMailer + SMTP | clearAddresses() 循环,覆盖 To 头部 |
⭐⭐⭐⭐⭐ |
| mail() 函数 | 循环发送,单收件人 | ⭐⭐ |
| 第三方 API | 使用 Personalization | ⭐⭐⭐⭐⭐ |
强烈推荐使用 PHPMailer + SMTP。
最核心的代码片段(PHPMailer 单显):
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
...
foreach ($recipients as $email) {
$mail->clearAddresses(); // 清空上一轮收件人
$mail->addAddress($email); // 添加当前收件人(Rcpt)
// 覆盖可见的 To 头:
$mail->clearCustomHeaders(); // 如有必要
$mail->addCustomHeader('To: 系统通知 <sender@example.com>');
$mail->send();
}
这样,收件人打开邮件,看到的“发给谁”是发件人自己,而实际 SMTP 投递时只发给了唯一的 $email 地址。