# 邮件 (20_Email)

最后更新: 2024-09-20


# 📚 概述

DarkM 框架的邮件模块提供了统一的邮件发送和接收功能。支持多种邮件协议(SMTP、POP3、IMAP)和多种实现(MailKit、OpenPop.NET),实现邮件发送、接收、附件处理等功能。

源代码位置: DarkM/src/Framework/Email


# 🏗️ 模块架构

# 项目结构

Email/
├── Email.Abstractions/                # 抽象层(接口、模型、配置)
│   ├── IEmailSender.cs                # 邮件发送接口
│   ├── IEmailReciver.cs               # 邮件接收接口
│   ├── EmailConfig.cs                 # 邮件配置类
│   ├── EmailProvider.cs               # 邮件提供器枚举
│   ├── EmailMessage.cs                # 邮件消息
│   ├── EmailBody.cs                   # 邮件正文
│   ├── EmailAddress.cs                # 邮件地址
│   ├── EmailAttachment.cs             # 邮件附件
│   ├── EmailServer.cs                 # 邮件服务器配置
│   └── Pop3Server.cs                  # POP3 服务器配置
│
├── Email.Integration/                 # 集成层(DI 注册)
│   └── ServiceCollectionExtensions.cs  # DI 扩展
│
├── Email.MailKit/                     # MailKit 实现
│   ├── MailKitSender.cs               # MailKit 发送器
│   └── IMapEmailReciver.cs            # IMAP 接收器
│
├── Email.MailKitOffice365/            # Office365 实现
│   ├── Office365Oauth2EmailReciver.cs  # OAuth2 接收器
│   └── EmailUtils.cs                  # 工具类
│
├── Email.Reciver/                     # 通用接收器
│   ├── ImapEmailReciver.cs            # IMAP 接收器
│   └── Pop3EmailReciver.cs            # POP3 接收器
│
├── Email.SmtpClient/                  # System.Net.Mail 实现
│   └── SmtpEmailSender.cs             # SMTP 发送器
│
└── Email.SystemMail/                  # 系统邮件
    └── SystemMailSender.cs            # 系统邮件发送器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 🔧 核心接口

# IEmailSender(邮件发送)

/// <summary>
/// 邮件发送接口
/// </summary>
public interface IEmailSender
{
    /// <summary>
    /// 发送邮件
    /// </summary>
    /// <param name="mailTo">收件人列表</param>
    /// <param name="mailCc">抄送列表</param>
    /// <param name="mailBc">密送列表</param>
    /// <param name="emailBody">邮件正文</param>
    /// <param name="fileAttachments">附件列表</param>
    /// <param name="emailFrom">发件人</param>
    /// <param name="emailServer">邮件服务器</param>
    Task Send(
        IList<EmailAddress> mailTo,
        IList<EmailAddress> mailCc,
        IList<EmailAddress> mailBc,
        EmailBody emailBody,
        IList<EmailAttachment> fileAttachments,
        EmailAddress emailFrom,
        EmailServer emailServer);

    /// <summary>
    /// 直接发送邮件(简化版)
    /// </summary>
    Task Send(
        string mailTo,
        string mailCc,
        string mailBc,
        string subject,
        string body,
        Encoding bodyEncoding,
        bool isBodyHtml,
        string fileAttachments,
        string fromAddress,
        string fromAddressDisplay,
        string host,
        int hostPort,
        string userAddress,
        string userPassword,
        bool useSSL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# EmailProvider(邮件提供器)

[Description("Email 提供器")]
public enum EmailProvider
{
    /// <summary>
    /// MailKit(推荐)
    /// </summary>
    [Description("MailKit")]
    MailKit,

    /// <summary>
    /// OpenPop.NET
    /// </summary>
    [Description("OpenPop.NET")]
    OpenPopNET,

    /// <summary>
    /// 自定义邮件服务
    /// </summary>
    [Description("自定义")]
    Custome
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# EmailConfig(邮件配置)

public class EmailConfig
{
    /// <summary>
    /// 提供器
    /// </summary>
    public EmailProvider Provider { get; set; } = EmailProvider.MailKit;

    /// <summary>
    /// MailKit 配置
    /// </summary>
    public MailKitConfig MailKt { get; set; } = new MailKitConfig();
}

public class MailKitConfig : EmailServer
{
    // 继承 EmailServer 所有属性
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# EmailMessage(邮件消息)

public class EmailMessage
{
    /// <summary>
    /// 邮件 ID
    /// </summary>
    public string MessageId { get; set; }

    /// <summary>
    /// 主题 ID
    /// </summary>
    public string TopicId { get; set; }

    /// <summary>
    /// 发件人
    /// </summary>
    public EmailAddress From { get; set; }

    /// <summary>
    /// 邮件正文
    /// </summary>
    public EmailBody Body { get; set; }

    /// <summary>
    /// 发送时间
    /// </summary>
    public DateTime SendTime { get; set; }

    /// <summary>
    /// 收件人列表
    /// </summary>
    public IList<EmailAddress> To { get; set; }

    /// <summary>
    /// 抄送列表
    /// </summary>
    public IList<EmailAddress> Cc { get; set; }

    /// <summary>
    /// 回复地址
    /// </summary>
    public IList<EmailAddress> ReplayTo { get; set; }

    /// <summary>
    /// 附件列表
    /// </summary>
    public IList<EmailAttachment> Attachment { get; set; }

    /// <summary>
    /// 引用邮件 ID 列表
    /// </summary>
    public IList<string> References { get; set; }

    /// <summary>
    /// 回复邮件 ID 列表
    /// </summary>
    public IList<string> InReplyTo { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# EmailAddress(邮件地址)

public class EmailAddress
{
    /// <summary>
    /// 邮箱地址
    /// </summary>
    public string Address { get; set; }

    /// <summary>
    /// 显示名称
    /// </summary>
    public string DisplayName { get; set; }

    /// <summary>
    /// 显示名称编码
    /// </summary>
    public Encoding DisplayNameEncoding { get; set; }

    /// <summary>
    /// 地址 Key ID
    /// </summary>
    public string AddressKeyId { get; set; }

    // 构造函数
    public EmailAddress(string address);
    public EmailAddress(string address, string displayName);
    public EmailAddress(string address, string displayName, string addressKeyId);
    public EmailAddress(MailAddress mailAddress, Encoding displayNameEncoding);

    // 转换为 MailAddress
    public MailAddress ToMailAddress();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# EmailBody(邮件正文)

public class EmailBody
{
    /// <summary>
    /// 邮件主题
    /// </summary>
    public string Subject { get; set; }

    /// <summary>
    /// 邮件内容
    /// </summary>
    public string Context { get; set; }

    /// <summary>
    /// 编码格式
    /// </summary>
    public Encoding Encoding { get; set; }

    /// <summary>
    /// 是否 HTML 格式
    /// </summary>
    public bool IsBodyHtml { get; set; }

    /// <summary>
    /// 优先级
    /// </summary>
    public MailPriority Priority { get; set; }

    /// <summary>
    /// 主题 ID
    /// </summary>
    public string TopicId { get; set; }

    // 构造函数
    public EmailBody(string subject, string body);
    public EmailBody(string subject, string body, bool isBodyHtml);
    public EmailBody(string subject, string body, Encoding encoding);
    public EmailBody(string subject, string body, Encoding encoding, bool isBodyHtml);
    public EmailBody(string subject, string body, Encoding encoding, bool isBodyHtml, MailPriority priority);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# EmailAttachment(邮件附件)

public class EmailAttachment
{
    /// <summary>
    /// 内容 ID(用于内嵌图片)
    /// </summary>
    public string ContentId { get; set; }

    /// <summary>
    /// 附件类型(MIME 类型)
    /// </summary>
    public string ContentType { get; set; }

    /// <summary>
    /// 文件名称
    /// </summary>
    public string FileName { get; set; }

    /// <summary>
    /// 编码方式
    /// </summary>
    public AttachmentContentEncoding ContentTransferEncoding { get; set; }

    /// <summary>
    /// 文件数据(字节数组)
    /// </summary>
    public byte[] Data { get; set; }

    /// <summary>
    /// 文件数据流
    /// </summary>
    public Stream Stream { get; set; }
}

public enum AttachmentContentEncoding
{
    Default = 0,
    Binary = 3,
    Base64 = 4
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# EmailServer(邮件服务器)

public class EmailServer
{
    /// <summary>
    /// 邮件服务器地址
    /// </summary>
    public string Server { get; set; }

    /// <summary>
    /// 邮件服务器端口
    /// </summary>
    public int Port { get; set; }

    /// <summary>
    /// 是否使用 SSL
    /// </summary>
    public bool Ssl { get; set; }

    /// <summary>
    /// 发送邮件显示名
    /// </summary>
    public string DisplayName { get; set; }

    /// <summary>
    /// 发送邮件地址
    /// </summary>
    public string UserAddress { get; set; }

    /// <summary>
    /// 发送账号密码
    /// </summary>
    public string Password { get; set; }

    // 构造函数
    public EmailServer();
    public EmailServer(string server);
    public EmailServer(string server, int port);
    public EmailServer(string server, int port, string userName, string password);
    public EmailServer(string server, int port, string userName, string password, bool ssl, string displayName = null);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 🏗️ 依赖注入

# 自动注册机制

// Email.Integration/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// 添加邮箱发送服务
    /// </summary>
    public static IServiceCollection AddEmail(
        this IServiceCollection services, 
        IConfiguration cfg)
    {
        var providerName = string.Empty;
        var list = EnumExtensions.ToResult<EmailProvider>(false, true);

        if (list != null && list.Count > 0)
        {
            foreach (var one in list)
            {
                // 加载所有邮件实现程序集
                var assembly = AssemblyHelper.LoadByNameEndString(
                    $".Lib.Email.{one.Value}");
                
                if (assembly != null)
                {
                    // 注册 IEmailSender 实现
                    var handlerType = assembly.GetTypes()
                        .FirstOrDefault(m => typeof(IEmailSender).IsAssignableFrom(m));
                    
                    if (handlerType != null)
                    {
                        services.AddSingleton(typeof(IEmailSender), handlerType);
                    }
                }
            }
        }

        return services;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 💡 使用示例

# 1. 配置邮件模块

// appsettings.json
{
  "Email": {
    "Provider": "MailKit",
    "MailKt": {
      "Server": "smtp.example.com",
      "Port": 587,
      "Ssl": true,
      "UserAddress": "noreply@example.com",
      "Password": "your-password",
      "DisplayName": "系统通知"
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2. 注册服务

// Program.cs 或 Startup.cs
var builder = WebApplication.CreateBuilder(args);

// 添加邮件服务
builder.Services.AddEmail(builder.Configuration);

var app = builder.Build();
1
2
3
4
5
6
7

# 3. 发送简单邮件

public class EmailService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;
    private readonly ILogger<EmailService> _logger;

    public EmailService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig,
        ILogger<EmailService> logger)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
        _logger = logger;
    }

    /// <summary>
    /// 发送简单邮件
    /// </summary>
    public async Task SendSimpleEmailAsync(
        string to,
        string subject,
        string body)
    {
        await _emailSender.Send(
            mailTo: to,
            mailCc: "",
            mailBc: "",
            subject: subject,
            body: body,
            bodyEncoding: Encoding.UTF8,
            isBodyHtml: false,
            fileAttachments: "",
            fromAddress: _emailServer.UserAddress,
            fromAddressDisplay: _emailServer.DisplayName,
            host: _emailServer.Server,
            hostPort: _emailServer.Port,
            userAddress: _emailServer.UserAddress,
            userPassword: _emailServer.Password,
            useSSL: _emailServer.Ssl);

        _logger.LogInformation("邮件已发送:{To}", to);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 4. 发送 HTML 邮件

public class HtmlEmailService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;

    public HtmlEmailService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
    }

    /// <summary>
    /// 发送 HTML 邮件
    /// </summary>
    public async Task SendHtmlEmailAsync(
        string to,
        string subject,
        string htmlBody)
    {
        var from = new EmailAddress(
            _emailServer.UserAddress,
            _emailServer.DisplayName);

        var toList = new List<EmailAddress>
        {
            new EmailAddress(to)
        };

        var body = new EmailBody(
            subject,
            htmlBody,
            Encoding.UTF8,
            true); // isBodyHtml = true

        await _emailSender.Send(
            mailTo: toList,
            mailCc: new List<EmailAddress>(),
            mailBc: new List<EmailAddress>(),
            emailBody: body,
            fileAttachments: new List<EmailAttachment>(),
            emailFrom: from,
            emailServer: _emailServer);
    }

    /// <summary>
    /// 发送带内嵌图片的 HTML 邮件
    /// </summary>
    public async Task SendEmailWithEmbeddedImageAsync(
        string to,
        string subject,
        string htmlBody,
        string imagePath)
    {
        var from = new EmailAddress(
            _emailServer.UserAddress,
            _emailServer.DisplayName);

        var toList = new List<EmailAddress>
        {
            new EmailAddress(to)
        };

        // 创建内嵌图片附件
        var imageContentId = "image001";
        var imageAttachment = new EmailAttachment
        {
            ContentId = imageContentId,
            FileName = Path.GetFileName(imagePath),
            ContentType = "image/png",
            Data = await File.ReadAllBytesAsync(imagePath)
        };

        // HTML 中引用图片:<img src="cid:image001" />
        var body = new EmailBody(
            subject,
            htmlBody.Replace("{IMAGE}", $"<img src=\"cid:{imageContentId}\" />"),
            Encoding.UTF8,
            true);

        await _emailSender.Send(
            mailTo: toList,
            mailCc: new List<EmailAddress>(),
            mailBc: new List<EmailAddress>(),
            emailBody: body,
            fileAttachments: new List<EmailAttachment> { imageAttachment },
            emailFrom: from,
            emailServer: _emailServer);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

# 5. 发送带附件的邮件

public class AttachmentEmailService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;

    public AttachmentEmailService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
    }

    /// <summary>
    /// 发送带附件的邮件
    /// </summary>
    public async Task SendEmailWithAttachmentsAsync(
        string to,
        string subject,
        string body,
        List<string> attachmentPaths)
    {
        var from = new EmailAddress(
            _emailServer.UserAddress,
            _emailServer.DisplayName);

        var toList = new List<EmailAddress>
        {
            new EmailAddress(to)
        };

        var emailBody = new EmailBody(
            subject,
            body,
            Encoding.UTF8,
            false);

        // 创建附件列表
        var attachments = new List<EmailAttachment>();
        foreach (var path in attachmentPaths)
        {
            var attachment = new EmailAttachment
            {
                FileName = Path.GetFileName(path),
                ContentType = GetContentType(path),
                Data = await File.ReadAllBytesAsync(path)
            };
            attachments.Add(attachment);
        }

        await _emailSender.Send(
            mailTo: toList,
            mailCc: new List<EmailAddress>(),
            mailBc: new List<EmailAddress>(),
            emailBody: emailBody,
            fileAttachments: attachments,
            emailFrom: from,
            emailServer: _emailServer);
    }

    private string GetContentType(string filePath)
    {
        var extension = Path.GetExtension(filePath).ToLower();
        return extension switch
        {
            ".pdf" => "application/pdf",
            ".doc" => "application/msword",
            ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            ".xls" => "application/vnd.ms-excel",
            ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".png" => "image/png",
            ".zip" => "application/zip",
            _ => "application/octet-stream"
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# 6. 群发邮件

public class BulkEmailService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;
    private readonly ILogger<BulkEmailService> _logger;

    public BulkEmailService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig,
        ILogger<BulkEmailService> logger)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
        _logger = logger;
    }

    /// <summary>
    /// 群发邮件
    /// </summary>
    public async Task SendBulkEmailAsync(
        List<string> recipients,
        string subject,
        string body,
        bool isHtml = false)
    {
        var from = new EmailAddress(
            _emailServer.UserAddress,
            _emailServer.DisplayName);

        var toList = recipients
            .Select(r => new EmailAddress(r))
            .ToList();

        var emailBody = new EmailBody(
            subject,
            body,
            Encoding.UTF8,
            isHtml);

        try
        {
            await _emailSender.Send(
                mailTo: toList,
                mailCc: new List<EmailAddress>(),
                mailBc: new List<EmailAddress>(),
                emailBody: emailBody,
                fileAttachments: new List<EmailAttachment>(),
                emailFrom: from,
                emailServer: _emailServer);

            _logger.LogInformation(
                "群发邮件成功,收件人数量:{Count}",
                recipients.Count);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "群发邮件失败");
            throw;
        }
    }

    /// <summary>
    /// 分批群发邮件(避免被标记为垃圾邮件)
    /// </summary>
    public async Task SendBulkEmailInBatchesAsync(
        List<string> recipients,
        string subject,
        string body,
        int batchSize = 50,
        int delayMs = 1000)
    {
        var batches = recipients
            .Select((r, i) => new { Recipient = r, Index = i })
            .GroupBy(x => x.Index / batchSize)
            .Select(g => g.Select(x => x.Recipient).ToList())
            .ToList();

        _logger.LogInformation(
            "开始分批群发,总人数:{Total}, 批次数:{Batches}",
            recipients.Count,
            batches.Count);

        foreach (var batch in batches)
        {
            await SendBulkEmailAsync(batch, subject, body);
            
            // 批次间延迟,避免触发反垃圾邮件机制
            if (batch != batches.Last())
            {
                await Task.Delay(delayMs);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

# 7. 邮件控制器

[Route("api/[controller]")]
[ApiController]
public class EmailController : ModuleController
{
    private readonly HtmlEmailService _htmlEmailService;
    private readonly AttachmentEmailService _attachmentEmailService;
    private readonly BulkEmailService _bulkEmailService;

    public EmailController(
        HtmlEmailService htmlEmailService,
        AttachmentEmailService attachmentEmailService,
        BulkEmailService bulkEmailService)
    {
        _htmlEmailService = htmlEmailService;
        _attachmentEmailService = attachmentEmailService;
        _bulkEmailService = bulkEmailService;
    }

    /// <summary>
    /// 发送 HTML 邮件
    /// </summary>
    [HttpPost("send-html")]
    public async Task<IResultModel> SendHtml(
        [FromBody] SendHtmlEmailRequest request)
    {
        try
        {
            await _htmlEmailService.SendHtmlEmailAsync(
                request.To,
                request.Subject,
                request.HtmlBody);

            return ResultModel.Success("邮件发送成功");
        }
        catch (Exception ex)
        {
            return ResultModel.Error($"邮件发送失败:{ex.Message}");
        }
    }

    /// <summary>
    /// 发送带附件的邮件
    /// </summary>
    [HttpPost("send-with-attachments")]
    public async Task<IResultModel> SendWithAttachments(
        [FromForm] string to,
        [FromForm] string subject,
        [FromForm] string body,
        [FromForm] List<IFormFile> attachments)
    {
        try
        {
            var attachmentPaths = new List<string>();

            // 保存上传的文件
            foreach (var file in attachments)
            {
                var tempPath = Path.Combine(Path.GetTempPath(), file.FileName);
                using var stream = new FileStream(tempPath, FileMode.Create);
                await file.CopyToAsync(stream);
                attachmentPaths.Add(tempPath);
            }

            await _attachmentEmailService.SendEmailWithAttachmentsAsync(
                to,
                subject,
                body,
                attachmentPaths);

            // 清理临时文件
            foreach (var path in attachmentPaths)
            {
                File.Delete(path);
            }

            return ResultModel.Success("邮件发送成功");
        }
        catch (Exception ex)
        {
            return ResultModel.Error($"邮件发送失败:{ex.Message}");
        }
    }

    /// <summary>
    /// 群发邮件
    /// </summary>
    [HttpPost("send-bulk")]
    public async Task<IResultModel> SendBulk(
        [FromBody] SendBulkEmailRequest request)
    {
        try
        {
            await _bulkEmailService.SendBulkEmailInBatchesAsync(
                request.Recipients,
                request.Subject,
                request.Body,
                request.BatchSize ?? 50,
                request.DelayMs ?? 1000);

            return ResultModel.Success($"邮件已发送,共 {request.Recipients.Count} 个收件人");
        }
        catch (Exception ex)
        {
            return ResultModel.Error($"邮件发送失败:{ex.Message}");
        }
    }
}

public class SendHtmlEmailRequest
{
    public string To { get; set; }
    public string Subject { get; set; }
    public string HtmlBody { get; set; }
}

public class SendBulkEmailRequest
{
    public List<string> Recipients { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public int? BatchSize { get; set; }
    public int? DelayMs { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

# 🔧 邮件提供器对比

# Email.MailKit(MailKit)

特点:

  • ✅ 跨平台(.NET Standard)
  • ✅ 支持 SMTP、POP3、IMAP
  • ✅ 支持 OAuth2 认证
  • ✅ 活跃的社区维护
  • ✅ 性能优秀
  • ⚠️ 需要学习新 API

适用场景:

  • 现代 .NET 应用
  • 需要接收邮件
  • 需要 IMAP 支持
  • 跨平台部署

NuGet 包:

Install-Package MailKit
1

# Email.SmtpClient(System.Net.Mail)

特点:

  • ✅ .NET Framework 内置
  • ✅ API 简单熟悉
  • ✅ 无需额外依赖
  • ⚠️ 仅支持 SMTP 发送
  • ⚠️ 不支持现代认证方式
  • ⚠️ .NET Core 中功能受限

适用场景:

  • 传统 .NET Framework 应用
  • 简单邮件发送
  • 不需要接收邮件

# Email.MailKitOffice365(Office365)

特点:

  • ✅ 支持 Office365 OAuth2 认证
  • ✅ 支持现代认证方式
  • ✅ 适合企业环境
  • ⚠️ 配置复杂
  • ⚠️ 需要 Azure AD 配置

适用场景:

  • Office365 企业邮箱
  • 需要 OAuth2 认证
  • 企业环境部署

# 💡 最佳实践

# 1. 邮件模板

public class EmailTemplateService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;

    public EmailTemplateService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
    }

    /// <summary>
    /// 发送欢迎邮件
    /// </summary>
    public async Task SendWelcomeEmailAsync(string to, string userName)
    {
        var subject = "欢迎加入!";
        var htmlBody = $@"
            <html>
            <body>
                <h1>欢迎 {userName}!</h1>
                <p>感谢您注册我们的服务。</p>
                <p>点击以下链接激活您的账户:</p>
                <a href='https://example.com/activate?user={userName}'>激活账户</a>
            </body>
            </html>";

        await _emailSender.Send(
            mailTo: new List<EmailAddress> { new EmailAddress(to) },
            mailCc: new List<EmailAddress>(),
            mailBc: new List<EmailAddress>(),
            emailBody: new EmailBody(subject, htmlBody, Encoding.UTF8, true),
            fileAttachments: new List<EmailAttachment>(),
            emailFrom: new EmailAddress(_emailServer.UserAddress, _emailServer.DisplayName),
            emailServer: _emailServer);
    }

    /// <summary>
    /// 发送密码重置邮件
    /// </summary>
    public async Task SendPasswordResetEmailAsync(
        string to, 
        string userName, 
        string resetToken)
    {
        var subject = "密码重置请求";
        var htmlBody = $@"
            <html>
            <body>
                <h1>密码重置</h1>
                <p>您好 {userName},</p>
                <p>您请求重置密码。点击以下链接设置新密码:</p>
                <a href='https://example.com/reset-password?token={resetToken}'>重置密码</a>
                <p>如果您没有请求重置密码,请忽略此邮件。</p>
            </body>
            </html>";

        await _emailSender.Send(
            mailTo: new List<EmailAddress> { new EmailAddress(to) },
            mailCc: new List<EmailAddress>(),
            mailBc: new List<EmailAddress>(),
            emailBody: new EmailBody(subject, htmlBody, Encoding.UTF8, true),
            fileAttachments: new List<EmailAttachment>(),
            emailFrom: new EmailAddress(_emailServer.UserAddress, _emailServer.DisplayName),
            emailServer: _emailServer);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 2. 邮件队列

public class EmailQueueService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger<EmailQueueService> _logger;

    public EmailQueueService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig,
        IBackgroundTaskQueue taskQueue,
        ILogger<EmailQueueService> logger)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
        _taskQueue = taskQueue;
        _logger = logger;
    }

    /// <summary>
    /// 将邮件加入队列(异步发送)
    /// </summary>
    public void QueueEmail(
        string to,
        string subject,
        string body,
        bool isHtml = false)
    {
        _taskQueue.QueueBackgroundWorkItem(async token =>
        {
            try
            {
                await _emailSender.Send(
                    mailTo: to,
                    mailCc: "",
                    mailBc: "",
                    subject: subject,
                    body: body,
                    bodyEncoding: Encoding.UTF8,
                    isBodyHtml: isHtml,
                    fileAttachments: "",
                    fromAddress: _emailServer.UserAddress,
                    fromAddressDisplay: _emailServer.DisplayName,
                    host: _emailServer.Server,
                    hostPort: _emailServer.Port,
                    userAddress: _emailServer.UserAddress,
                    userPassword: _emailServer.Password,
                    useSSL: _emailServer.Ssl);

                _logger.LogInformation("队列邮件发送成功:{To}", to);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "队列邮件发送失败:{To}", to);
            }
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 3. 邮件发送重试

public class RetryEmailService
{
    private readonly IEmailSender _emailSender;
    private readonly EmailServer _emailServer;
    private readonly ILogger<RetryEmailService> _logger;

    public RetryEmailService(
        IEmailSender emailSender,
        IOptions<EmailConfig> emailConfig,
        ILogger<RetryEmailService> logger)
    {
        _emailSender = emailSender;
        _emailServer = emailConfig.Value.MailKt;
        _logger = logger;
    }

    /// <summary>
    /// 带重试的邮件发送
    /// </summary>
    public async Task SendWithRetryAsync(
        string to,
        string subject,
        string body,
        int maxRetries = 3,
        int retryDelayMs = 5000)
    {
        for (int i = 0; i < maxRetries; i++)
        {
            try
            {
                await _emailSender.Send(
                    mailTo: to,
                    mailCc: "",
                    mailBc: "",
                    subject: subject,
                    body: body,
                    bodyEncoding: Encoding.UTF8,
                    isBodyHtml: false,
                    fileAttachments: "",
                    fromAddress: _emailServer.UserAddress,
                    fromAddressDisplay: _emailServer.DisplayName,
                    host: _emailServer.Server,
                    hostPort: _emailServer.Port,
                    userAddress: _emailServer.UserAddress,
                    userPassword: _emailServer.Password,
                    useSSL: _emailServer.Ssl);

                _logger.LogInformation("邮件发送成功:{To}", to);
                return;
            }
            catch (Exception ex) when (i < maxRetries - 1)
            {
                _logger.LogWarning(
                    ex,
                    "邮件发送失败,{Seconds}秒后重试(第 {Retry} 次):{To}",
                    retryDelayMs / 1000,
                    i + 1,
                    to);

                await Task.Delay(retryDelayMs);
            }
        }

        _logger.LogError("邮件发送失败,已达到最大重试次数:{To}", to);
        throw new EmailSendException($"邮件发送失败,已达到最大重试次数");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 🔍 常见问题

# Q1: SSL 连接失败?

检查清单:

  1. ✅ 确认端口是否正确(SSL 通常用 465 或 587)
  2. ✅ 确认 useSSL 参数是否设置为 true
  3. ✅ 确认防火墙是否允许出站连接
  4. ✅ 确认邮件服务器支持 SSL

常见端口:

  • SMTP: 25(非 SSL)、587(STARTTLS)、465(SSL)
  • POP3: 110(非 SSL)、995(SSL)
  • IMAP: 143(非 SSL)、993(SSL)

# Q2: 认证失败?

解决方案:

// 检查配置
{
  "Email": {
    "MailKt": {
      "Server": "smtp.example.com",
      "Port": 587,
      "Ssl": true,
      "UserAddress": "your-email@example.com",
      "Password": "your-password-or-app-password"
    }
  }
}

// 注意:某些邮件服务商需要使用"应用专用密码"
// 如 Gmail、Office365 需要开启两步验证后生成应用密码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Q3: 邮件被标记为垃圾邮件?

解决方案:

  1. ✅ 配置 SPF 记录
  2. ✅ 配置 DKIM 签名
  3. ✅ 配置 DMARC 策略
  4. ✅ 避免大量群发(使用分批发送)
  5. ✅ 提供退订链接
  6. ✅ 使用可信的邮件服务器

SPF 记录示例:

v=spf1 include:_spf.example.com ~all
1

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20