.Net Core使用mailkit收取和发送邮件

邮件服务是一般的系统都会拥有和需要的功能,但是对于.Net项目来说,邮件服务的创建和使用会较为的麻烦。.NET对于邮件功能提供了System.Net.Mail用于创建邮件服务,该基础服务提供邮件的基础操作,并且使用也较为的简单。对于真正将该功能使用于项目的人,就会慢慢发现其中的优缺点,甚至有些时候不能忍受其中的问题。在这里介绍一种微软用于替代System.Net.Mail的邮件服务组件MailKit和MimeKit,官网地址:http://www.mimekit.net/ GitHub地址:https://github.com/jstedfast/MimeKit

MailKit和MimeKit基础概述

MailKit组件是一个免费开源的邮箱类库,简单来说MailKit帮我们封装了有关邮箱的一些帮助类,提供方法让我们更容易使用邮箱的SMTP、POP3、IMAP等协议。该组件是一个跨平台的Email组件,该组件支持.Net Core 、.Net FrameWork、Xamarin.Android、Xamarin.iOS、Windows Phone等等平台。

MimeKit提供了一个MIME解析器,组件具备的解析特性灵活、性能高、很好的处理各种各样的破碎的MIME格式化。MimeKit的性能实际上与GMime相当。

该组件在安全性的还是比较高的,处理安全的方式较多,SASL认证、支持S / MIME v3.2、支持OpenPGP、支持DKIM签名等等方式。Mailkit组件可以通过CancellationToken取消对应的操作,CancellationToken传播应取消操作的通知,一个的CancellationToken使线程,线程池工作项目之间,或取消合作任务的对象。过实例化CancellationTokenSource对象来创建取消令牌,该对象管理从其CancellationTokenSource.Token属性检索的取消令牌。然后,将取消令牌传递到应该收到取消通知的任意数量的线程,任务或操作。令牌不能用于启动取消。
MailKit组件支持异步操作,在内部编写的有关I/O异步操作的类

创建基础邮件服务

介绍过MailKit和MimeKit组建的基础信息,接下来就介绍一下如何使用两个组件的基本功能,在这里我将基本操作做了一个简单的封装,一般的项目可以直接引用封装好的类,大家可以根据实际的情况对该组件进行扩展。

基础实体类

邮件实体类

用于保存邮件至数据库

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
public class MailBoxEntity : BaseEntity {
/// <summary>
/// 该邮件所属邮箱账号
/// </summary>
public int OwnerMailAccount { get; set; }
/// <summary>
/// 邮件主题
/// </summary>
public string Subject { get; set; }
/// <summary>
/// 邮件文本内容
/// </summary>
public string MailTextBody { get; set; } = "";
/// <summary>
/// 邮件html内容
/// </summary>
public string MailHtmlBody { get; set; } = "";
/// <summary>
/// 邮件内容类型
/// </summary>
public string MailBodyType { get; set; } = "html";
/// <summary>
/// 邮件附件文件路径
/// </summary>
public string MailFilePath { get; set; }
/// <summary>
/// 收件人地址
/// </summary>
public string Recipients { get; set; }
/// <summary>
/// 收件人名字
/// </summary>
public string RecipientsName { get; set; }
/// <summary>
/// 抄送
/// </summary>
public string Cc { get; set; }
/// <summary>
/// 密送
/// </summary>
public string Bcc { get; set; }
/// <summary>
/// 发件人
/// </summary>
public string Sender { get; set; } = "";
/// <summary>
/// 发件人地址
/// </summary>
public string SenderAddress { get; set; }
/// <summary>
/// 邮件类型
/// </summary>
public int MailType { get; set; }
/// <summary>
/// 收件/发件时间
/// </summary>
public string Date { get; set; }
/// <summary>
/// 邮件标识
/// 是否已读,是否回复,是否删除
/// </summary>
public bool Flag { get; set; }
/// <summary>
/// 是否已读
/// </summary>
public bool IsRead { get; set; }
/// <summary>
/// 是否已经回复
/// </summary>
public bool IsAnswered { get; set; }
/// <summary>
/// 邮件唯一标识
/// </summary>
public string MessageId { get; set; }
/// <summary>
/// 邮件唯一查询标识
/// </summary>
public uint? UniqueId { get; set; }
/// <summary>
/// 引用邮件唯一标识
/// </summary>
public string References { get; set; }
/// <summary>
/// 邮件所属邮箱服务器的文件夹标识
/// inbox(收件箱),
/// archive(档案箱),
/// drafts(草稿箱),
/// flagged(标记的),
/// junk(垃圾箱),
/// sent(发件箱),
/// trash(回收箱)
/// </summary>
public string FolderType { get; set; } = "";
/// <summary>
/// 文件夹名称
/// 一般用于区别Inbox文件夹下用户自定义的文件夹
/// </summary>
public string FolderName { get; set; } = "";
/// <summary>
/// 附件个数
/// </summary>
public int? AttaCount { get; set; }

[NotMapped]
public List<MailBoxEntity> MailBoxList { get; set; }
}

