在 PHP 開發中,若伺服器未安裝或無法使用 mail() 函式,或是需要更底層的控制,透過 Socket 直接與 SMTP 伺服器通訊發送郵件是一種經典且高效的替代方案。本文提供一個完整的函式範例,展示如何使用 fsockopen() 實現 SMTP 協定流程,包括 EHLO、AUTH LOGIN 認證、發件人/收件人指定、資料傳輸等關鍵步驟。
以下函式 send_mail($to, $subject, $body) 封裝了完整的 SMTP 互動邏輯:
function send_mail($to, $subject, $body) {
// SMTP 伺服器設定(請替換為您的實際帳號資訊)
$loc_host = "your_hostname";
$smtp_acc = "your_smtp_username";
$smtp_pass = "your_smtp_password";
$smtp_host = "smtp.example.com";
$from = "sender@example.com";
// 郵件標頭,指定編碼為 gb2312,內容採 Base64 編碼
$headers = "Content-Type: text/plain; charset=\"gb2312\"\r\nContent-Transfer-Encoding: base64";
$lb = "\r\n";
$hdr = explode($lb, $headers);
// 處理郵件主體:將每一行以 "." 開頭的轉義為 "..",並進行 Base64 編碼
if ($body) {
$bdy = preg_replace("/^\./", "..", explode($lb, $body));
}
// 定義 SMTP 指令序列:每個元素包含 [指令字串, 預期回應代碼, 錯誤提示前綴]
$smtp = array(
array("EHLO ".$loc_host.$lb, "220,250", "HELO error: "),
array("AUTH LOGIN".$lb, "334", "AUTH error:"),
array(base64_encode($smtp_acc).$lb, "334", "AUTHENTIFICATION error: "),
array(base64_encode($smtp_pass).$lb, "235", "AUTHENTIFICATION error: ")
);
// 後續指令:MAIL FROM, RCPT TO, DATA, 郵件標頭/主體, 結束符 ".", QUIT
$smtp[] = array("MAIL FROM: <".$from.">".$lb, "250", "MAIL FROM error: ");
$smtp[] = array("RCPT TO: <".$to.">".$lb, "250", "RCPT TO error: ");
$smtp[] = array("DATA".$lb, "354", "DATA error: ");
$smtp[] = array("From: ".$from.$lb, "", "");
$smtp[] = array("To: ".$to.$lb, "", "");
$smtp[] = array("Subject: ".$subject.$lb, "", "");
foreach ($hdr as $h) { $smtp[] = array($h.$lb, "", ""); }
$smtp[] = array($lb, "", ""); // 空行分隔標頭與主體
if ($bdy) {
foreach ($bdy as $b) {
$smtp[] = array(base64_encode($b.$lb).$lb, "", "");
}
}
$smtp[] = array(".".$lb, "250", "DATA(end) error: ");
$smtp[] = array("QUIT".$lb, "221", "QUIT error: ");
// 建立 Socket 連線
$fp = @fsockopen($smtp_host, 25);
if (!$fp) return "<b>Error:</b> Cannot connect to ".$smtp_host."<br>";
// 讀取伺服器歡迎訊息
while ($result = @fgets($fp, 1024)) {
if (substr($result, 3, 1) == " ") break;
}
$result_str = "";
// 依序發送指令並檢查回應
foreach ($smtp as $req) {
@fputs($fp, $req[0]);
if ($req[1]) {
while ($result = @fgets($fp, 1024)) {
if (substr($result, 3, 1) == " ") break;
}
if (!strstr($req[1], substr($result, 0, 3))) {
$result_str .= $req[2].$result."<br>";
}
}
}
@fclose($fp);
return ($result_str == "") ? "發送成功!!" : $result_str;
}
. 結束 DATA 階段。fsockopen("ssl://smtp.host", 465) 或搭配 stream_socket_enable_crypto() 升級為 STARTTLS。此函式不依賴 PHP 的 mail() 或任何擴充套件,僅需核心的 sockets 功能即可運作,適合需要高度自訂或在受限環境下發送郵件的場景。