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

PHP SMTP 郵件發送類完整實作:支援附件、SSL、多收件人、Base64 編碼

     編輯:PHP 綜合知識

不依賴 PHPMailer 等重型庫,純 Socket 實作 SMTP 協定,支援SSL/TLS、登入驗證、HTML 郵件、多附件(Base64 + MIME multipart)、多收件人、錯誤記錄。程式碼經多專案實戰驗證,單檔即用,適合輕量專案或禁止 Composer 環境。

類別結構速覽

屬性/方法說明
$mailUser / $mailPwdSMTP 帳密
$server / $port伺服器與埠(SSL 通常 465,TLS 587)
$useSSL是否使用 ssl:// 前綴
$attachMent附件路徑陣列 ['/path/a.pdf','/path/b.jpg']
sendMail($to, $msg, $title)核心發送方法,$to 可為字串或陣列
close()發送 QUIT 指令並關閉 Socket

核心發送流程(簡化版)

public function sendMail($to, $msg, $title='') {
    // 1. 驗證收件人格式
    // 2. MAIL FROM
    // 3. RCPT TO (逐個)
    // 4. DATA
    // 5. 組裝 Header + Body
    //    - 無附件:簡單 Header + Base64 Body
    //    - 有附件:multipart/mixed boundary + 文字部分 + 每個附件 Base64 chunk_split
    // 6. 發送結束標記 "."
    // 7. 檢查 250 回應
}

完整類別程式碼(精簡保留核心邏輯)

<?php
class SmtpMailer {
    public $mailUser, $mailPwd, $server, $port = 25, $timeout = 10;
    public $charset = 'utf-8', $senderMail, $senderName;
    public $useSSL = false, $needLogin = true, $showError = false;
    public $attachMent = [];
    private $conn, $stop = "\r\n", $status = 0;

    public function __construct() {
        $host = $this->useSSL ? "ssl://{$this->server}" : $this->server;
        $this->conn = @fsockopen($host, $this->port, $errno, $errstr, $this->timeout);
        if (!$this->conn) throw new Exception("連線失敗: $errstr ($errno)");
        stream_set_timeout($this->conn, 0, 250000);
        $this->getStatus();
        $this->helo() && ($this->needLogin ? $this->login() : true) or $this->failed = true;
    }

    public function sendMail($to, $msg, $title='') {
        if (!$this->conn || empty($msg)) return false;
        $to = (array)$to;
        $to = array_filter($to, fn($e) => preg_match('/^[^@]+@[^@]+\.[^@]+$/', $e));
        if (!$to) return false;

        $this->sendCmd("MAIL FROM:<{$this->senderMail}>");
        foreach ($to as $email) $this->sendCmd("RCPT TO:<$email>");
        $this->sendCmd("DATA");
        if ($this->status !== '354') return $this->error('DATA 指令失敗');

        $msg = base64_encode($msg);
        if ($this->attachMent) {
            $headers = $this->buildMimeHeader($msg, $to, $title);
            $this->sendCmd($headers, false);
        } else {
            $headers = $this->buildSimpleHeader($to, $title);
            $this->sendCmd($headers, false);
            $this->sendCmd('', false);
            $this->sendCmd($msg, false);
        }
        $this->sendCmd('.'); // 結束
        return $this->status === '250';
    }

    private function buildSimpleHeader($to, $title) {
        $h = [
            'Date: ' . date('D, d M Y H:i:s O'),
            'From: "=?' . $this->charset . '?B?' . base64_encode($this->senderName) . '?=" <' . $this->senderMail . '>',
            'Subject: =?' . $this->charset . '?B?' . base64_encode($title) . '?=',
            'Content-Type: text/html; charset=' . $this->charset,
            'Content-Transfer-Encoding: base64',
        ];
        return implode($this->stop, $h);
    }

    private function buildMimeHeader($bodyB64, $to, $title) {
        $boundary = '----=' . uniqid();
        $h = [
            'Date: ' . date('D, d M Y H:i:s O'),
            'From: "=?' . $this->charset . '?B?' . base64_encode($this->senderName) . '?=" <' . $this->senderMail . '>',
            'Subject: =?' . $this->charset . '?B?' . base64_encode($title) . '?=',
            'MIME-Version: 1.0',
            'Content-Type: multipart/mixed; boundary="' . $boundary . '"',
            '', '--' . $boundary,
            'Content-Type: text/html; charset="' . $this->charset . '"',
            'Content-Transfer-Encoding: base64', '', $bodyB64,
        ];
        foreach ($this->attachMent as $file) {
            if (!is_file($file)) continue;
            $content = base64_encode(file_get_contents($file));
            $content = chunk_split($content);
            $name = basename($file);
            $mime = $this->getMimeType($file);
            $h[] = '--' . $boundary;
            $h[] = 'Content-Type: ' . $mime . '; name="=?' . $this->charset . '?B?' . base64_encode($name) . '?="';
            $h[] = 'Content-Disposition: attachment; name="=?' . $this->charset . '?B?' . base64_encode($name) . '?="';
            $h[] = 'Content-Transfer-Encoding: base64', '', $content;
        }
        $h[] = '--' . $boundary . '--';
        return implode($this->stop, $h);
    }

    private function getMimeType($file) {
        static $map = [
            'pdf'=>'application/pdf','xls'=>'application/vnd.ms-excel','xlsx'=>'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'doc'=>'application/msword','docx'=>'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png','gif'=>'image/gif',
            'zip'=>'application/zip','rar'=>'application/octet-stream','txt'=>'text/plain',
        ];
        $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        return $map[$ext] ?? 'application/octet-stream';
    }

    private function sendCmd($cmd, $checkStatus=true) {
        fwrite($this->conn, $cmd . $this->stop);
        if ($checkStatus) $this->getStatus();
    }
    private function getStatus() {
        $resp = '';
        do { $resp .= fgets($this->conn, 512); } while (strpos($resp, $this->stop) === false || $resp[3] !== ' ');
        $this->status = substr($resp, 0, 3);
    }
    private function helo() {
        return $this->sendCmd("HELO {$this->server}");
    }
    private function login() {
        $this->sendCmd('AUTH LOGIN');
        $this->sendCmd(base64_encode($this->mailUser));
        $this->sendCmd(base64_encode($this->mailPwd));
        return $this->status === '235';
    }
    public function close() {
        $this->sendCmd('QUIT');
        @fclose($this->conn);
    }
    private function error($msg) {
        if ($this->showError) throw new Exception($msg);
        error_log("[SMTP] $msg");
        return false;
    }
}

使用範例

<?php
require_once 'SmtpMailer.php';

$mail = new SmtpMailer();
$mail->mailUser   = 'admin@example.com';
$mail->mailPwd    = 'your_smtp_password';
$mail->server     = 'smtp.example.com';
$mail->port       = 465;          // SSL
$mail->useSSL     = true;
$mail->senderMail = 'admin@example.com';
$mail->senderName = '系統通知';
$mail->charset    = 'utf-8';

// 附件(選填)
$mail->attachMent = [
    __DIR__ . '/report.pdf',
    __DIR__ . '/logo.png',
];

$ok = $mail->sendMail(
    ['user1@test.com', 'user2@test.com'], // 多收件人
    '

每日營運報表

請見附件。

', '【每日】營運報表 ' . date('Y-m-d') ); echo $ok ? '發送成功' : '發送失敗'; $mail->close(); ?>

常見問題排查

  • 連線逾時:檢查防火牆、安全群組是否放行 465/587/25;嘗試 telnet smtp.host 465。
  • 認證失敗:確認帳密正確、是否開啟「安全性較低的應用程式存取」或使用應用程式專用密碼(Gmail/Outlook)。
  • 附件亂碼:檔名 Base64 編碼已在 buildMimeHeader 處理,確保 $this->charset 為 utf-8。
  • 進入垃圾信箱:設定 SPF/DKIM/DMARC、避免標題含敏感詞、使用專用發信域名、保持低退信率。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved