在不依賴 PHPMailer 等第三方庫的情況下,直接使用 PHP 的 Socket 擴展實現 SMTP 郵件發送,能夠更深入地理解郵件協議原理,也適合輕量級項目或教學場景。本文提供一個功能完整、支持 HTML 格式、帶錯誤處理的 SMTP 發送類實現。
SMTP(簡單郵件傳輸協議)基於 TCP 連接,通過文本命令與郵件服務器交互。PHP 的 socket_create、socket_connect、socket_write、socket_read 等函數提供了底層網絡通信能力。發送流程主要包括:建立連接 → EHLO/HELO 問候 → AUTH LOGIN 認證 → MAIL FROM/RCPT TO 指定收發件人 → DATA 傳輸郵件內容 → QUIT 結束會話。
<?php
class SmtpMailer {
private $host;
private $port = 25;
private $username;
private $password;
private $from;
private $to;
private $subject;
private $body;
private $isHtml = false;
private $socket;
private $error = '';
public function __construct(array $config = []) {
foreach ($config as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
}
public function setServer(string $host, int $port = 25): bool {
if (empty($host) || !is_string($host)) {
$this->error = 'Invalid server address';
return false;
}
$this->host = $host;
$this->port = $port;
return true;
}
public function setMailInfo(array $info): bool {
$required = ['from', 'to', 'subject', 'body', 'username', 'password'];
if (count(array_intersect_key(array_flip($required), $info)) !== count($required)) {
$this->error = 'Missing required mail fields';
return false;
}
foreach ($info as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
return true;
}
public function send(): bool {
if (!$this->connect()) return false;
$commands = $this->buildCommands();
foreach ($commands as [$cmd, $expectedCode]) {
if (!$this->sendCommand($cmd, $expectedCode)) {
$this->close();
return false;
}
}
$this->close();
return true;
}
public function getError(): string {
return $this->error;
}
private function connect(): bool {
if (!function_exists('socket_create')) {
$this->error = 'PHP sockets extension not enabled';
return false;
}
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->socket) {
$this->error = socket_strerror(socket_last_error());
return false;
}
if (!socket_connect($this->socket, $this->host, $this->port)) {
$this->error = socket_strerror(socket_last_error());
return false;
}
// 讀取服務器歡迎信息
$response = socket_read($this->socket, 1024);
return $this->checkResponse($response, 220);
}
private function buildCommands(): array {
$headers = "From: {$this->from}\r\n";
$headers .= "To: {$this->to}\r\n";
$headers .= "Subject: {$this->subject}\r\n";
if ($this->isHtml) {
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
} else {
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
}
$message = $headers . "\r\n" . $this->body . "\r\n.\r\n";
return [
["EHLO localhost\r\n", 250],
["AUTH LOGIN\r\n", 334],
[base64_encode($this->username) . "\r\n", 334],
[base64_encode($this->password) . "\r\n", 235],
["MAIL FROM: <{$this->from}>\r\n", 250],
["RCPT TO: <{$this->to}>\r\n", 250],
["DATA\r\n", 354],
[$message, 250],
["QUIT\r\n", 221]
];
}
private function sendCommand(string $command, int $expectedCode): bool {
if (!socket_write($this->socket, $command, strlen($command))) {
$this->error = 'Send failed: ' . socket_strerror(socket_last_error());
return false;
}
$response = trim(socket_read($this->socket, 1024));
return $this->checkResponse($response, $expectedCode);
}
private function checkResponse(string $response, int $expectedCode): bool {
if (empty($response)) {
$this->error = 'Empty response from server';
return false;
}
if (!preg_match("/^{$expectedCode}/", $response)) {
$this->error = "SMTP Error: {$response}";
return false;
}
return true;
}
private function close(): void {
if ($this->socket) {
socket_shutdown($this->socket);
socket_close($this->socket);
}
}
}
<?php
require_once 'SmtpMailer.php';
$config = [
'host' => 'smtp.163.com',
'port' => 465, // SSL 端口需配合 stream_socket_client 或 openssl
'username' => 'your_email@163.com',
'password' => 'your_auth_code', // 授權碼,非登錄密碼
'from' => 'your_email@163.com',
'to' => 'recipient@example.com',
'subject' => 'PHP Socket SMTP 測試',
'body' => '這是一封 HTML 郵件
通過原生 Socket 發送。
',
'isHtml' => true
];
$mailer = new SmtpMailer($config);
if ($mailer->send()) {
echo '郵件發送成功!';
} else {
echo '發送失敗:' . $mailer->getError();
}
stream_socket_client + STREAM_CRYPTO_METHOD_TLS_CLIENT 實現,或使用 OpenSSL 封裝。=?UTF-8?B?base64_string?=)。原生 Socket 實現 SMTP 雖然代碼量較大,但能幫助開發者深入理解郵件發送底層流程。實際項目中,推薦使用成熟的 PHPMailer、Symfony Mailer 或 Laravel Mail 等庫,它們封裝了加密、附件、嵌入圖片、隊列發送等豐富功能,更加穩健高效。