邮箱发送服务器配置

此表用于保存邮箱的账号信息及不同邮箱服务器的SMTP端口等信息

当然,这些信息可以拆分成两个表存储。

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
public class MailConfigEntity : BaseEntity {
/// <summary>
/// 邮箱显示名
/// </summary>
public string DisplayName { get; set; } = "";
/// <summary>
/// 发件SMTP服务器地址
/// </summary>
public string SmtpHost { get; set; } = "";
/// <summary>
/// 收件Imtp服务器地址
/// </summary>
public string ImapHost { get; set; } = "";
/// <summary>
/// 发件SMTP服务器端口
/// </summary>
public int? SmtpPort { get; set; }
/// <summary>
/// 收件Imtp服务器端口
/// </summary>
public int? ImapPort { get; set; }
/// <summary>
/// 是否启用SSL
/// </summary>
public bool IsSsl { get; set; } = true;
/// <summary>
/// 邮件编码
/// </summary>
public string MailEncoding { get; set; } = "UTF-8";
/// <summary>
/// 邮箱账号
/// </summary>
public string Account { get; set; }
/// <summary>
/// 邮箱密码/授权码
/// </summary>
public string Password { get; set; }
/// <summary>
/// 平台ID
/// </summary>
public int PlatId { get; set; }
/// <summary>
/// 更新到的日期
/// </summary>
public string UpdateTo { get; set; }
/// <summary>
/// 总发件数量
/// </summary>
public int SentCount { get; set; } = 0;
/// <summary>
/// 员工自定义的文件夹列表
/// </summary>
[NotMapped]
public List<MailFolderEntity> MailFolders { get; set; }
}

邮件发送结果信息

用于保存每一封邮件的发送状态,后续也可以关联其它业务表,与自动发送邮件任务配合,记录更多相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SendResultEntity : BaseEntity {
// [MaxLength (50)]
// public string OrderId { get; set; }
// [MaxLength (50)]
// public string ReviewId { get; set; }
public int MailId { get; set; }
/// <summary>
/// 结果信息
/// </summary>
public string ResultInformation { get; set; } = "发送成功!";
/// <summary>
/// 结果状态
/// </summary>
public bool ResultStatus { get; set; } = true;
}

邮件操作服务

配置基础信息

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
/// <summary>
/// 账户认证
/// </summary>
private void Authenticate (MailConfigEntity mailConfigEntity, SmtpClient client, SendResultEntity sendResultEntity) {
try {
client.Authenticate (mailConfigEntity.Account, mailConfigEntity.Password);
} catch (AuthenticationException ex) {
sendResultEntity.ResultInformation = $"无效的用户名或密码:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
} catch (SmtpCommandException ex) {
sendResultEntity.ResultInformation = $"尝试验证错误:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
} catch (SmtpProtocolException ex) {
sendResultEntity.ResultInformation = $"尝试验证时的协议错误:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
} catch (Exception ex) {
sendResultEntity.ResultInformation = $"账户认证错误:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
}
}
/// <summary>
/// 获取SMTP基础信息
/// </summary>
private MailServerInformationEntity SmtpClientBaseMessage (SmtpClient client) {
var mailServerInformation = new MailServerInformationEntity {
Authentication = client.Capabilities.HasFlag (SmtpCapabilities.Authentication),
BinaryMime = client.Capabilities.HasFlag (SmtpCapabilities.BinaryMime),
Dsn = client.Capabilities.HasFlag (SmtpCapabilities.Dsn),
EightBitMime = client.Capabilities.HasFlag (SmtpCapabilities.EightBitMime),
Size = client.MaxSize
};
return mailServerInformation;
}
/// <summary>
/// 连接服务器
/// </summary>
private void Connection (MailConfigEntity sendServerConfiguration, SmtpClient client, SendResultEntity sendResultEntity) {
try {
client.Connect (sendServerConfiguration.SmtpHost, sendServerConfiguration.SmtpPort.Value);
} catch (SmtpCommandException ex) {
sendResultEntity.ResultInformation = $"尝试连接时出错:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
} catch (SmtpProtocolException ex) {
sendResultEntity.ResultInformation = $"尝试连接时的协议错误:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
} catch (Exception ex) {
sendResultEntity.ResultInformation = $"服务器连接错误:{0}" + ex.Message;
sendResultEntity.ResultStatus = false;
}
}
/// <summary>
/// 设置发件人信息
/// </summary>
private SmtpClient SmtpClientInit (int accountId) {
var m = _mailConfigService.GetAsNoTracking ();
MailConfigEntity mailConfig = m.FirstOrDefault (a => a.FID == accountId);
SmtpClient client = new SmtpClient () {
ServerCertificateValidationCallback = (s, c, h, e) => true
};
var sendResultEntity = new SendResultEntity ();
Connection (mailConfig, client, sendResultEntity);
if (sendResultEntity.ResultStatus == false)
return null;
SmtpClientBaseMessage (client);
Authenticate (mailConfig, client, sendResultEntity);
return sendResultEntity.ResultStatus == false ? null : client;
}
/// <summary>
/// 设置收件人信息
/// </summary>
public ImapClient ImapClientInit (int AccountId) {
MailConfigEntity mailConfig = _mailConfigService.GetAsNoTracking ().FirstOrDefault (a => a.FID == AccountId);
ImapClient client = new ImapClient ();
client.Connect (mailConfig.ImapHost, mailConfig.ImapPort.Value,
SecureSocketOptions.SslOnConnect);
client.Authenticate (mailConfig.Account, mailConfig.Password);
#region 网易邮箱需要此语句,用于验证客户端身份
if (mailConfig.ImapHost == "imap.126.com" || mailConfig.ImapHost == "imap.163.com") {
var clientImplementation = new ImapImplementation {
Name = "MeSince",
Version = "2.0"
};
client.Identify (clientImplementation);
}
#endregion
return client;
}

发送邮件

Mailkit使用时会遇到“附件文件名不能为中文”和“附件文件名长度不能超过41字符”的Bug,这里我顺便参照网上的解决了

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/// <summary>
/// 组装邮件文本/附件邮件信息
/// </summary>
public MimeMessage AssemblyMailMessage (MailBoxEntity mailBoxEntity) {
if (mailBoxEntity == null) {
throw new ArgumentNullException (nameof (mailBoxEntity));
}
var message = new MimeMessage ();
//设置邮件基本信息
SetMailBaseMessage (message, mailBoxEntity);
var multipart = new Multipart ("mixed");
//插入文本消息
if (mailBoxEntity.MailHtmlBody.IsNotNullAndWhiteSpace ()) {
multipart.Add (new MultipartAlternative {
AssemblyMailTextMessage (mailBoxEntity.MailHtmlBody, mailBoxEntity.MailBodyType)
});
}
//插入附件信息
if (mailBoxEntity.MailFilePath.IsNotNullAndWhiteSpace ()) {
List<FileStream> list = new List<FileStream> ();
foreach (var path in mailBoxEntity.MailFilePath.Split ('|')) {
if (!File.Exists (path))
throw new FileNotFoundException ("文件未找到", path);
string[] contentTypeArr = MimeTypes.GetMimeType (path).Split ('/');
FileStream file = File.Open (path, FileMode.Open);
list.Add (file);
ContentType contentType = new ContentType (contentTypeArr[0], contentTypeArr[1]);
MimePart mimePart = new MimePart (contentType) {
Content = new MimeContent (file),
ContentDisposition = new ContentDisposition (ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Base64,
};
//解决文件名不能为中文
mimePart.ContentType.Parameters.Add ("GB18030", "name", Path.GetFileName (path));
mimePart.ContentDisposition.Parameters.Add ("GB18030", "filename", Path.GetFileName (path));
//解决文件名长度限制
foreach (var param in mimePart.ContentDisposition.Parameters)
param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
foreach (var param in mimePart.ContentType.Parameters)
param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
multipart.Add (mimePart);
}
}
//组合邮件内容
message.Body = multipart;
return message;
}

/// <summary>
/// 设置邮件基础信息
/// </summary>
public MimeMessage SetMailBaseMessage (MimeMessage minMessag, MailBoxEntity mailBoxEntity) {
if (minMessag == null)
throw new ArgumentNullException ();
if (mailBoxEntity == null)
throw new ArgumentNullException ();
//插入发件人
minMessag.From.Add (new MailboxAddress (mailBoxEntity.Sender, mailBoxEntity.SenderAddress));

//插入收件人
string[] _recipients = mailBoxEntity.Recipients.Split ("|");
foreach (var recipients in _recipients) {
minMessag.To.Add (new MailboxAddress (recipients.Trim ()));
}
if (mailBoxEntity.Cc.IsNotNullAndWhiteSpace ()) {
//插入抄送人
string[] _cc = mailBoxEntity.Cc.Split ("|");
foreach (var cc in _cc)
minMessag.Cc.Add (new MailboxAddress (cc));
}
if (mailBoxEntity.Bcc.IsNotNullAndWhiteSpace ()) {
//插入密送人
string[] _bcc = mailBoxEntity.Bcc.Split ("|");
foreach (var bcc in _bcc)
minMessag.Bcc.Add (new MailboxAddress (bcc));
}
//插入主题
minMessag.Subject = mailBoxEntity.Subject;

return minMessag;
}

/// <summary>
/// 组装邮件文本信息
/// </summary>
public TextPart AssemblyMailTextMessage (string mailBody, string textPartType) {
if (string.IsNullOrEmpty (mailBody))
throw new ArgumentNullException ();

if (string.IsNullOrEmpty (textPartType))
throw new ArgumentNullException ();
var textBody = new TextPart (textPartType) {
Text = mailBody
};
return textBody;
}
/// <summary>
/// 发送邮件
/// </summary>
private void Send (MailBoxEntity mailBoxEntity, SmtpClient client, SendResultEntity sendResultEntity, string replyto, int accid) {
try {
MimeMessage mimeMessage = _mailMessageService.AssemblyMailMessage (mailBoxEntity);
if (replyto.IsNotNullAndWhiteSpace ()) {
uint.TryParse (replyto.Trim ('|'), out uint mailuint);
SetReplyTo (mailuint, mimeMessage, accid); //设置回复
}
client.Send (mimeMessage);

if (mimeMessage.References != null) {
foreach (var item in mimeMessage.References)
mailBoxEntity.References = mailBoxEntity.References + "|" + item;
}
mailBoxEntity.References = mailBoxEntity.References?.Trim ('|');
mailBoxEntity.MessageId = mimeMessage.MessageId;

sendResultEntity.MailId = mailBoxEntity.MailType == (int) MailType.Auto
? _mailBoxService.Add (mailBoxEntity, true, true) : _mailBoxService.Add (mailBoxEntity, true);

} catch (SmtpCommandException ex) {
switch (ex.ErrorCode) {
case SmtpErrorCode.RecipientNotAccepted:
sendResultEntity.ResultInformation = $"收件人未被接受:{ex.Message}";
break;
case SmtpErrorCode.SenderNotAccepted:
sendResultEntity.ResultInformation = $"发件人未被接受:{ex.Message}";
break;
case SmtpErrorCode.MessageNotAccepted:
sendResultEntity.ResultInformation = $"消息未被接受:{ex.Message}";
break;
}
sendResultEntity.ResultStatus = false;
} catch (SmtpProtocolException ex) {
sendResultEntity.ResultInformation = $"发送消息时的协议错误:{ex.Message}";
sendResultEntity.ResultStatus = false;
} catch (Exception ex) {
sendResultEntity.ResultInformation = $"邮件发送失败:{ex.Message}";
sendResultEntity.ResultStatus = false;
}
}

收取邮件

这部分比较繁琐,先贴代码,然后再解释

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/// <summary>
/// 接收所有文件夹的邮件
/// </summary>
public bool GetFolders (string Account) {
try {
bool result = false;
MailConfigEntity mailConfig = _mailConfigService.Get ().FirstOrDefault (a => a.Account == Account);

List<DateTime> dateTimes = new List<DateTime> {
Convert.ToDateTime (mailConfig.UpdateTo)
};
// 用户所拥有的文件夹
var mailFolderUserEntitys = _mailFolderUserService.GetAsNoTracking ().
Where (a => a.AccountUserID == _applicationContext.CurrentUser.FID && a.DataStatus == (int) DataStatus.Active)
.Select (a => a.MailFolderId).ToArray ();
// 文件夹详细信息
var mailFolders = _mailFolderService.GetAsNoTracking ().Where (a => a.MailAccountId == mailConfig.FID &&
a.DataStatus == (int) DataStatus.Active && a.FolderType == 1 && mailFolderUserEntitys.Contains (a.FID));

var mailFoldersArr = mailFolders.Where (a => mailFolders.Any (r => r.ParentFolderId == a.FID) == false).Select (a => a.FID).ToArray ();

// 文件夹规则表
var mailFolderRuleEntities = _mailFolderRuleService.GetAsNoTracking ().Where (a => mailFoldersArr.Contains (a.FolderId) &&
a.DataStatus == (int) DataStatus.Active && a.RuleContent.IsNotNullAndWhiteSpace ()).ToList ();

using (var client = ImapClientInit (mailConfig.FID)) {
List<IMailFolder> mailFolderList = client.GetFolders (client.PersonalNamespaces[0]).ToList ();
foreach (var item in mailFolderList) {
item.Open (FolderAccess.ReadOnly);
//todo 因未知原因,拉取Junk文件夹邮件拉取时会报错,先跳过
if (item.Count < 1 || item.Name.ToLower () == "junk") continue;
var m = FillEntity (null, null, item, client, true, mailConfig.FID);
if (m.MailBoxList.Count < 1) continue;
if (item.Name.ToLower () == "inbox") {
result = true;
// 邮件归档
if (mailFolderRuleEntities.Any ())
MailArchive (m.MailBoxList, mailFolderRuleEntities);
}
//取出邮件后对比 获得当前文件夹最后一封邮件的时间
string maxdate = m.MailBoxList.Max (a => a.Date);
DateTime.TryParse (mailConfig.UpdateTo, out var updateTo);
DateTime.TryParse (maxdate, out var date);
if (date > updateTo) dateTimes.Add (Convert.ToDateTime (maxdate));
//保存所有邮件
_mailBoxService.AddRange (m.MailBoxList);
}
client.Disconnect (true);
// 最后更新时间取所有文件夹最大时间 避免覆盖或取值错误
mailConfig.UpdateTo = dateTimes.Max ().ToString ("yyyy-MM-dd HH:mm:ss");
//todo 此处排除字段修改无效
_mailConfigService.Update (mailConfig, false, "SentCount");
}
return result;
} catch (Exception e) {
throw e;
}
}
/// <summary>
/// 填充实体
/// 返回新邮件数量
/// </summary>
private MailBoxEntity FillEntity (IMessageSummary emhead = null, MimeMessage embody = null, IMailFolder folder = null, ImapClient client = null, bool Loop = false, int AccountId = 0) {
MailBoxEntity mailbox = new MailBoxEntity ();
if (emhead != null) {
if (emhead.Envelope.From.Count > 0) {
mailbox.Sender = emhead.Envelope.From.Mailboxes.ElementAt (0).Name;
mailbox.SenderAddress = emhead.Envelope.From.Mailboxes.ElementAt (0).Address;
}
//记录邮件唯一标识 后续获取邮件全文后可直接修改
mailbox.MessageId = emhead.Envelope.MessageId;
//记录邮件唯一查询标识 后续根据此字段及文件夹联合查询邮件全文
mailbox.UniqueId = emhead.UniqueId.Id;
//直接取本地时间 忽略时间差
mailbox.Date = emhead.Date.LocalDateTime.ToString ("yyyy-MM-dd HH:mm:ss");
//邮件时间=本地时间+时区差
//mailbox.Date = emhead.Date.LocalDateTime.
// AddHours(emhead.Date.Offset.TotalHours).ToString("yyyy-MM-dd HH:mm:ss");
mailbox.Subject = emhead.Envelope.Subject;
// 有文件夹的则记录邮件头所属文件夹
if (folder != null) {
if (folder.Attributes.ToString ().Contains ("Inbox"))
mailbox.FolderType = "Inbox";
else if (folder.Attributes.ToString ().Contains ("Sent"))
mailbox.FolderType = "Sent";
else if (folder.Attributes.ToString ().Contains ("Trash"))
mailbox.FolderType = "Trash";
else if (folder.Attributes.ToString ().Contains ("Drafts"))
mailbox.FolderType = "Drafts";
else
mailbox.FolderType = "Inbox";
mailbox.FolderName = folder.FullName;
}
// 循环记录收件人
foreach (var _Recipients in emhead.Envelope.To.Mailboxes)
mailbox.Recipients = mailbox.Recipients + "|" + _Recipients.Address.Trim ();
mailbox.Recipients = mailbox.Recipients?.Trim ('|');
//标记为收到的邮件
mailbox.MailType = (int) MailType.In;
// 邮件状态,已读未读等等
if (emhead.Flags.HasValue && mailbox.FolderType == "Inbox") {
mailbox.IsRead = emhead.Flags.Value.HasFlag (MessageFlags.Seen);
mailbox.IsAnswered = emhead.Flags.Value.HasFlag (MessageFlags.Answered);
}
// 附件个数
mailbox.AttaCount = emhead.Attachments.Count ();
}
if (embody != null) {
mailbox.OwnerMailAccount = AccountId;
// 正文
mailbox.MailTextBody = embody.TextBody;
mailbox.MailHtmlBody = embody.HtmlBody;
foreach (var _Cc in embody.Cc) //抄送
mailbox.Cc = embody.Cc + "|" + ((MailboxAddress) _Cc).Address.Trim ();
mailbox.Cc = mailbox.Cc?.Trim ('|');
foreach (var _Bcc in embody.Bcc) //密送
mailbox.Bcc = embody.Bcc + "|" + ((MailboxAddress) _Bcc).Address.Trim ();
mailbox.Bcc = mailbox.Bcc?.Trim ('|');
foreach (var _References in embody.References) //引用
mailbox.References = embody.References + "|" + _References;
mailbox.References = mailbox.References?.Trim ('|');
if (embody.Attachments.Count () > 0) {
// 收件箱箱附件保存路径
string _guid = !string.IsNullOrEmpty (embody.MessageId) ? embody.MessageId : Guid.NewGuid ().ToString ();
string _rootPath = Directory.GetDirectoryRoot (Directory.GetCurrentDirectory ()) + @"RecMailAttachment\" + $" { _guid }
";
if (!Directory.Exists(_rootPath))
Directory.CreateDirectory(_rootPath);
FileInfo fileInfo;
//附件路径集合
List<string> _attachPaths = new List<string>();
// 这里要转成mimepart类型
foreach (MimePart attachment in embody.Attachments)
{
fileInfo = new FileInfo(Path.Combine(_rootPath, attachment.FileName));
_attachPaths.Add(fileInfo.ToString());
if (File.Exists(fileInfo.ToString())) continue;
using (FileStream fs = new FileStream(fileInfo.ToString(), FileMode.Create))
{
attachment.Content.DecodeTo(fs);
fs.Flush();
}
}
mailbox.MailFilePath = _attachPaths.Aggregate((ttl, next) => string.Format($" { ttl } | { next }"));
}
}
//不需要循环或者未指定文件夹的直接返回
if (folder == null || !Loop)
return mailbox;
//需要循环的则循环整个文件夹取值
MailConfigEntity mailConfig = _mailConfigService.Get ().FirstOrDefault (a => a.FID == AccountId);
var mails = _mailBoxService.GetAsNoTracking ().Where (a => a.OwnerMailAccount == mailConfig.FID && a.MailType == 1).Select (a => a.UniqueId);
mailbox.MailBoxList = new List<MailBoxEntity> ();
IList<UniqueId> uids;
//如果之前更新过 则仅同步上次同步之后的邮件
DateTime Updateto = DateTime.MinValue;
if (mailConfig.UpdateTo.IsNotNullAndWhiteSpace ())
{
DateTime.TryParse (mailConfig.UpdateTo, out Updateto);
uids = client.GetFolder (folder.FullName).Search (SearchQuery.DeliveredAfter (Updateto));
}
else//若是第一次更新 则取出所有邮件
uids = client.GetFolder (folder.FullName).Search (SearchQuery.All);
if (uids.Count < 1) return mailbox;
int pagecount;
int sum = uids.Count > 1000 ? 1000 : uids.Count;
int pageSize = 100; // 每页记录数
if (sum % pageSize == 0)
pagecount =sum / pageSize;
else
pagecount = sum / pageSize + 1;
for (int i = 1; i <= pagecount; i++)//分页取最后一千封邮件
{
var cpage = uids.SkipLast ((i - 1) * pageSize).TakeLast (pageSize).ToList ();
var items = folder.Fetch (cpage, MessageSummaryItems.UniqueId | MessageSummaryItems.All);
foreach (var item in items)
{
DateTime.TryParse (item.Date.LocalDateTime.ToString (), out DateTime date);
if (date < Updateto || mails.Any (a => a == item.UniqueId.Id))
continue;
//取出正文
MimeMessage ebody = folder.GetMessage (item.UniqueId);
var mbox = FillEntity (item, ebody, folder, null, false, AccountId);
mailbox.MailBoxList.Add (mbox);
}
}
return mailbox;
}

原本逻辑为初次仅拉取邮件头,用户点击详情后再拉取详情。收件速度可成倍提升

但目前客户需求在列表预览邮件部分内容,所以暂时选择全部拉取,后续可以改为仅拉取加载出邮件头的详情

其他操作

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
/// <summary>
/// 设置单个邮件状态
/// </summary>
private MimeMessage SetMailFlag (MessageFlags Flage, uint uniqueid, int accountId, string folderName = null) {
MimeMessage remail;
try {
// 查找这个邮件,设置状态
using (var client = _receiveEmailService.ImapClientInit (accountId)) {
if (folderName == null)
folderName = client.Inbox.Name;
var emailUniqueId = new UniqueId (uniqueid);
var folder = client.GetFolder (folderName);
folder.Open (FolderAccess.ReadWrite);
remail = folder.GetMessage (emailUniqueId);
folder.AddFlags (emailUniqueId, Flage, true);
folder.Close ();
client.Disconnect (true);
}
} catch (Exception e) {
throw e;
}
return remail;
}
/// <summary>
/// 将邮件保存到草稿箱 返回邮件的唯一标识
/// </summary>
public int SaveDrafts (MailBoxEntity mailBox, int accountId, int uniqueId = -1) {
try {
MimeMessage mimeMessage = _mailMessageService.AssemblyMailMessage (mailBox);
using (var client = _receiveEmailService.ImapClientInit (accountId)) {
IMailFolder folder = client.GetFolder (SpecialFolder.Drafts);
folder.Open (FolderAccess.ReadWrite);
// 如果保存的是已经有的草稿邮件,则删除它再保存新的草稿.(没找到保存已有草稿的办法)
if (uniqueId > -1) {
List<UniqueId> uidls = new List<UniqueId> {
new UniqueId ((uint) uniqueId)
};
folder.SetFlags (uidls, MessageFlags.Seen | MessageFlags.Deleted, true);
folder.Expunge (uidls);
}
UniqueId? uid = folder.Append (mimeMessage, MessageFlags.Seen | MessageFlags.Draft);
folder.Close ();
client.Disconnect (true);
return uid.HasValue ? (int) uid.Value.Id : -1;
}
} catch (Exception e) {
throw e;
}
}
/// <summary>
/// 设置邮件已读
/// </summary>
public bool SetSeen (uint uniqueid, int accountId, string folderName = null) {
bool r = false;
try {
SetMailFlag (MessageFlags.Seen, uniqueid, accountId, folderName);
// 将本地邮件状态设置为已读
var mailBox = _mailBoxService.Get (a => a.UniqueId == uniqueid && a.OwnerMailAccount == accountId).ToArray ();
mailBox.Each (a => a.IsRead = true);
if (_mailBoxService.UpdateRange (mailBox) > 0) r = true;
} catch (Exception e) {
throw e;
}
return r;
}
/// <summary>
/// 设置此邮件是对指定邮件的回复
/// </summary>
private void SetReplyTo (uint uniqueid, MimeMessage message, int AccountId, string folderName = null) {
try {
MimeMessage remail = SetMailFlag (MessageFlags.Answered, uniqueid, AccountId, folderName);
if (string.IsNullOrEmpty (remail.MessageId))
return;
// 设置此邮件是对这个MESSAGEID的邮件的回复
message.InReplyTo = remail.MessageId;
// 此邮件的"对其它消息"的引用属性设为这个邮件的引用属性
foreach (var id in remail.References)
message.References.Add (id);
message.References.Add (remail.MessageId);
MailBoxEntity mail = _mailBoxService.Get (a => a.UniqueId == uniqueid).FirstOrDefault ();
mail.IsAnswered = true;
_mailBoxService.Update (mail, true);
} catch (Exception e) {
throw e;
}
}
/// <summary>
/// 设置邮件已删除
/// </summary>
public bool SetDelted (uint uniqueid, int accountId, string folderName = null) {
bool r = false;
try {
SetMailFlag (MessageFlags.Deleted, uniqueid, accountId, folderName);
// 将本地邮件状态设置为已删除
MailBoxEntity mailBox = _mailBoxService.Get (a => a.UniqueId == uniqueid && a.OwnerMailAccount == accountId).FirstOrDefault ();
mailBox.DataStatus = (int) DataStatus.Deleted;
if (_mailBoxService.Update (mailBox) > 0) r = true;
} catch (Exception e) {
throw e;
}
return r;
}

组件使用感悟

MailKit和MimeKit组件在项目的使用中较为的便捷,基本包含了所有的基础邮件服务操作。组件提供的SmtpClient类提供的功能很丰富,例如连接邮件服务器,邮件账户认证,组装邮件消息,获取邮件服务器配置信息等等方法的提供,可以让我们在项目中快速的获取邮件服务的所有信息。

使用过邮件功能的项目 都会有困扰,客户端与邮件服务器的连接是否成功,以及邮件是否发送成功状态没有办法很快的获取,只能根据邮件服务器返回的一场状态进行判断。但是MailKit提供对应的方法和异常类,对邮件服务器返回的异常信息进行解析,客户端可以根据这些异常类获取邮件状态。

MailKit组件的提供了ProtocolLogger类,该类用于记录SMTP操作基础信息,该类作用为记录邮件服务日志。在邮件发送完毕后,需要及时的关闭连接,调用Disconnect(true)方法。