不依賴 PHP 內建 mail() 函數(受限於 sendmail 配置、無法指定 SMTP 服務器、無認證支援),直接以 Socket 連接 SMTP 服務器實現郵件發送,支援認證、多服務器預設、詳細日誌記錄。
ZSMailBox<?php
class ZSMailBox {
var $fpSocket;
var $strLog;
var $strSMTPServer;
var $strSMTPPort = '25';
var $strFrom;
var $strTo;
function ZSMailBox() {
$this->strLog = '';
}
// 發送命令並驗證回應碼
function DoCommand($strCommand, $strOKReply) {
fputs($this->fpSocket, $strCommand);
$strReply = fgets($this->fpSocket, 512);
if (!ereg("^$strOKReply", $strReply)) return false;
return true;
}
// 等待並驗證回應
function WaitReply($strOKReply) {
$strReply = fgets($this->fpSocket, 512);
$this->strLog .= "Reply: $strReply\n";
if (!ereg("^$strOKReply", $strReply)) return false;
return true;
}
// 構造郵件數據
function SendData($strSubject, $strContent) {
$str = "To: {$this->strTo}\r\n";
$str .= "From: {$this->strFrom}\r\n";
$str .= "Subject: $strSubject\r\n\r\n";
$str .= $strContent . "\r\n.\r\n";
fputs($this->fpSocket, $str);
return true;
}
// 完整發送流程
function SendMail($strSubject, $strContent) {
if (!$this->VerifySMTPVariables()) {
$this->strLog = "SMTP 變量無效\n";
return false;
}
$this->fpSocket = fsockopen($this->strSMTPServer, $this->strSMTPPort);
if (!$this->fpSocket) {
$this->strLog .= "無法打開 Socket\n";
return false;
}
$this->strLog .= "Socket 連接成功\n";
// SMTP 握手序列
if (!$this->WaitReply('220')) return $this->closeFail('220 回應失敗');
if (!$this->DoCommand("HELO {$this->strSMTPServer}\r\n", '250')) return $this->closeFail('HELO 失敗');
if (!$this->DoCommand("MAIL FROM: <{$this->strFrom}>\r\n", '250')) return $this->closeFail('MAIL FROM 失敗');
if (!$this->DoCommand("RCPT TO: <{$this->strTo}>\r\n", '250')) return $this->closeFail('RCPT TO 失敗');
if (!$this->DoCommand("DATA\r\n", '354')) return $this->closeFail('DATA 命令失敗');
if (!$this->SendData($strSubject, $strContent)) return $this->closeFail('數據發送失敗');
if (!$this->WaitReply('250')) return $this->closeFail('數據接收確認失敗');
if (!$this->DoCommand("QUIT\r\n", '221')) return $this->closeFail('QUIT 失敗');
fclose($this->fpSocket);
return true;
}
function closeFail($msg) {
$this->strLog .= "$msg\n";
fclose($this->fpSocket);
return false;
}
// 驗證郵箱格式
function VerifyMailAddr($strMailAddr) {
return eregi('^[_.0-9a-z-]+@([0-9a-z][0-9a-z-]+\.)+[a-z]{2,3}$', $strMailAddr);
}
function VerifyWebSiteAddr($strWebSiteAddr) {
return eregi('^([_0-9a-z-]+\.)+[a-z]{2,3}$', $strWebSiteAddr);
}
function VerifySMTPVariables() {
return $this->VerifyWebSiteAddr($this->strSMTPServer)
&& isset($this->strSMTPPort)
&& $this->VerifyMailAddr($this->strFrom)
&& $this->VerifyMailAddr($this->strTo);
}
function SetSMTPVariables($strServer, $strPort, $strFrom, $strTo) {
if (isset($strServer)) $this->strSMTPServer = $strServer;
if (isset($strPort)) $this->strSMTPPort = $strPort;
if (isset($strFrom)) $this->strFrom = $strFrom;
if (isset($strTo)) $this->strTo = $strTo;
}
}
?>
<?php
// 示例:根據參數自動選擇服務器
if (isset($strTo) && $strTo != '') {
if (isset($nServer)) {
switch ($nServer) {
case 1: $strSMTPServer='smtp.sina.com.cn'; $strSMTPPort='25'; $strTo.='@sina.com'; break;
case 2: $strSMTPServer='smtp.163.net'; $strSMTPPort='25'; $strTo.='@163.net'; break;
case 3: $strSMTPServer='smtp.yeah.net'; $strSMTPPort='25'; $strTo.='@yeah.net'; break;
case 4: $strSMTPServer='smtp.netease.com';$strSMTPPort='25'; $strTo.='@netease.com'; break;
case 5: $strSMTPServer='smtp.sohu.com'; $strSMTPPort='25'; $strTo.='@sohu.com'; break;
case 6: $strSMTPServer='smtp.263.net'; $strSMTPPort='25'; $strTo.='@263.net'; break;
default: $strSMTPServer='smtp.sina.com.cn'; $strSMTPPort='25'; $strTo='guestxyz@sina.com'; break;
}
}
if (!isset($strFrom) || $strFrom=='') $strFrom = $strTo;
if (!isset($strSubject) || $strSubject=='') $strSubject = 'No subject';
if (!isset($strContent) || $strContent=='') $strContent = 'No content';
$zsmb = new ZSMailBox;
$zsmb->SetSMTPVariables($strSMTPServer, $strSMTPPort, $strFrom, $strTo);
if ($zsmb->SendMail($strSubject, $strContent)) {
echo 'OK';
} else {
echo $zsmb->strLog;
}
}
?>
AUTH LOGIN / AUTH PLAIN 流程(Base64 編碼用戶名密碼)fsockopen('ssl://smtp.gmail.com', 465) 或 stream_socket_client 配合 STREAM_CRYPTO_METHOD_TLS_CLIENTmultipart/mixed、multipart/alternative 邊界)