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

C# 自製 ESMTP 郵件發送元件:支援驗證、附件、HTML 郵件完整原始碼

     編輯:關於C語言

前言:為什麼不直接用 SmtpClient?

.NET 內建 System.Net.Mail.SmtpClient 雖然方便,但在某些舊版框架、特殊伺服器需求、或需深度控制 MIME 結構(如自定義 Boundary、優先序、多重替代內容)時,自行實作 SMTP 協定能獲得最大彈性。本文提供一個 零依賴、同步阻塞、支援 ESMTP AUTH LOGIN、多附件、HTML/Plain Text 替代內容 的 MailSender 元件完整原始碼,並解析關鍵協定細節。

類別架構


namespace JcPersonal.Utility
{
    public class MailSender
    {
        // 屬性:Server, Port, UserName, PassWord, From, To, FromName, ToName,
        // Subject, Body, HtmlBody, IsHtml, LanguageEncoding, MailEncoding, Priority
        // 集合:Attachments (IList)

        public void SendMail() { /* 核心流程 */ }

        // 內部方法:WriteString, ReadString, Command, ToBase64
    }

    public struct AttachmentInfo
    {
        public string FileName { get; set; }
        public string Bytes { get; set; } // Base64 編碼內容
        // 多重建構子:路徑、Stream、byte[]
    }
}

SMTP 協定流程圖解

  1. TCP 連線:TcpClient(server, port),預設 25 埠。
  2. 讀取歡迎横幅:確認 220 代碼。
  3. EHLO:宣告支援 ESMTP,期待 250。
  4. AUTH LOGIN(若有帳密):
    1. 發送 AUTH LOGIN → 334
    2. 發送 Base64(UserName) → 334
    3. 發送 Base64(PassWord) → 235
  5. MAIL FROM: 寄件人 → 250
  6. RCPT TO: 收件人 → 250
  7. DATA:進入資料模式 → 354
  8. 郵件標頭:Date、From、To、Subject、MIME-Version、Content-Type (multipart/mixed)、Priority、Encoding 等。
  9. 郵件主體:採雙層 Boundary 結構
    • --unique-boundary-1 (外層 mixed)
    • --unique-boundary-2 (內層 alternative: text/plain + text/html)
    • 文字內容
    • --unique-boundary-2-- 結束 alternative
    • 附件區塊(每個附件一組 --unique-boundary-1 + 標頭 + Base64 內容)
    • --unique-boundary-1-- 結束 mixed
  10. 單獨一行 . 結束 DATA → 250
  11. QUIT 關閉連線。

關鍵實作細節

1. 發送指令並驗證回應碼


protected bool Command(NetworkStream ns, string cmd, string expectedCode)
{
    WriteString(ns, cmd);
    string resp = ReadString(ns);
    return resp.IndexOf(expectedCode) != -1;
}

2. 寫入資料流:自動分塊 (75 位元組)


protected void WriteString(NetworkStream ns, string str)
{
    str += "\r\n";
    byte[] buf = Encoding.GetEncoding(LanguageEncoding).GetBytes(str);
    const int chunk = 75;
    for (int i = 0; i < buf.Length; i += chunk)
        ns.Write(buf, i, Math.Min(chunk, buf.Length - i));
}

3. 附件編碼:Base64 + Content-Disposition


foreach (AttachmentInfo att in Attachments)
{
    WriteString(ns, "--unique-boundary-1");
    WriteString(ns, $"Content-Type: application/octet-stream; name=\"{att.FileName}\"");
    WriteString(ns, "Content-Transfer-Encoding: base64");
    WriteString(ns, $"Content-Disposition: attachment; filename=\"{att.FileName}\"");
    WriteString(ns, "");
    WriteString(ns, att.Bytes); // 已為 Base64 字串
    WriteString(ns, "");
}

4. AttachmentInfo 多來源建構


public AttachmentInfo(string path) // 從檔案
{
    FileName = Path.GetFileName(path);
    using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
    {
        byte[] raw = new byte[fs.Length];
        fs.Read(raw, 0, raw.Length);
        Bytes = Convert.ToBase64String(raw);
    }
}

public AttachmentInfo(string name, Stream stream) // 從記憶體流
{
    FileName = Path.GetFileName(name);
    byte[] raw = new byte[stream.Length];
    stream.Read(raw, 0, raw.Length);
    Bytes = Convert.ToBase64String(raw);
}

public AttachmentInfo(string name, byte[] raw) // 從位元組陣列
{
    FileName = Path.GetFileName(name);
    Bytes = Convert.ToBase64String(raw);
}

使用範例


var mail = new MailSender
{
    Server = "smtp.gmail.com",
    Port = 587, // 或 25, 465 (SSL 需自行在 TcpClient 上包裝 SslStream)
    UserName = "your@gmail.com",
    PassWord = "app-password",
    From = "your@gmail.com",
    FromName = "您的名字",
    To = "target@example.com",
    ToName = "收件人",
    Subject = "測試 ESMTP 元件",
    Body = "這是純文字內容",
    HtmlBody = "

這是 HTML 內容

支援 粗體。

", IsHtml = true, LanguageEncoding = "UTF-8", Priority = 1 // 高優先序 }; mail.Attachments.Add(new MailSender.AttachmentInfo(@"C:\report.pdf")); mail.Attachments.Add(new MailSender.AttachmentInfo("logo.png", File.OpenRead(@"C:\logo.png"))); try { mail.SendMail(); Console.WriteLine("發送成功"); } catch (Exception ex) { Console.WriteLine($"失敗: {ex.Message}"); }

已知限制與擴充方向

限制解決方案
同步阻塞改為 async/await + NetworkStream.ReadAsync/WriteAsync
無 SSL/TLS (STARTTLS)在 EHLO 後檢查 250-STARTTLS,啟動 SslStream 升級連線
單一收件人To 屬性改為 List,RCPT TO 迴圈發送
無 CC/BCC新增 Cc/Bcc 集合,標頭加入 Cc:,RCPT 階段同步發送
大附件記憶體壓力附件改為串流式分塊 Base64 編碼寫入,避免一次載入完整 byte[]
錯誤處理過於簡略拋出自訂 SmtpException 包含狀態碼、伺服器回應、階段資訊

原始碼檔案結構

  • MailSender.cs:主類別 + AttachmentInfo 結構。
  • 無額外依賴,可直接拖入專案(.NET Framework / .NET Core / .NET 5+ 皆可編譯)。
  • 若專案已引用 System.Net.Mail,建議以介面 IMailSender 抽象,雙實作並存,便於單元測試 Mock。

這個元件雖然樸實,但完整展示了 SMTP/ESMTP 協定核心流程、MIME 多部分結構組裝、Base64 編碼與網路串流操作,是理解「郵件如何送達」的最佳教學級程式碼,也是受限環境下無法引用高階庫時的實用備選方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved