.NET 發送郵件歷經三代演進:System.Web.Mail (1.x) → System.Net.Mail.SmtpClient (2.0+) → MailKit (跨平台現代)。本文詳解框架內置兩種高級用法(認證、SSL、異步)、CDO COM 組件方案、Socket 底層協議實現,並給出生產環境選型建議。
<system.net>
<mailSettings>
<smtp deliveryMethod="Network" from="noreply@example.com">
<network host="smtp.example.com" port="587"
userName="noreply@example.com" password="secret"
enableSsl="true" />
</smtp>
</mailSettings>
</system.net>
public interface IMailService
{
Task SendAsync(string to, string subject, string body, bool isHtml = true);
Task SendAsync(MailMessage message);
}
public class SmtpMailService : IMailService
{
private readonly SmtpClient _client;
public SmtpMailService()
{
_client = new SmtpClient(); // 自動讀取 web.config
}
public async Task SendAsync(string to, string subject, string body, bool isHtml = true)
{
using (var msg = new MailMessage
{
From = new MailAddress(_client.Host.Contains("localhost") ? "test@local" : "noreply@example.com"),
Subject = subject,
Body = body,
IsBodyHtml = isHtml
})
{
msg.To.Add(to);
await _client.SendMailAsync(msg);
}
}
public async Task SendAsync(MailMessage message) => await _client.SendMailAsync(message);
}
public async Task SendWithAttachmentAsync(string to, string subject, string body,
IEnumerable attachments, IEnumerable inlineImages = null)
{
using (var msg = new MailMessage { Subject = subject, Body = body, IsBodyHtml = true })
{
msg.To.Add(to);
foreach (var att in attachments) msg.Attachments.Add(att);
if (inlineImages != null)
{
var view = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
foreach (var img in inlineImages) view.LinkedResources.Add(img);
msg.AlternateViews.Add(view);
}
await _client.SendMailAsync(msg);
}
}
適用於需精細控制 SMTP 會話、NTLM 認證、舊系統遷移場景。
// 需引用 COM: "Microsoft CDO for Windows 2000 Library" (cdosys.dll)
public void SendViaCdo(string to, string subject, string htmlBody)
{
var msg = new CDO.Message();
msg.From = "sender@example.com";
msg.To = to;
msg.Subject = subject;
msg.HTMLBody = htmlBody;
var cfg = msg.Configuration;
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/sendusing"].Value = 2; // cdoSendUsingPort
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/smtpserver"].Value = "smtp.example.com";
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/smtpserverport"].Value = 587;
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/smtpauthenticate"].Value = 1; // cdoBasic
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/sendusername"].Value = "user";
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/sendpassword"].Value = "pwd";
cfg.Fields["https://schemas.microsoft.com/cdo/configuration/smtpusessl"].Value = true;
cfg.Fields.Update();
msg.Send();
}
缺點:依賴 COM、非托管、難調試、不支持 .NET Core/跨平台。
public class RawSmtpClient
{
private readonly string _host;
private readonly int _port;
private readonly string _user;
private readonly string _pass;
public RawSmtpClient(string host, int port, string user, string pass)
{
_host = host; _port = port; _user = user; _pass = pass;
}
public void Send(string from, string to, string subject, string body)
{
using (var tcp = new TcpClient(_host, _port))
using (var stream = tcp.GetStream())
using (var reader = new StreamReader(stream, Encoding.ASCII))
using (var writer = new StreamWriter(stream, Encoding.ASCII) { AutoFlush = true })
{
ReadResponse(reader); // 220
WriteCommand(writer, $"EHLO {Dns.GetHostName()}");
ReadResponse(reader); // 250
// AUTH LOGIN
WriteCommand(writer, "AUTH LOGIN");
ReadResponse(reader); // 334 VXNlcm5hbWU6
WriteCommand(writer, Convert.ToBase64String(Encoding.ASCII.GetBytes(_user)));
ReadResponse(reader); // 334 UGFzc3dvcmQ6
WriteCommand(writer, Convert.ToBase64String(Encoding.ASCII.GetBytes(_pass)));
ReadResponse(reader); // 235
WriteCommand(writer, $"MAIL FROM:<{from}>");
ReadResponse(reader); // 250
WriteCommand(writer, $"RCPT TO:<{to}>");
ReadResponse(reader); // 250
WriteCommand(writer, "DATA");
ReadResponse(reader); // 354
writer.WriteLine($"From: {from}");
writer.WriteLine($"To: {to}");
writer.WriteLine($"Subject: {subject}");
writer.WriteLine("MIME-Version: 1.0");
writer.WriteLine("Content-Type: text/html; charset=utf-8");
writer.WriteLine();
writer.WriteLine(body);
writer.WriteLine(".");
ReadResponse(reader); // 250
WriteCommand(writer, "QUIT");
ReadResponse(reader); // 221
}
}
private void WriteCommand(StreamWriter w, string cmd) => w.WriteLine(cmd);
private void ReadResponse(StreamReader r)
{
string line = r.ReadLine();
if (!line.StartsWith("2") && !line.StartsWith("3"))
throw new Exception($"SMTP Error: {line}");
}
}
| 維度 | SmtpClient | CDO | Socket |
|---|---|---|---|
| 易用性 | ★★★★★ | ★★☆☆☆ | ★☆☆☆☆ |
| 功能完整度 | ★★★★☆ | ★★★★☆ | ★★☆☆☆ (需自行擴展) |
| 跨平台 | ★★★☆☆ (.NET Core 支持) | ☆☆☆☆☆ (僅 Windows) | ★★★★★ |
| 性能 | 高 (連接池) | 中 (COM 開銷) | 低 (無連接復用) |
| 維護性 | 最佳 | 差 (COM 部署地獄) | 極差 (協議細節多) |
| 適用場景 | 95% 普通業務 | 舊系統、特殊認證 | 教學、嵌入式、無框架環境 |
IMailService,DI 注入 SmtpMailService,便於測試與替換。常規業務直接用 SmtpClient + 配置文件 + 異步隊列即可滿足 99% 需求。CDO 僅作為遷移過渡方案。Socket 實現僅供協議學習。新架構請直接拥抱 MailKit,獲得最佳跨平台體驗與功能完整度。