安全建议:虽然没有记录,但对于参数 $to 和 $subject,mail() 函数至少会将 \r 和 \n 更改为空格。因此,这些参数对于注入额外的标头是安全的。但是您可能需要检查 $to 中的逗号,因为这些逗号会分隔多个地址,您可能不希望发送给多个收件人。
关键部分是 $additional_headers 参数。此参数无法由 mail() 函数清理。因此,您有责任防止将不需要的 \r 或 \n 插入到您放入其中的值中。否则,您只是创建了一个潜在的垃圾邮件分发器。
(PHP 4, PHP 5, PHP 7, PHP 8)
mail — 发送邮件
$to
,$subject
,$message
,$additional_headers
= [],$additional_params
= ""发送电子邮件。
to
邮件的接收者。
此字符串的格式必须符合 » RFC 2822。一些示例:
subject
要发送的电子邮件的主题。
主题必须满足 » RFC 2047。
message
要发送的消息。
每一行都应该用 CRLF (\r\n) 分隔。行长度不应超过 70 个字符。
(仅限 Windows) 当 PHP 直接与 SMTP 服务器通信时,如果在一行的开头发现句点,则会将其删除。为了解决这个问题,请将这些实例替换为双点。
<?php
$text = str_replace("\n.", "\n..", $text);
?>
additional_headers
(可选)这通常用于添加额外的标题(发件人、抄送和密送)。多个额外标题应以 CRLF (\r\n) 分隔。如果使用外部数据来组成此标题,则应对数据进行清理,以防止注入不需要的标题。
如果传递了一个 数组,则其键是标题名称,其值是相应的标题值。
注意:
发送邮件时,邮件 *必须* 包含
From
标头。这可以使用additional_headers
参数设置,或者可以在 php.ini 中设置默认值。如果不这样做,将导致类似于
Warning: mail(): "sendmail_from" not set in php.ini or custom "From:" header missing
的错误消息。From
标头在直接通过 SMTP 发送时也设置Return-Path
(仅限 Windows)。
注意:
如果未收到邮件,请尝试仅使用 LF (\n)。某些 Unix 邮件传输代理(最显著的是 » qmail)会自动将 LF 替换为 CRLF(如果使用 CRLF,则会导致 CR 加倍)。这应该是最后的选择,因为它不符合 » RFC 2822。
additional_params
(可选)additional_params
参数可用于将附加标志作为命令行选项传递给配置为在发送邮件时使用的程序,如 sendmail_path
配置设置所定义。例如,这可用于在使用 sendmail 和 -f
sendmail 选项时设置信封发件人地址。
此参数在内部由 escapeshellcmd() 转义,以防止命令执行。escapeshellcmd() 防止命令执行,但允许添加其他参数。出于安全原因,建议用户清理此参数,以避免向 shell 命令添加不需要的参数。
由于 escapeshellcmd() 会自动应用,因此互联网 RFC 允许作为电子邮件地址使用的一些字符将无法使用。mail()不允许使用此类字符,因此在需要使用此类字符的程序中,建议使用其他发送电子邮件的方法(例如使用框架或库)。
应将 web 服务器运行的用户的用户添加到 sendmail 配置中作为受信任用户,以防止在使用此方法设置信封发件人 (-f) 时向消息添加“X-Warning”标头。对于 sendmail 用户,此文件是 /etc/mail/trusted-users。
示例 #1 发送邮件。
使用 mail() 发送简单的电子邮件
<?php
// 消息
$message = "Line 1\r\nLine 2\r\nLine 3";
// 如果我们任何一行的字符数超过 70 个,我们应该使用 wordwrap()
$message = wordwrap($message, 70, "\r\n");
// 发送
mail('[email protected]', 'My Subject', $message);
?>
示例 #2 发送包含额外标头的邮件。
添加基本标头,告诉 MUA 发件人和回复地址
<?php
$to = '[email protected]';
$subject = 'the subject';
$message = 'hello';
$headers = 'From: [email protected]' . "\r\n" .
'Reply-To: [email protected]' . "\r\n" .
'X-Mailer: PHP/' . phpversion();
mail($to, $subject, $message, $headers);
?>
示例 #3 将额外标头作为 数组 发送邮件
此示例发送与上一示例相同的邮件,但将附加标头作为数组传递(从 PHP 7.2.0 开始可用)。
<?php
$to = '[email protected]';
$subject = '主题';
$message = '你好';
$headers = array(
'From' => '[email protected]',
'Reply-To' => '[email protected]',
'X-Mailer' => 'PHP/' . phpversion()
);
mail($to, $subject, $message, $headers);
?>
示例 #4 使用额外的命令行参数发送邮件。
可以使用 additional_params
参数向配置为在使用 sendmail_path
发送邮件时使用的程序传递额外参数。
<?php
mail('[email protected]', '主题', '邮件内容', null,
'[email protected]');
?>
示例 #5 发送 HTML 邮件
也可以使用 mail() 发送 HTML 邮件。
<?php
// 多个收件人
$to = '[email protected], [email protected]'; // 注意逗号
// 主题
$subject = '八月份生日提醒';
// 消息
$message = '
<html>
<head>
<title>八月份生日提醒</title>
</head>
<body>
<p>以下是八月份即将到来的生日!</p>
<table>
<tr>
<th>姓名</th><th>日</th><th>月</th><th>年</th>
</tr>
<tr>
<td>Johny</td><td>10日</td><td>八月</td><td>1970</td>
</tr>
<tr>
<td>Sally</td><td>17日</td><td>八月</td><td>1973</td>
</tr>
</table>
</body>
</html>
';
// 要发送 HTML 邮件,必须设置 Content-type 头部
$headers[] = 'MIME-Version: 1.0';
$headers[] = 'Content-type: text/html; charset=iso-8859-1';
// 附加头部
$headers[] = 'To: Mary <[email protected]>, Kelly <[email protected]>';
$headers[] = 'From: 生日提醒 <[email protected]>';
$headers[] = 'Cc: [email protected]';
$headers[] = 'Bcc: [email protected]';
// 发送邮件
mail($to, $subject, $message, implode("\r\n", $headers));
?>
注意:
如果打算发送 HTML 或其他复杂的邮件,建议使用 PEAR 包 » PEAR::Mail_Mime。
注意:
mail() 的 SMTP 实现(仅限 Windows)与 sendmail 实现有很多不同之处。首先,它不使用本地二进制文件来编写邮件,而只在直接套接字上运行,这意味着需要一个
MTA
在网络套接字上监听(可以在本地主机或远程机器上)。其次,
From:
、Cc:
、Bcc:
和Date:
等自定义标头首先不会被MTA
解释,而是由 PHP 解析。因此,
to
参数不应该是 "Something <[email protected]>" 形式的地址。与 MTA 通信时,邮件命令可能无法正确解析此信息。
注意:
值得注意的是,mail() 函数不适用于循环中大量发送邮件的情况。此函数为每封邮件打开和关闭 SMTP 套接字,效率不高。
对于发送大量邮件,请参阅 » PEAR::Mail 和 » PEAR::Mail_Queue 包。
注意:
以下 RFC 可能有用:» RFC 1896,» RFC 2045,» RFC 2046,» RFC 2047,» RFC 2048,» RFC 2049 和 » RFC 2822。
安全建议:虽然没有记录,但对于参数 $to 和 $subject,mail() 函数至少会将 \r 和 \n 更改为空格。因此,这些参数对于注入额外的标头是安全的。但是您可能需要检查 $to 中的逗号,因为这些逗号会分隔多个地址,您可能不希望发送给多个收件人。
关键部分是 $additional_headers 参数。此参数无法由 mail() 函数清理。因此,您有责任防止将不需要的 \r 或 \n 插入到您放入其中的值中。否则,您只是创建了一个潜在的垃圾邮件分发器。
通常,查找 mail() 函数触发的确切错误消息很有帮助。虽然该函数不会直接提供错误,但当 mail() 返回 false 时,您可以使用 error_get_last()。
<?php
$success = mail('[email protected]', '我的主题', $message);
if (!$success) {
$errorMessage = error_get_last()['message'];
}
?>
(已在默认使用 SMTP 的 Windows 上成功测试,但 Linux/OSX 上的 sendmail 可能无法提供相同的详细程度。)
感谢 https://stackoverflow.com/a/20203870/195835
使用 XAMPP 服务器发送邮件
在尝试使用 XAMPP 服务器发送电子邮件时,我遇到了许多问题。但是,我最终找到了完成它的正确方法。
将 PHP 的邮件功能配置为与 Gmail 的 SMTP 服务器一起使用,涉及编辑 `php.ini` 和 `sendmail.ini` 配置文件。以下是设置 PHP 通过 Gmail 的 SMTP 服务器使用 XAMPP 发送电子邮件的正式步骤
配置 php.ini
1. 在编辑器中打开 `php.ini`
在您首选的文本编辑器中打开 `php.ini` 配置文件。
2. 定位邮件函数
使用搜索功能 (Ctrl + F) 在 `php.ini` 文件中找到与邮件函数相关的部分。
3. 更新邮件函数设置
将以下配置参数复制并粘贴到邮件函数部分。注释掉或禁用所有其他与邮件相关的设置。
要编辑的 php.ini 代码
SMTP=smtp.gmail.com
smtp_port=587
sendmail_from = [email protected]
sendmail_path = write_sendmail.exe_path
4. 保存更改
应用修改后保存 `php.ini` 文件。
配置 sendmail.ini(在 XAMPP 文件夹中)
1. 在 XAMPP 文件夹中打开 `sendmail.ini`
找到并在 XAMPP 目录中打开 `sendmail.ini` 配置文件。
2. 调整 SMTP 设置
将以下内容插入 `sendmail.ini` 文件中,并将其他配置标记为注释
sendmail.ini 代码
smtp_server=smtp.gmail.com
smtp_port=587
error_logfile=error.log
debug_logfile=debug.log
[email protected]
auth_password=启用邮箱的双因素身份验证后生成的应用密码
[email protected]
3. 保存更改
插入指定配置后,保存 `sendmail.ini` 文件。
这些步骤配置 PHP 使用 Gmail 的 SMTP 服务器发送电子邮件。确保保存修改并重新启动必要的 XAMPP 服务才能使更改生效。
请注意,在配置文件中使用硬编码密码存在安全风险。在生产环境中应避免将密码直接存储在纯文本文件中。考虑使用环境变量或安全的凭证管理系统以获得更好的安全实践。
发送邮件的代码:
<?php
$subject = "邮件测试";
$msg = "嗨!让我们一起玩 PHP。";
$receiver = "[email protected]";
mail($receiver, $subject, $msg);
?>
如果发现邮件中显示的字符错误,则需要在邮件的标头中正确设置 Content-Type 和 Charset。
<?php
$headers = 'Content-Type: text/plain; charset=utf-8' . "\r\n";
?>
通常,UTF-8 是最佳选择。
您可以使用 mail() 函数的第四个参数设置自定义标头。
为了使整个过程万无一失,也请添加以下标头:
<?php
$headers .= 'Content-Transfer-Encoding: base64' . "\r\n";
?>
现在,您可以结合使用 UTF-8 和 Base64 来正确编码主题行和收件人姓名,如下所示:
<?php
$subject = '=?UTF-8?B?' . base64_encode('包含德文字符 öäüß 的测试邮件') . '?=';
$recipient = '=?UTF-8?B?' . base64_encode('Margret Müller') . '?= <[email protected]>';
?>
并且不要忘记对邮件正文也进行 Base64 编码
<?php
$message = base64_encode('此邮件包含德文字符 öäüß。');
?>
所有参考均来自
https://dev.to/lutvit/how-to-make-the-php-mail-function-awesome-3cii
我将一个应用程序迁移到没有本地传输代理 (MTA) 的平台。我不希望配置 MTA,因此我编写了这个 xxmail 函数来用对远程 SMTP 服务器的调用替换 mail()。希望它对某些人有用。
function xxmail($to, $subject, $body, $headers)
{
$smtp = stream_socket_client('tcp://smtp.yourmail.com:25', $eno, $estr, 30);
$B = 8192;
$c = "\r\n";
$s = '[email protected]';
fwrite($smtp, 'helo ' . $_ENV['HOSTNAME'] . $c);
$junk = fgets($smtp, $B);
// 信封
fwrite($smtp, 'mail from: ' . $s . $c);
$junk = fgets($smtp, $B);
fwrite($smtp, 'rcpt to: ' . $to . $c);
$junk = fgets($smtp, $B);
fwrite($smtp, 'data' . $c);
$junk = fgets($smtp, $B);
// 标头
fwrite($smtp, 'To: ' . $to . $c);
if(strlen($subject)) fwrite($smtp, 'Subject: ' . $subject . $c);
if(strlen($headers)) fwrite($smtp, $headers); // 必须以 \r\n 分隔
fwrite($smtp, $headers . $c);
// 正文
if(strlen($body)) fwrite($smtp, $body . $c);
fwrite($smtp, $c . '.' . $c);
$junk = fgets($smtp, $B);
// 关闭
fwrite($smtp, 'quit' . $c);
$junk = fgets($smtp, $B);
fclose($smtp);
}
PHP 在 Linux/Mac(而非 Windows)上使用的 'sendmail' 可执行文件期望 "\n" 作为行分隔符。
此可执行文件是标准的,并由其他 MTA 模拟。
已确认 qmail 和 postfix 需要 "\n",sendmail 和 exim 也可能需要,但我尚未测试。
如果使用 "\r\n" 作为分隔符,它似乎可以工作,但您的电子邮件会略微损坏,并且某些中间件可能会中断。它之所以有效是因为某些系统会清理您的错误。
如果您正在实现 DKIM,请务必小心,因为如果您搞砸了这一点,DKIM 检查将失败(至少在流行的验证工具上)。DKIM 必须使用 "\r\n" 计算,然后在使用 PHP mail 函数时必须将其全部切换为 "\n"。
但是,在 Windows 上,您应该使用 "\r\n",因为在这种情况下,PHP 使用的是 SMTP,因此适用 SMTP 协议的正常规则(而非 Unix 管道的正常规则)。
mail() 内部机制
通过一些测试,我可以说……如果在 php.ini 中或通过 ini_set() 定义了 sendmail_path,通过调用诸如…之类的函数
mail($to, $subject, $message, $headers, $params)
就像 php 在内部打开一个 shell,执行此命令,将此文本发送到 stdin,如果返回值 == 0 则返回 true 一样。
------------
shell> $sendmail_path $params
To: $to
Subject: $subject
$headers
$message
(EOF)
------------
在 Windows 中,我更倾向于强制使用类似 sendmail 的行为,方法是设置 sendmail_path,然后使用适用于 Windows 的 msmtp,而不是使用功能非常有限的 php smtp。
值得注意的是,您可以使用 php.ini 中的 sendmail_path 指令设置一个伪造的 sendmail 程序。
尽管该文件中存在注释,但 sendmail_path 也适用于 Windows。来自 https://php.net/manual/en/mail.configuration.php#ini.sendmail-path:
此指令也适用于 Windows。如果设置了此指令,则会忽略 smtp、smtp_port 和 sendmail_from,并执行指定的命令。
使用电子邮件服务的最低要求发送邮件。
<?php
$encoding = "utf-8";
// 主题字段的首选项
$subject_preferences = array(
"input-charset" => $encoding,
"output-charset" => $encoding,
"line-length" => 76,
"line-break-chars" => "\r\n"
);
// 邮件标头
$header = "Content-type: text/html; charset=".$encoding." \r\n";
$header .= "From: ".$from_name." <".$from_mail."> \r\n";
$header .= "MIME-Version: 1.0 \r\n";
$header .= "Content-Transfer-Encoding: 8bit \r\n";
$header .= "Date: ".date("r (T)")." \r\n";
$header .= iconv_mime_encode("Subject", $mail_subject, $subject_preferences);
// 发送邮件
mail($mail_to, $mail_subject, $mail_message, $header);
?>
* 发送带附件的邮件
function sendMail(
string $fileAttachment,
string $mailMessage = MAIL_CONF["mailMessage"],
string $subject = MAIL_CONF["subject"],
string $toAddress = MAIL_CONF["toAddress"],
string $fromMail = MAIL_CONF["fromMail"]
): bool {
$fileAttachment = trim($fileAttachment);
$from = $fromMail;
$pathInfo = pathinfo($fileAttachment);
$attchmentName = "attachment_".date("YmdHms").(
(isset($pathInfo['extension']))? ".".$pathInfo['extension'] : ""
);
$attachment = chunk_split(base64_encode(file_get_contents($fileAttachment)));
$boundary = "PHP-mixed-".md5(time());
$boundWithPre = "\n--".$boundary;
$headers = "From: $from";
$headers .= "\nReply-To: $from";
$headers .= "\nContent-Type: multipart/mixed; boundary=\"".$boundary."\"";
$message = $boundWithPre;
$message .= "\n Content-Type: text/plain; charset=UTF-8\n";
$message .= "\n $mailMessage";
$message .= $boundWithPre;
$message .= "\nContent-Type: application/octet-stream; name=\"".$attchmentName."\"";
$message .= "\nContent-Transfer-Encoding: base64\n";
$message .= "\nContent-Disposition: attachment\n";
$message .= $attachment;
$message .= $boundWithPre."--";
return mail($toAddress, $subject, $message, $headers);
}
* 发送HTML格式邮件
function sendHtmlMail(
string $mailMessage = MAIL_CONF["mailMessage"],
string $subject = MAIL_CONF["subject"],
array $toAddress = MAIL_CONF["toAddress"],
string $fromMail = MAIL_CONF["fromMail"]
): bool {
$to = implode(",", $toAddress);
$headers[] = 'MIME-Version: 1.0';
$headers[] = 'Content-type: text/html; charset=iso-8859-1';
$headers[] = 'To: '.$to;
$headers[] = 'From: '.$fromMail;
return mail($to, $subject, $mailMessage, implode("\r\n", $headers));
}
这是一个我用来发送 UTF-8 邮件的小巧实用的函数。
<?php
function mail_utf8($to, $from_user, $from_email,
$subject = '(无主题)', $message = '')
{
$from_user = "=?UTF-8?B?".base64_encode($from_user)."?=";
$subject = "=?UTF-8?B?".base64_encode($subject)."?=";
$headers = "From: $from_user <$from_email>\r\n".
"MIME-Version: 1.0" . "\r\n" .
"Content-type: text/html; charset=UTF-8" . "\r\n";
return mail($to, $subject, $message, $headers);
}
?>
请注意,此函数在 Windows 系统和 UNIX 系统上的行为存在很大差异。在 Windows 系统上,它直接发送到 SMTP 服务器,而在 UNIX 系统上,它使用本地命令将邮件传递给系统的 MTA。
总而言之,在 Windows 系统上,您的邮件和邮件头必须使用电子邮件规范规定的标准换行符 \r\n。在 UNIX 系统上,MTA 的 "sendmail" 接口假定接收到的数据将使用 UNIX 换行符,并将任何 \n 转换为 \r\n,因此您必须仅向 UNIX 系统上的 mail() 函数提供 \n,以避免 MTA 将其过度修正为 \r\r\n。
如果您在 Windows 系统上使用普通的 \n,一些 MTA 会出现问题。特别是 qmail 会直接拒绝接受任何只有 \n 而没有伴随 \r 的邮件。
到目前为止,我使用以下方法来确保邮件主题中特殊字符的正确显示
<?php $subject = '=?utf-8?B?' . base64_encode($subject) . '?='; ?>
但是对于非常长的主题,邮件头行会超过 76 个字符,一些邮件服务器对此非常反感……所以这是我的新解决方案
<?php $subject = substr(mb_encode_mimeheader("Subject: " . $subject, 'utf-8', 'B', "\r\n", 0), 9); ?>
请注意:我在 $subject 前添加了 "Subject: " 并随后将其删除。这是为了确保保留必要的空间,因为 PHP 会自行添加 "Subject: "……
我使用此函数向 Gmail、雅虎、AOL 等发送邮件时遇到投递问题。我根据这里的说明发现,需要将您的 Return-Path 设置为有效的电子邮件地址才能捕获退信。除此之外,还有两个额外的投递问题:
1) php.ini sendmail 参数或 mail() 附加参数字段中使用的 -f 选项中的电子邮件地址中的域名需要具有有效的 SPF 记录(在 DNS 中肯定是“TXT”记录类型,如果可能,请添加额外的“SPF”类型记录)。为什么?该邮件头字段用于垃圾邮件检查。
2) 您还应该使用域名密钥或 DKIM。这里的技巧是域名密钥/DKIM 区分大小写!我使用 Cpanel 创建了我的域名密钥,它在密钥创建中自动使用所有小写域名。我发现,当发送电子邮件并使用驼峰式大小写“-f [email protected]”选项时,我的密钥不被接受。但是,当我使用“-f [email protected]”时,它被接受了。
还有许多其他因素可能导致邮件无法送达收件箱,包括您自己多次失败的测试尝试,因此我建议您查阅每个站点的指南,不要问我寻求帮助。这些只是帮助解决我问题的几个技术问题。
我希望这能为某些人节省一些时间和麻烦……
小心不要在 $headers 变量中添加额外的空格。
例如,这在我们的服务器上不起作用
$headers = "From: $from \r\n Bcc: $bcc \r\n";
但是这个可以
$headers = "From: $from\r\nBcc: $bcc\r\n";
请注意,删除了第一个 \r\n 周围的空格。
关于收件人
小心不要在 additional_headers 中重复收件人,
否则 Gmail 会标记它,从而导致
主机 gmail-smtp-in.l.google.com [142.251.xx.xx]
数据结束后的远程邮件服务器 SMTP 错误
550-5.7.1 [xxx.xxx.xx.xx] 此邮件不符合 RFC 5322 规范,问题是
550-5.7.1 收件人标题重复。为了减少发送到 Gmail 的垃圾邮件数量,
550-5.7.1 此邮件已被阻止。请查看
550 5.7.1 RFC 5322 规范以了解更多信息。