安全建议:虽然没有记录,但对于参数 $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
(可选)这通常用于添加额外的标头(From、Cc 和 Bcc)。多个额外的标头应使用 CRLF (\r\n) 分隔。如果使用外部数据来组合此标头,则应对数据进行清理,以确保不会注入任何不需要的标头。
如果传递了 数组,则其键是标头名称,其值是相应的标头值。
注意:
发送邮件时,邮件必须包含
From
标头。可以使用$additional_headers
参数设置此项,或者可以在 php.ini 中设置默认值。如果未这样做,会导致类似于
Warning: mail(): "sendmail_from" not set in php.ini or custom "From:" header missing
的错误消息。当通过 SMTP 直接发送(仅限 Windows)时,From
标头还会设置Return-Path
。
注意:
如果未收到消息,请尝试仅使用 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 = 'the subject';
$message = 'hello';
$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]', 'the subject', 'the message', null,
'[email protected]');
?>
示例 #5 发送 HTML 邮件
也可以使用 mail() 发送 HTML 邮件。
<?php
// 多个收件人
$to = '[email protected], [email protected]'; // 注意逗号
// 主题
$subject = 'Birthday Reminders for August';
// 消息
$message = '
<html>
<head>
<title>Birthday Reminders for August</title>
</head>
<body>
<p>Here are the birthdays upcoming in August!</p>
<table>
<tr>
<th>Person</th><th>Day</th><th>Month</th><th>Year</th>
</tr>
<tr>
<td>Johny</td><td>10th</td><td>August</td><td>1970</td>
</tr>
<tr>
<td>Sally</td><td>17th</td><td>August</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: Birthday Reminder <[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 插入到您放入其中的值中。否则,您就创建了一个潜在的垃圾邮件发送者。
使用 XAMPP 服务器发送邮件
我在尝试使用 XAMPP 服务器发送电子邮件时遇到了很多问题。但是,我最终找到了完成它的正确方法。
将 PHP 的邮件功能配置为与 Gmail 的 SMTP 服务器一起使用,需要编辑 `php.ini` 和 `sendmail.ini` 配置文件。以下是使用 XAMPP 通过 Gmail 的 SMTP 服务器设置 PHP 以发送电子邮件的正式步骤
配置 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=为您的邮件 ID 启用双因素身份验证后的应用程序密码
[email protected]
3. 保存更改
插入指定的配置后保存 `sendmail.ini` 文件。
这些步骤将 PHP 配置为使用 Gmail 的 SMTP 服务器发送电子邮件。确保保存修改,并重新启动必要的 XAMPP 服务,以使更改生效。
请注意,在配置文件中使用硬编码密码会带来安全风险。应避免在生产环境中直接在纯文本文件中存储密码。为了更好地保障安全,请考虑使用环境变量或安全的凭据管理系统。
发送邮件的代码 -
<?php
$subject = "Mail for checking";
$msg = "Hey! Let us play with PHP.";
$receiver = "[email protected]";
mail($receiver, $subject, $msg);
?>
通常,查找由 mail() 函数触发的确切错误消息很有用。虽然该函数不直接提供错误,但您可以在 mail() 返回 false 时使用 error_get_last()。
<?php
$success = mail('[email protected]', 'My Subject', $message);
if (!$success) {
$errorMessage = error_get_last()['message'];
}
?>
(已在默认使用 SMTP 的 Windows 上成功测试,但在 Linux/OSX 上的 sendmail 可能无法提供相同的详细程度。)
感谢 https://stackoverflow.com/a/20203870/195835
如果发现电子邮件中显示的字符错误,这是因为您需要在电子邮件的标头中正确设置 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('Test email with German Umlauts öäüß') . '?=';
$recipient = '=?UTF-8?B?' . base64_encode('Margret Müller') . '?= <[email protected]>';
?>
并且不要忘记对电子邮件内容也进行 Base64 编码。
<?php
$message = base64_encode('This email contains German Umlauts öäüß.');
?>
所有引用均来自
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 模拟。
"\n" 确认适用于 qmail 和 postfix,可能也适用于 sendmail 和 exim,但我没有测试。
如果您使用 "\r\n" 作为分隔符进行传递,它可能看起来有效,但您的电子邮件将被细微地损坏,并且某些中间件可能会中断。它之所以有效,是因为某些系统会清理您的错误。
如果您正在实施 DKIM,请格外小心,因为如果您搞砸了,DKIM 检查将失败(至少在流行的验证工具上)。DKIM 必须使用 "\r\n" 进行计算,但之后在使用 PHP mail 函数时,必须将其全部切换为 "\n"。
但是,在 Windows 上,您应该使用 "\r\n",因为 PHP 在这种情况下使用的是 SMTP,因此 SMTP 协议的正常规则(而不是 Unix 管道的正常规则)适用。
值得注意的是,您可以使用 php.ini 中的 sendmail_path 指令设置一个假 sendmail 程序。
尽管该文件中存在注释,但 sendmail_path 也适用于 Window。来自 https://php.net/manual/en/mail.configuration.php#ini.sendmail-path:
此指令也适用于 Windows。如果设置了此指令,smtp、smtp_port 和 sendmail_from 将被忽略,并且将执行指定的命令。
mail() 内部
进行了一些测试,我可以说... 如果在 php.ini 中定义了 sendmail_path 或通过 ini.set() 定义,通过调用像这样的函数...
mail($to, $subject, $message, $headers, $params)
就像 php 在内部打开一个 shell,执行此命令,将此文本发送到 stdin,如果返回值 == 0 则返回 true 一样。
------------
shell> $sendmail_path $params
To: $to
Subject: $subject
$headers
$message
(EOF)
------------
在 Windows 中,我更喜欢使用 sendmail 类型的行为,而不是使用非常有限的 php smtp,通过设置 sendmail_path 然后使用 msmtp for windows。
* 发送带有附件的电子邮件
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));
}
使用电子邮件服务的最低要求发送电子邮件。
<?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);
?>
以下是我用于发送 UTF-8 编码的电子邮件的便捷小函数。
<?php
function mail_utf8($to, $from_user, $from_email,
$subject = '(No 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 的邮件。
我从这个函数向 Gmail、Yahoo、AOL 等发送邮件时遇到了问题。我使用这里的笔记发现您需要将 Return-Path 设置为有效的电子邮件地址来捕获退信。除此之外,还有两个额外的发送问题
1) 在 php.ini sendmail 参数的 -f 选项中使用的电子邮件中的域名,或者在 mail() 附加参数字段中使用的域名,需要为该域名设置有效的 SPF 记录(在 DNS 中作为“TXT”记录类型,如果可能,请添加额外的“SPF”类型记录)。为什么?因为该邮件头字段用于垃圾邮件检查。
2) 您还应该使用域名密钥或 DKIM。这里的诀窍是,域名密钥/DKIM 区分大小写!我使用 Cpanel 创建了我的域名密钥,该密钥在创建密钥时自动使用所有小写域名。我发现,当发送电子邮件并使用驼峰式大小写“-f [email protected]”选项时,我的密钥没有被接受。但是当我使用“-f [email protected]”时,它被接受了。
还有许多其他因素会导致邮件无法到达收件箱,包括您自己的多次测试失败,因此我建议您咨询每个网站的指南,不要向我寻求帮助。这些只是在我案例中有所帮助的几个技术问题。
我希望这能为某些人节省一些时间和头痛...
到目前为止,我使用以下方法确保特殊字符在邮件主题中正确显示
<?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: ”...
小心不要在 $headers 变量中添加额外的空格。
例如,这在我们的服务器上不起作用
$headers = "From: $from \r\n Bcc: $bcc \r\n";
但这样做有效
$headers = "From: $from\r\nBcc: $bcc\r\n";
注意,第一个 \r\n 周围的空格已删除。
关于 To
小心不要在 additional_headers 中重复 To,
否则 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 重复的 To 邮件头。为了减少发送到 Gmail 的垃圾邮件数量,
550-5.7.1 此邮件已被阻止。请查看
550 5.7.1 RFC 5322 规范以了解更多信息。
要定义邮件敏感度,您需要在邮件头中添加以下行
<?php
$headers = "MIME-Version: 1.0\n" ;
$headers .= "Content-Type: text/html; charset=\"iso-8859-1\"\n";
$headers .= "Sensitivity: Personal\n";
$status = mail($to, $subject, $message,$headers);
?>
可能的选项
Sensitivity: Normal、Personal、Private 和 Company-Confidential
这些将在 Outlook、Thunderbird 和其他程序中被识别和处理。
在发送 MIME 邮件时,请确保遵循文档中的“每行 70 个字符”...否则可能会丢失字符...这真的很难以追踪...