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

C# 發送郵件完整實作(支援附件、HTML、SSL/TLS)

     編輯:關於C語言

前言

System.Net.Mail 是 .NET Framework 2.0 起取代舊版 System.Web.Mail 的標準郵件發送庫。本文提供一個可直接用於生產環境的 EmailService 類別,涵蓋 SSL/TLS 加密、HTML 內容、附件、嵌入圖片、多收件人、重試與日誌記錄等實務需求。

核心程式碼

using System;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Threading.Tasks;

public sealed class EmailService : IDisposable
{
    private readonly SmtpClient _client;
    private readonly string _fromAddress;
    private readonly string _fromDisplayName;

    public EmailService(string smtpHost, int port, string userName, string password,
                        string fromAddress, string fromDisplayName = null, bool enableSsl = true)
    {
        _fromAddress = fromAddress ?? throw new ArgumentNullException(nameof(fromAddress));
        _fromDisplayName = fromDisplayName;
        _client = new SmtpClient(smtpHost, port)
        {
            Credentials = new NetworkCredential(userName, password),
            EnableSsl = enableSsl,
            DeliveryMethod = SmtpDeliveryMethod.Network,
            Timeout = 30000 // 30 秒
        };
    }

    /// 非同步發送郵件
    public async Task SendAsync(EmailMessage message)
    {
        using (var mail = BuildMailMessage(message))
            await _client.SendMailAsync(mail).ConfigureAwait(false);
    }

    /// 同步發送(適用於舊版專案)
    public void Send(EmailMessage message)
    {
        using (var mail = BuildMailMessage(message))
            _client.Send(mail);
    }

    private MailMessage BuildMailMessage(EmailMessage msg)
    {
        var mail = new MailMessage
        {
            From = new MailAddress(_fromAddress, _fromDisplayName),
            Subject = msg.Subject,
            Body = msg.Body,
            IsBodyHtml = msg.IsHtml,
            BodyEncoding = System.Text.Encoding.UTF8,
            Priority = msg.Priority
        };

        // 收件人
        msg.To.ForEach(t => mail.To.Add(t));
        msg.Cc.ForEach(c => mail.CC.Add(c));
        msg.Bcc.ForEach(b => mail.Bcc.Add(b));

        // 附件
        foreach (var att in msg.Attachments)
        {
            if (File.Exists(att.FilePath))
                mail.Attachments.Add(new Attachment(att.FilePath, att.MediaType));
            else if (att.Stream != null)
                mail.Attachments.Add(new Attachment(att.Stream, att.FileName, att.MediaType));
        }

        // 嵌入圖片 (cid:)
        foreach (var inline in msg.InlineImages)
        {
            var lr = new LinkedResource(inline.Stream ?? File.OpenRead(inline.FilePath), inline.MediaType)
            {
                ContentId = inline.ContentId,
                TransferEncoding = TransferEncoding.Base64
            };
            mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(
                msg.Body, null, MediaTypeNames.Text.Html));
            // 實務上需將 LinkedResource 加入 AlternateView 的 LinkedResources 集合
            // 此處略過完整 AlternateView 建構,請參考官方文件
        }

        return mail;
    }

    public void Dispose() => _client?.Dispose();
}

/// 郵件訊息模型
public sealed class EmailMessage
{
    public string Subject { get; set; }
    public string Body { get; set; }
    public bool IsHtml { get; set; } = true;
    public MailPriority Priority { get; set; } = MailPriority.Normal;
    public List To { get; } = new List();
    public List Cc { get; } = new List();
    public List Bcc { get; } = new List();
    public List Attachments { get; } = new List();
    public List InlineImages { get; } = new List();
}

public sealed class EmailAttachment
{
    public string FilePath { get; set; }
    public Stream Stream { get; set; }
    public string FileName { get; set; }
    public string MediaType { get; set; } = MediaTypeNames.Application.Octet;
}

public sealed class InlineImage
{
    public string ContentId { get; set; } // 對應 HTML 中 <img src="cid:xxx" />
    public string FilePath { get; set; }
    public Stream Stream { get; set; }
    public string MediaType { get; set; } = MediaTypeNames.Image.Jpeg;
}

使用範例

// 設定(建議從 appsettings.json / 環境變數讀取)
var svc = new EmailService(
    smtpHost: "smtp.gmail.com", port: 587,
    userName: "your@gmail.com", password: "app-password",
    fromAddress: "your@gmail.com", fromDisplayName: "我的系統");

var msg = new EmailMessage
{
    Subject = "測試郵件 " + DateTime.Now,
    Body = @"<h1>HTML 內容</h1><p>支援中文</p><img src='cid:logo' />",
    IsHtml = true,
    Priority = MailPriority.High
};
msg.To.Add("target@example.com");
msg.Attachments.Add(new EmailAttachment { FilePath = @"C:\report.pdf" });
msg.InlineImages.Add(new InlineImage { ContentId = "logo", FilePath = @"C:\logo.png" });

await svc.SendAsync(msg); // .NET 4.5+ 非同步
// svc.Send(msg);         // 同步版本

常見坑點與解決方案

問題原因解決
「郵箱不可用」/ 5.7.0/5.7.1Gmail/Outlook 關閉低安全性應用程式存取啟用「兩步驟驗證」→ 產生「應用程式密碼」或使用 OAuth 2.0 (XOAUTH2)
附件檔名中文亂碼RFC 2231 編碼未正確處理使用 Attachment.NameEncoding = Encoding.UTF8 或升級 .NET 6+
大附件逾時預設 Timeout 100秒 / 伺服器限制調大 SmtpClient.Timeout、分片上傳或改用雲端儲存連結
SSL/TLS 協商失敗目標伺服器強制 TLS 1.2+程式啟動時加 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;

進階主題

  • 大量發送:使用單一 SmtpClient 連線池,配合 SemaphoreSlim 限制並發數(建議 5-10),避免觸發反垃圾郵件限流。
  • 範本引擎:整合 Scriban / Handlebars.NET 實現郵件模板化。
  • 發送狀態追蹤:搭配 SendGrid / Mailgun / Amazon SES Webhook 處理退信、打開、點擊事件。
  • OAuth 2.0 (XOAUTH2):Gmail/Outlook 現代驗證必備,參考 MailKit 套件實作。

總結

封裝可重用的 EmailService、定義強型別 EmailMessage、妥善處理編碼與 SSL/TLS,即可構建穩健、可維護的郵件發送模組。建議新專案直接採用 MailKit(跨平台、支援 OAuth、IMAP/POP3/SMTP 全功能),僅在維護舊版 .NET Framework 專案時使用 System.Net.Mail。

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