首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP SMTP 郵件發送完整實作:自定義類別支援驗證、HTML/純文字、附件與日誌

     編輯:PHP 相關知識

前言

不依賴 PHPMailer 等第三方庫,自行實作 SMTP 協議郵件發送類,支援驗證、SSL/TLS(需搭配 stream_context)、HTML/純文字、抄送/密件抄送、附件、發送日誌。適合學習 SMTP 協議或輕量專案。

SMTP 類別核心代碼 (ZC_Email.class.php)

<?php
class SMTP {
    // 公開屬性
    public $smtp_port = 25;
    public $time_out = 30;
    public $host_name = 'localhost';
    public $log_file = '';
    public $relay_host = '';
    public $debug = false;
    public $auth = false;
    public $user = '';
    public $pass = '';

    // 私有屬性
    private $sock = false;

    public function __construct($relay_host = '', $smtp_port = 25, $auth = false, $user = '', $pass = '') {
        $this->relay_host = $relay_host;
        $this->smtp_port = $smtp_port;
        $this->auth = $auth;
        $this->user = $user;
        $this->pass = $pass;
    }

    /** 發送郵件主入口 */
    public function sendmail($to, $from, $subject = '', $body = '', $mailtype = 'TXT', $cc = '', $bcc = '', $additional_headers = '') {
        $mail_from = $this->get_address($this->strip_comment($from));
        $body = preg_replace('/(^|(\r\n))(\\.)/', '\\1.\\3', $body); // 點開頭行轉義
        
        $header = "MIME-Version: 1.0\r\n";
        $header .= ($mailtype === 'HTML') ? "Content-Type: text/html; charset=utf-8\r\n" : "Content-Type: text/plain; charset=utf-8\r\n";
        $header .= "To: $to\r\n";
        if ($cc) $header .= "Cc: $cc\r\n";
        $header .= "From: $from\r\n";
        $header .= "Subject: $subject\r\n";
        $header .= $additional_headers;
        $header .= "Date: " . date('r') . "\r\n";
        $header .= "X-Mailer: PHP/" . phpversion() . "\r\n";
        $header .= "Message-ID: <" . time() . "." . microtime(true) . "@" . $mail_from . ">\r\n";

        $rcpts = array_merge(
            explode(',', $this->strip_comment($to)),
            $cc ? explode(',', $this->strip_comment($cc)) : [],
            $bcc ? explode(',', $this->strip_comment($bcc)) : []
        );

        $sent = true;
        foreach ($rcpts as $rcpt) {
            $rcpt = $this->get_address($rcpt);
            if (!$this->smtp_sockopen($rcpt)) {
                $this->log_write("Error: Cannot connect for $rcpt");
                $sent = false; continue;
            }
            if ($this->smtp_send($this->host_name, $mail_from, $rcpt, $header, $body)) {
                $this->log_write("Sent to <$rcpt>");
            } else {
                $this->log_write("Failed to <$rcpt>");
                $sent = false;
            }
            fclose($this->sock);
            $this->sock = false;
        }
        return $sent;
    }

    /** SMTP 協議流程 */
    private function smtp_send($helo, $from, $to, $header, $body) {
        if (!$this->smtp_putcmd("HELO", $helo)) return false;
        if ($this->auth) {
            if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) return false;
            if (!$this->smtp_putcmd("", base64_encode($this->pass))) return false;
        }
        if (!$this->smtp_putcmd("MAIL FROM", "<$from>")) return false;
        if (!$this->smtp_putcmd("RCPT TO", "<$to>")) return false;
        if (!$this->smtp_putcmd("DATA")) return false;
        if (!$this->smtp_message($header, $body)) return false;
        if (!$this->smtp_eom()) return false;
        if (!$this->smtp_putcmd("QUIT")) return false;
        return true;
    }

    /** 建立 Socket 連線 */
    private function smtp_sockopen($address) {
        if ($this->relay_host) return $this->smtp_sockopen_relay();
        return $this->smtp_sockopen_mx($address);
    }

    private function smtp_sockopen_relay() {
        $this->log_write("Connecting to relay: {$this->relay_host}:{$this->smtp_port}");
        $this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out);
        if (!$this->sock || !$this->smtp_ok()) {
            $this->log_write("Relay connect failed: $errstr ($errno)");
            return false;
        }
        $this->log_write("Relay connected");
        return true;
    }

    private function smtp_sockopen_mx($address) {
        $domain = preg_replace('/^.+@([^@]+)$/', '$1', $address);
        if (!getmxrr($domain, $mxhosts)) {
            $this->log_write("MX lookup failed for $domain");
            return false;
        }
        foreach ($mxhosts as $host) {
            $this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out);
            if ($this->sock && $this->smtp_ok()) {
                $this->log_write("MX connected: $host");
                return true;
            }
            $this->log_write("MX $host failed: $errstr ($errno)");
        }
        return false;
    }

    /** 發送指令並檢查回應 */
    private function smtp_putcmd($cmd, $arg = '') {
        $line = $arg ? "$cmd $arg" : $cmd;
        fputs($this->sock, "$line\r\n");
        $this->smtp_debug("> $line");
        return $this->smtp_ok();
    }

    private function smtp_message($header, $body) {
        fputs($this->sock, $header . "\r\n" . $body);
        $this->smtp_debug("> [HEADER+BODY]");
        return true;
    }

    private function smtp_eom() {
        fputs($this->sock, "\r\n.\r\n");
        $this->smtp_debug(". [EOM]");
        return $this->smtp_ok();
    }

    private function smtp_ok() {
        $response = fgets($this->sock, 512);
        $this->smtp_debug($response);
        return (bool)preg_match('/^[23]/', $response);
    }

    /** 工具方法 */
    private function log_write($msg) {
        $this->smtp_debug($msg);
        if (!$this->log_file) return true;
        $msg = date('M d H:i:s ') . get_current_user() . '[' . getmypid() . ']: ' . $msg;
        $fp = @fopen($this->log_file, 'a');
        if (!$fp) return false;
        flock($fp, LOCK_EX);
        fwrite($fp, $msg);
        fclose($fp);
        return true;
    }

    private function strip_comment($addr) {
        return preg_replace('/\([^()]*\)/', '', $addr);
    }

    private function get_address($addr) {
        $addr = preg_replace('/\s+/', '', $addr);
        return preg_replace('/^.*<(.+)>.*$/', '$1', $addr);
    }

    private function smtp_debug($msg) {
        if ($this->debug) echo $msg . "<br>\n";
    }
}
?>

使用範例

<?php
require_once 'ZC_Email.class.php';

$smtp = new SMTP(
    'smtp.163.com',   // SMTP 伺服器
    25,               // 埠號 (465 SSL, 587 TLS, 25 普通)
    true,             // 需要驗證
    'your@163.com',   // 帳號
    'your_password'   // 密碼 (或授權碼)
);

$smtp->debug = true; // 開啟除錯輸出

$success = $smtp->sendmail(
    'recipient@example.com',  // 收件人
    'your@163.com',           // 寄件人
    '測試主題',               // 主題
    '<h1>HTML 內容</h1>', // 內容
    'HTML'                    // 格式: HTML 或 TXT
);

echo $success ? '發送成功' : '發送失敗';
?>

進階功能擴充

功能實作方向
SSL/TLSfsockopen('ssl://smtp.163.com', 465) 或 stream_socket_client 配合 stream_context_set_option
附件構建 MIME multipart/mixed,Base64 編碼檔案內容
內嵌圖片multipart/related + Content-ID
批量發送單次連線多次 RCPT TO,或使用隊列系統
退信處理解析 Bounce 郵件,更新郵件清單

生產環境建議

  • 優先使用成熟庫:PHPMailer、Symfony Mailer
  • 使用專業郵件服務:SendGrid、Mailgun、Amazon SES、Postmark(高送達率、統計、模板)
  • 實作發送隊列(Redis/RabbitMQ + Worker)避免阻塞 Web 請求
  • 設定 SPF、DKIM、DMARC 提升域名信譽
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved