.NET Framework 早期 System.Web.Mail 功能受限,System.Net.Mail (2.0+) 雖強大但理解底層 SMTP 協議、處理特殊編碼、附件邊界仍有學習價值。本文呈現一個純 Socket 實現的 SMTP 客戶端類別 SmtpMail,支援:SMTP AUTH (LOGIN)、多收件人、HTML/純文本、Base64 編碼主題/附件名/正文、多部分 MIME、附件傳輸、優先級、詳細日誌/錯誤碼對照。代碼經過實戰驗證,修正了 1.0 版中文內容丟失、亂碼、附件損壞等問題。
namespace Lion.Web.Mail
{
public class SmtpMail
{
// 設定屬性
public string Charset { get; set; } = "GB2312"; // 郵件編碼
public string From { get; set; } // 發件人郵箱
public string FromName { get; set; } // 發件人顯示名
public string RecipientName { get; set; } // 收件人顯示名
public string MailDomain { set { /* 解析 server:port@user:pass */ } }
public int MailDomainPort { set { /* port */ } }
public string MailServerUserName { set { /* user + 啟用 ESmtp */ } }
public string MailServerPassWord { set { /* pass */ } }
public string Priority { set { /* High/Normal/Low -> 1/3/5 */ } }
public bool Html { get; set; } // 是否 HTML
public string Subject { get; set; }
public string Body { get; set; }
public int RecipientMaxNum { set { /* 最大收件人數 */ } }
// 唯讀屬性
public string ErrorMessage { get; private set; }
public string Logs { get; private set; } // SMTP 交互日誌
// 方法
public void AddAttachment(params string[] filePaths) { /* ... */ }
public bool AddRecipient(params string[] emails) { /* ... */ }
public bool Send() { /* 核心發送流程 */ }
public bool Send(string smtpServer) { /* 便捷重載 */ }
public bool Send(string smtpServer, string from, string fromName,
string to, string toName, bool html, string subject, string body) { /* 全參數重載 */ }
}
}
TcpClient.Connect(server, port) → NetworkStreamHELO serverEHLO server → AUTH LOGIN → Base64(user) → Base64(pass)MAIL FROM:<from> → 多個 RCPT TO:<to>DATA → MIME 完整郵件內容 → .\r\n 結束QUIT
// 邊界字串:HTML 有附件 vs 無附件、純文本有附件 用不同前綴區分
string boundary = Html
? "=====001_Dragon520636771063_====="
: "=====001_Dragon303406132050_=====";
// 主題/附件名/顯示名 Base64 編碼:= ?charset?B?base64?=
string EncodeHeader(string text, string charset) {
return "=?" + charset.ToUpper() + "?B?" + Convert.ToBase64String(Encoding.GetEncoding(charset).GetBytes(text)) + "?=";
}
// 正文/附件內容 Base64 編碼
string EncodeBody(string text, string charset) {
return Convert.ToBase64String(Encoding.GetEncoding(charset).GetBytes(text));
}
// 附件檔案讀取 Base64
string GetFileBase64(string path) {
byte[] bytes = File.ReadAllBytes(path);
return Convert.ToBase64String(bytes);
}
| 問題 | 原因 | 修正 |
|---|---|---|
| 163 郵箱文本模式內容消失 | MIME 結構錯誤,純文本部分未正確封裝 | 統一使用 multipart/alternative (HTML) 或 multipart/mixed (含附件) 結構,明確 Content-Transfer-Encoding: base64 |
| Outlook 收到亂碼尾隨 | Base64 解碼邊界處理錯誤,行尾未換行 | 每行 76 字符換行(MIME 標準),附件結束雙換行 |
| 新郵箱 (126.com) 亂碼 | Charset 未正確聲明/編碼不匹配 | 全鏈路指定 charset=GB2312 (或 UTF-8),Header/Body 一致 |
| 附件打開內容錯亂 | 附件 Content-Type 錯誤、Base64 編碼錯誤 | 附件用 application/octet-stream,檔案名同樣 Base64 編碼於 Header |
var mail = new SmtpMail {
Charset = "UTF-8", // 建議現代環境用 UTF-8
From = "sender@domain.com",
FromName = "發件人名稱",
RecipientName = "收件人名稱",
Html = true,
Subject = "測試郵件",
Body = "HTML 內容
支援中文
",
Priority = "High"
};
mail.MailDomain = "user:pass@smtp.domain.com:25"; // 或分開設定
mail.AddRecipient("to1@domain.com", "to2@domain.com");
mail.AddAttachment(@"C:\report.pdf", @"C:\image.png");
if (mail.Send()) {
Console.WriteLine("發送成功");
} else {
Console.WriteLine("失敗: " + mail.ErrorMessage);
Console.WriteLine("日誌: " + mail.Logs);
}
// 成功碼
220 服務就緒
250 操作完成
251 非本地用戶,將轉發
354 開始輸入郵件內容
221 關閉連線
334 驗證挑戰 (Base64)
235 驗證成功
// 錯誤碼 (部分)
500 語法錯誤
501 參數錯誤
503 需要認證
550 郵箱不可用/不存在
553 郵箱名格式錯誤
530 需要認證
421 服務未就緒
450/451/452 暫時失敗
UTF-8;舊系統相容才用 GB2312/Big5SslStream 包裝 NetworkStreamErrorMessage 含人類可讀說明,Logs 含完整 SMTP 對話,除錯必備MailKit (跨平台、支援 OAuth2、IMAP/POP3/SMTP 全協議)手寫 SMTP 客戶端是理解郵件協議、MIME 結構、Base64 編碼、TCP 通訊的最佳練習。本 SmtpMail 類別修正了實戰中常見的中文編碼、附件邊界、多部分結構問題,展示了協議級開發的嚴謹性。雖然生產環境推薦成熟庫,但掌握底層細節能在排查郵件發送疑難雜症時游刃有餘。