安全性建議:雖然沒有文件說明,但對於 $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 傳送時,From
標頭也會設定Return-Path
(僅限 Windows)。
注意:
如果未收到訊息,請嘗試僅使用 LF (\n)。 一些 Unix 郵件傳輸代理程式(最值得注意的是 » qmail)會自動將 LF 替換為 CRLF(如果使用 CRLF,則會導致 CR 加倍)。 這應該是最後的手段,因為它不符合 » RFC 2822。
additional_params
(選用)可以使用 additional_params
參數,將其他旗標作為命令列選項傳遞給設定為在傳送郵件時使用的程式,如 sendmail_path
組態設定所定義。 例如,當使用具有 -f
sendmail 選項的 sendmail 時,可以使用它來設定信封寄件者位址。
此參數在內部會被 escapeshellcmd() 逸出,以防止命令執行。 escapeshellcmd() 可防止命令執行,但允許新增額外的參數。 出於安全原因,建議使用者清理此參數,以避免將不需要的參數新增至 shell 命令。
由於 escapeshellcmd() 會自動應用,因此網際網路 RFC 允許作為電子郵件位址的一些字元無法使用。 mail() 不允許使用這些字元,因此在需要使用這些字元的程式中,建議使用其他方式傳送電子郵件(例如使用框架或程式庫)。
應將執行 Web 伺服器的使用者新增為 sendmail 組態中的信任使用者,以防止在使用此方法設定信封寄件者 (-f) 時,將 'X-Warning' 標頭新增至訊息。 對於 sendmail 使用者,此檔案為 /etc/mail/trusted-users。
範例 #1 傳送郵件。
使用 mail() 傳送簡單電子郵件
<?php
// 訊息
$message = "第一行\r\n第二行\r\n第三行";
// 萬一我們的任何一行超過 70 個字元,我們應該使用 wordwrap()
$message = wordwrap($message, 70, "\r\n");
// 傳送
mail('caffeinated@example.com', '我的主旨', $message);
?>
範例 #2 傳送帶有額外標頭的郵件。
新增基本標頭,告訴 MUA 寄件者和回覆至的位址
<?php
$to = 'nobody@example.com';
$subject = 'the subject';
$message = 'hello';
$headers = 'From: webmaster@example.com' . "\r\n" .
'Reply-To: webmaster@example.com' . "\r\n" .
'X-Mailer: PHP/' . phpversion();
mail($to, $subject, $message, $headers);
?>
範例 #3 使用額外標頭以陣列方式發送郵件
此範例發送與上述範例相同的郵件,但將額外的標頭以陣列方式傳遞(自 PHP 7.2.0 起可用)。
<?php
$to = 'nobody@example.com';
$subject = 'the subject';
$message = 'hello';
$headers = array(
'From' => 'webmaster@example.com',
'Reply-To' => 'webmaster@example.com',
'X-Mailer' => 'PHP/' . phpversion()
);
mail($to, $subject, $message, $headers);
?>
範例 #4 使用額外的命令列參數發送郵件。
additional_params
參數可用於將額外的參數傳遞給設定為使用 sendmail_path
發送郵件時所使用的程式。
<?php
mail('nobody@example.com', 'the subject', 'the message', null,
'-fwebmaster@example.com');
?>
範例 #5 發送 HTML 郵件
也可以使用 mail() 發送 HTML 郵件。
<?php
// 多個收件者
$to = 'johny@example.com, sally@example.com'; // 注意逗號
// 主題
$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>10th</td><td>八月</td><td>1970</td>
</tr>
<tr>
<td>Sally</td><td>17th</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 <mary@example.com>, Kelly <kelly@example.com>';
$headers[] = 'From: 生日提醒 <birthday@example.com>';
$headers[] = 'Cc: birthdayarchive@example.com';
$headers[] = 'Bcc: birthdaycheck@example.com';
// 發送郵件
mail($to, $subject, $message, implode("\r\n", $headers));
?>
注意:
如果打算發送 HTML 或其他複雜的郵件,建議使用 PEAR 套件 » PEAR::Mail_Mime。
注意:
mail() 的 SMTP 實作(僅限 Windows)在許多方面與 sendmail 實作不同。首先,它不使用本機二進位檔來組合訊息,而是僅在直接 socket 上操作,這表示需要一個
MTA
在網路 socket 上監聽(可以在本機主機或遠端機器上)。其次,像
From:
、Cc:
、Bcc:
和Date:
等自訂標頭首先不會被MTA
解釋,而是由 PHP 解析。因此,
to
參數不應使用 "Something <someone@example.com>" 格式的地址。與 MTA 通訊時,mail 命令可能無法正確解析此格式。
注意:
值得注意的是,mail() 函數不適用於迴圈中大量發送電子郵件。此函數為每封電子郵件開啟和關閉一個 SMTP socket,效率不高。
對於發送大量電子郵件,請參閱 » 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('example@example.com', 'My Subject', $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` 設定檔。以下是使用 XAMPP 設定 PHP 以透過 Gmail 的 SMTP 伺服器發送電子郵件的正式步驟
設定 php.ini
1. 在編輯器中開啟 `php.ini`
在您慣用的文字編輯器中開啟 `php.ini` 設定檔。
2. 找到郵件函數
使用搜尋功能 (Ctrl + F) 來尋找 `php.ini` 檔案中與郵件函數相關的區段。
3. 更新郵件函數設定
將以下設定參數複製並貼到郵件函數區段中。將所有其他與郵件相關的設定註解掉或停用。
要編輯的 php.ini 程式碼
SMTP=smtp.gmail.com
smtp_port=587
sendmail_from = yourmail@gmail.com
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
auth_username=yourmail@gmail.com
auth_password=app_password_after_enabling_two_factor_authentication_for_your_mail_id
force_sender=priyansh.kala.4@gmail.com
3. 儲存變更
插入指定的設定後,儲存 `sendmail.ini` 檔案。
這些步驟設定 PHP 以使用 Gmail 的 SMTP 伺服器發送電子郵件。請確保已儲存修改,並且已重新啟動必要的 XAMPP 服務以使變更生效。
請注意,在組態檔案中使用硬編碼密碼會構成安全風險。在生產環境中應避免直接將密碼儲存在純文字檔案中。為了更好的安全性,請考慮使用環境變數或安全的憑證管理系統。
寄送郵件的程式碼 -
<?php
$subject = "用於檢查的郵件";
$msg = "嘿!讓我們玩 PHP。";
$receiver = "reciever@gmail.com";
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('瑪格麗特·穆勒') . '?= <recipient@domain.com>';
?>
而且不要忘記也對電子郵件訊息進行 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 = 'myapp@someserver.com';
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() 內部運作
進行一些測試後,我可以說...如果 sendmail_path 在 php.ini 中定義或由 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 然後為 Windows 使用 msmtp
值得注意的是,您可以使用 php.ini 中的 sendmail_path 指令設定一個假的 sendmail 程式。
儘管該檔案中有註解,但 sendmail_path 也適用於 Windows。來自 https://php.dev.org.tw/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 會直接拒絕接受任何沒有伴隨 \r 的單獨 \n 的訊息。
到目前為止,我使用以下方法來確保特殊字元在郵件主旨中正確顯示
<?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、Yahoo、AOL 等發送郵件時遇到了傳送問題。我使用了這裡的註解來理解到您需要將 Return-Path 設定為有效的電子郵件,以捕捉退信。除此之外,還有兩個額外的傳送陷阱
1) php.ini sendmail 參數或 mail() 額外參數欄位中的 -f 選項中使用的電子郵件中的網域,需要具有該網域的有效 SPF 記錄(在 DNS 中肯定作為「TXT」記錄類型,並在可能的情況下新增額外的「SPF」類型記錄)。為什麼?這是用於垃圾郵件檢查的標頭欄位。
2) 您也應該使用網域金鑰或 DKIM。這裡的訣竅是網域金鑰/DKIM 是區分大小寫的!我使用 Cpanel 來建立我的網域金鑰,它在金鑰建立中自動使用了全部小寫的網域名稱。我發現當發送電子郵件並使用駝峰式 "-f account@MyDomainHere.Com" 選項時,我的金鑰不被接受。但是,當我使用 "-f account@mydomainhere.com" 時,它被接受了。
還有許多其他因素可能會導致郵件無法進入收件匣,包括您自己多次失敗的測試嘗試,因此我建議您查閱每個網站的指南,而不要向我尋求協助。這些只是對我的情況有幫助的幾個技術問題。
我希望這能為某些人節省一些時間和頭痛...
請注意不要在 $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 會因此標記它
host 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 規範以取得更多資訊。