核心類別架構
namespace KSN.Web.Mail
{
// 郵件內容實體
public class MailMessage
{
public string Sender { get; set; }
public StringCollection Receivers { get; } = new StringCollection();
public string Subject { get; set; } = "";
public string XMailer { get; set; } = "";
public StringCollection Attachments { get; } = new StringCollection();
public MailEncodings MailEncoding { get; set; } = MailEncodings.GB2312;
public MailTypes MailType { get; set; } = MailTypes.Html;
public byte[] MailBody { get; set; }
}
// 郵件編碼枚舉
public enum MailEncodings { GB2312, ASCII, Unicode, UTF8 }
// 郵件格式枚舉
public enum MailTypes { Html, Text }
// SMTP 驗證方式
public enum SmtpValidateTypes
{
None, // 無需驗證
Login, // AUTH LOGIN
Plain, // AUTH PLAIN
CRAMMD5 // CRAM-MD5
}
// SMTP 發送核心類別
public class KSN_Smtp
{
#region 欄位
private TcpClient _tcpClient;
private NetworkStream _networkStream;
private StringDictionary _errorCodes = new StringDictionary();
private StringDictionary _rightCodes = new StringDictionary();
private string _errorMessage = "";
private string _logs = "";
private StringCollection _validateTypes = new StringCollection();
private const string CRLF = "\r\n";
private string _serverName = "smtp";
private string _logPath = null;
private string _userId = null;
private string _password = null;
private string _mailEncodingName = "GB2312";
private bool _sendIsComplete = false;
private SmtpValidateTypes _smtpValidateType = SmtpValidateTypes.Login;
#endregion
#region 屬性
public string ErrorMessage => _errorMessage;
public string LogPath { get => _logPath; set => _logPath = value; }
public string UserID { get => _userId; set => _userId = value; }
public string PassWord { get => _password; set => _password = value; }
public SmtpValidateTypes SmtpValidateType { get => _smtpValidateType; set => _smtpValidateType = value; }
#endregion
#region 建構函式
public KSN_Smtp(string server, int port)
{
_tcpClient = new TcpClient(server, port);
_networkStream = _tcpClient.GetStream();
_serverName = server;
InitializeFields();
}
public KSN_Smtp(IPAddress ip, int port)
{
_tcpClient = new TcpClient(new IPEndPoint(ip, port));
_networkStream = _tcpClient.GetStream();
_serverName = ip.ToString();
InitializeFields();
}
#endregion
#region 核心方法
private void InitializeFields()
{
_logs = "================" + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + "================\r\n";
// 錯誤狀態碼對照表
_errorCodes.Add("421", "服務未就緒,關閉傳輸通道");
_errorCodes.Add("432", "需要密碼轉換");
_errorCodes.Add("450", "郵箱不可用");
_errorCodes.Add("451", "放棄操作");
_errorCodes.Add("452", "系統存儲不足");
_errorCodes.Add("454", "暫時認證失敗");
_errorCodes.Add("500", "郵箱地址錯誤");
_errorCodes.Add("501", "參數格式錯誤");
_errorCodes.Add("502", "命令不可實現");
_errorCodes.Add("503", "命令次序不正確");
_errorCodes.Add("504", "命令參數不可實現");
_errorCodes.Add("530", "需要認證");
_errorCodes.Add("534", "認證機制過於簡單");
_errorCodes.Add("538", "需要加密認證");
_errorCodes.Add("550", "郵箱不可用(未找到/不能用)");
_errorCodes.Add("551", "用戶非本地");
_errorCodes.Add("552", "存儲分配過量");
_errorCodes.Add("553", "郵箱名不可用(格式錯誤)");
_errorCodes.Add("554", "傳送失敗");
_errorCodes.Add("535", "用戶身份驗證失敗");
// 成功狀態碼對照表
_rightCodes.Add("220", "服務就緒");
_rightCodes.Add("221", "服務關閉傳輸通道");
_rightCodes.Add("235", "驗證成功");
_rightCodes.Add("250", "操作完成");
_rightCodes.Add("251", "非本地用戶,將轉發");
_rightCodes.Add("334", "服務器響應驗證 Base64 字串");
_rightCodes.Add("354", "開始郵件輸入,以 <CRLF>.<CRLF> 結束");
// 讀取伺服器歡迎訊息
using (var reader = new StreamReader(_networkStream))
_logs += reader.ReadLine() + CRLF;
}
private string SendCommand(string cmd, bool isMailData)
{
if (string.IsNullOrWhiteSpace(cmd)) return null;
if (!isMailData) cmd += CRLF;
_logs += cmd;
byte[] cmdBytes;
if (!isMailData)
{
cmdBytes = Encoding.ASCII.GetBytes(cmd);
_networkStream.Write(cmdBytes, 0, cmdBytes.Length);
}
else
{
cmdBytes = Encoding.GetEncoding(_mailEncodingName).GetBytes(cmd);
_networkStream.BeginWrite(cmdBytes, 0, cmdBytes.Length, AsyncCallback, null);
}
using (var reader = new StreamReader(_networkStream))
{
string response = reader.ReadLine();
_logs += response + CRLF;
string statusCode = response.Substring(0, 3);
bool isKnown = false, isSuccess = true;
foreach (string code in _errorCodes.Keys)
if (statusCode == code) { isKnown = true; isSuccess = false; break; }
foreach (string code in _rightCodes.Keys)
if (statusCode == code) { isKnown = true; break; }
if (!isKnown) SetError("非合法 SMTP 主機或服務器拒絕服務");
else if (!isSuccess) SetError(statusCode + ":" + _errorCodes[statusCode]);
else _errorMessage = "";
return response;
}
}
private void LoginByLogin()
{
string userB64 = ConvertToBase64(_userId, "ASCII");
string passB64 = ConvertToBase64(_password, "ASCII");
SendCommand("helo " + _serverName, false);
SendCommand("auth login", false);
SendCommand(userB64, false);
SendCommand(passB64, false);
}
private void LoginByPlain()
{
string nullChar = ((char)0).ToString();
string loginStr = nullChar + _userId + nullChar + _password;
string b64 = ConvertToBase64(loginStr, "ASCII");
SendCommand("helo " + _serverName, false);
SendCommand(b64, false);
}
private void LoginByCramMd5()
{
SendCommand("helo " + _serverName, false);
string response = SendCommand("auth CRAM-MD5", false);
string challenge = response.Substring(4);
// 注意:原始碼引用 KSN_MACTripleDES,此處略過具體 HMAC-MD5 實作
// 實際應使用 HMACMD5 類別計算回應
}
public bool SendMail(MailMessage mail)
{
bool sent = true;
try
{
// 驗證必要條件
if (mail.Sender == null) SetError("未設置發件人");
if (mail.Receivers.Count == 0) SetError("至少需要一個收件人");
if (_smtpValidateType != SmtpValidateTypes.None && (string.IsNullOrEmpty(_userId) || string.IsNullOrEmpty(_password)))
SetError("需 SMTP 驗證但未提供帳號");
// 登入
switch (_smtpValidateType)
{
case SmtpValidateTypes.None: SendCommand("helo " + _serverName, false); break;
case SmtpValidateTypes.Login: LoginByLogin(); break;
case SmtpValidateTypes.Plain: LoginByPlain(); break;
case SmtpValidateTypes.CRAMMD5: LoginByCramMd5(); break;
}
// MAIL FROM
SendCommand("mail from:<" + mail.Sender + ">", false);
// RCPT TO
foreach (string rcpt in mail.Receivers)
SendCommand("rcpt to:<" + rcpt + ">", false);
// DATA
SendCommand("data", false);
// 建構郵件內容
_mailEncodingName = mail.MailEncoding.ToString();
string message = BuildMailContent(mail);
// 發送郵件資料
SendCommand(message, true);
if (_sendIsComplete) SendCommand("QUIT", false);
}
catch { sent = false; }
finally
{
Disconnect();
if (!string.IsNullOrEmpty(_logPath)) WriteLog();
}
return sent;
}
private string BuildMailContent(MailMessage mail)
{
string msg = "Subject:" + mail.Subject + CRLF;
msg += "X-mailer:" + mail.XMailer + CRLF;
msg += "MIME-Version:1.0" + CRLF;
if (mail.Attachments.Count == 0)
{
if (mail.MailType == MailTypes.Text)
{
msg += "Content-Type:text/plain;" + CRLF + " ".PadRight(8) + "charset=\"" + mail.MailEncoding + "\"" + CRLF;
msg += "Content-Transfer-Encoding:base64" + CRLF + CRLF;
if (mail.MailBody != null) msg += Convert.ToBase64String(mail.MailBody) + CRLF + CRLF + CRLF + "." + CRLF;
}
else // Html
{
msg += "Content-Type:multipart/alternative;" + CRLF + " ".PadRight(8) + "boundary=\"=====003_Dragon310083331177_=====\"" + CRLF + CRLF + CRLF;
msg += "This is a multi-part message in MIME format" + CRLF + CRLF;
msg += "--=====003_Dragon310083331177_=====" + CRLF;
msg += "Content-Type:text/html;" + CRLF + " ".PadRight(8) + "charset=\"" + mail.MailEncoding.ToString().ToLower() + "\"" + CRLF;
msg += "Content-Transfer-Encoding:base64" + CRLF + CRLF;
if (mail.MailBody != null) msg += Convert.ToBase64String(mail.MailBody) + CRLF + CRLF;
msg += "--=====003_Dragon310083331177_=====--" + CRLF + CRLF + CRLF + "." + CRLF;
}
}
else // 有附件
{
// 附件處理邏輯(略,原始碼完整包含 multipart/mixed 邊界處理)
// 核心:各附件讀取檔案 → Base64 → 組裝 MIME 部分
}
return msg;
}
private void AsyncCallback(IAsyncResult ar) => _sendIsComplete = ar.IsCompleted;
private void Disconnect()
{
try { _networkStream?.Close(); _tcpClient?.Close(); } catch { }
}
private void SetError(string msg)
{
_errorMessage = msg;
_logs += msg + CRLF + "【郵件處理動作中止】" + CRLF;
Disconnect();
throw new ApplicationException(msg);
}
private string ConvertToBase64(string str, string encoding)
{
byte[] bytes = Encoding.GetEncoding(encoding).GetBytes(str);
return Convert.ToBase64String(bytes);
}
private void WriteLog()
{
using (var fs = File.Exists(_logPath) ? new FileStream(_logPath, FileMode.Append, FileAccess.Write) : new FileStream(_logPath, FileMode.Create, FileAccess.Write))
{
byte[] logBytes = Encoding.GetEncoding("gb2312").GetBytes(CRLF + CRLF + _logs);
fs.Write(logBytes, 0, logBytes.Length);
}
}
#endregion
}
}
使用範例
var smtp = new KSN_Smtp("smtp.gmail.com", 587)
{
UserID = "your@gmail.com",
PassWord = "your_password",
SmtpValidateType = SmtpValidateTypes.Login,
LogPath = @"c:\smtp_log.txt"
};
var mail = new MailMessage
{
Sender = "your@gmail.com",
Subject = "測試郵件",
MailType = MailTypes.Html,
MailEncoding = MailEncodings.UTF8,
MailBody = Encoding.UTF8.GetBytes("<h1>Hello World</h1>")
};
mail.Receivers.Add("recipient@example.com");
bool success = smtp.SendMail(mail);
Console.WriteLine(success ? "發送成功" : "發送失敗: " + smtp.ErrorMessage);
特色功能
- 支援四種 SMTP 驗證機制
- 完整 MIME 組裝:純文字、HTML、附件、混合模式
- Base64 編碼支援多種字元集
- 詳細通訊記錄與錯誤碼對照
- 非同步資料發送支援
- 自動記錄除錯日誌