.NET Core 使用MailKit发送电子邮件
Github:关于 MailKit
很多有经验的.NET老程序员可能会说,发邮件有什么难的,十几年前我们就能用.NET Framework自带的SmtpClient发邮件了,并且.NET Core也能用。为啥还要写这篇文章?
但是,万物皆有始有终,最近我突然发现,SmtpClient 已经被微软标记为弃用:
并且微软官方钦点了一个继任者:MailKit,https://github.com/jstedfast/MailKit
这是一个基于MimeKit的跨平台.NET邮件库,支持IMAP、POP3、SMTP协议。它相比.NET自带的SmtpClient,支持更广泛的协议和更现代的电子邮件标准。因此微软官方建议,SmtpClient只用来兼容老应用,如果开发新应用的话,直接使用MailKit。
并且,它是在MIT协议下开源的。意味着非常自由的使用,也可以由全世界的.NET开发者参与贡献,一起维护和完善这个东西。
使用SMTP协议发送邮件
我得到这个好东西以后,第一步就是将使用SmtpClient的老代码迁移到MailKit。因此,我的案例里只使用SMTP这一种协议来发邮件。
首先,使用NuGet安装MailKit:
Visual Studio
Install-Package MailKit
.NET Core CLI
dotnet add package MailKit
构建 MimeMessage
MimeMessage是MailKit里代表一封电子邮件的对象,它和.NET自带的MailMessage类型非常类似。比如添加主题和发件人:
var messageToSend = new MimeMessage
{
Sender = new MailboxAddress("发件人姓名", "发件人Email地址"),
Subject = "主题",
};
添加发件人信息和以前有所不同,MailKit居然支持多个发件人,所以From是一个集合类型,要通过Add方法来添加:
messageToSend.From.Add(new MailboxAddress("发件人姓名", "发件人邮箱账号名"));
邮件正文(Body属性)支持多种格式,最常用的是纯文本和HTML。需要用TextPart类来安排,TextPart的构造函数里可以指定正文格式,例如HTML:
messageToSend.Body = new TextPart(TextFormat.Html) { Text = bodyText };
或者纯文本
messageToSend.Body = new TextPart(TextFormat.Plain) { Text = bodyText };
添加收件人信息:
messageToSend.To.Add(new MailboxAddress("收件人Email地址"));
添加抄送(CC)信息:
messageToSend.Cc.Add(new MailboxAddress("抄送者Email地址"));
以下代码演示了几个步骤:
- 注册邮件发送成功后的事件
- 连接服务器
- 验证账号
- 发送邮件
- 断开连接
using (var smtp = new MailKit.Net.Smtp.SmtpClient()) { smtp.MessageSent += (sender, args) => { // args.Response }; smtp.ServerCertificateValidationCallback = (s, c, h, e) => true; await smtp.ConnectAsync("smtp-mail.outlook.com", 587, SecureSocketOptions.StartTls); await smtp.AuthenticateAsync("账号", "密码"); await smtp.SendAsync(messageToSend); await smtp.DisconnectAsync(true); }
MessageSent事件里可以通过args参数,获得服务器的响应信息,以便于记录Log。
连接outlook.com的服务器需要设置为SecureSocketOptions.StartTls,不然会拒绝连接。对于其他服务器,可以试试 SecureSocketOptions.Auto
MailKit文档中的一些翻译
文档:http://www.mimekit.net/docs/html/Introduction.htm
1、MessageFlags:消息标志的枚举
None | 0 | 无 | |
Seen | 1 | 消息标记为 已读 | |
Answered | 2 | 该消息已得到答复 | |
Flagged | 4 | 该消息已标记为重要 | |
Deleted | 8 | 删除 | |
Draft | 16 | 草稿 | |
Recent | 32 | 该消息刚到达文件夹中。 | |
UserDefined | 64 | 文件夹允许使用用户定义的标志。 |
2、MessageSummaryItems
- Envelope :消息信封,其中包含消息的简短摘要。其中包含To、From、Date、Subject...
- Body
- Flags:MessageFlags
- UniqueID
- EmailID
- Full
IMAP接收邮件
接收邮件协议有pop3、Imap,比POP3支持更重要的是IMAP支持。 这是一个从IMAP服务器检索消息的简单用例:
using System; using MailKit.Net.Imap; using MailKit.Search; using MailKit; using MimeKit; namespace TestClient { class Program { public static void Main (string[] args) { using (var client = new ImapClient ()) { // For demo-purposes, accept all SSL certificates client.ServerCertificateValidationCallback = (s,c,h,e) => true; client.Connect ("imap.friends.com", 993, true); client.Authenticate ("joey", "password"); // The Inbox folder is always available on all IMAP servers... var inbox = client.Inbox; inbox.Open (FolderAccess.ReadOnly); Console.WriteLine ("Total messages: {0}", inbox.Count); Console.WriteLine ("Recent messages: {0}", inbox.Recent); for (int i = 0; i < inbox.Count; i++) { var message = inbox.GetMessage (i); Console.WriteLine ("Subject: {0}", message.Subject); } client.Disconnect (true); } } } }
但是,您可能想对IMAP做更复杂的事情,例如获取摘要信息(summary),以便您可以在邮件客户端中显示邮件列表,而不必先从服务器下载所有邮件:
//为两个索引(包括两个索引)之间的消息获取消息摘要 foreach (var summary in inbox.Fetch(0,-1,MessageSummaryItems.Full | MessageSummaryItems.UniqueId)) { Console.WriteLine("[summary] {0:D2}: {1}", summary.Index, summary.Envelope.Subject); }
MessageSummaryItems 是MailKit.MessageSummary字段的位字段,每个枚举值是想要获取的属性。
通过调用Fetch() 是填充MessageSummary的哪些属性 。
Fetch命令的结果还可以用于下载单个MIME部分,而不是下载整个消息。 例如:
private static void GetMime(ImapClient client) { var inbox = client.Inbox; //下载MIME协议格式中的某个域,而不是下载整个邮件内容 foreach (var summary in inbox.Fetch(0, -1, MessageSummaryItems.UniqueId | MessageSummaryItems.BodyStructure)) { if (summary.TextBody != null) { // this will download *just* the text/plain part var text = inbox.GetBodyPart(summary.UniqueId, summary.TextBody); } if (summary.HtmlBody != null) { // this will download *just* the text/html part var html = inbox.GetBodyPart(summary.UniqueId, summary.HtmlBody); } // 【获取图片附件】if you'd rather grab, say, an image attachment... it might look something like this: if (summary.Body is BodyPartMultipart) { var multipart = (BodyPartMultipart)summary.Body; var attachment = multipart.BodyParts.OfType<BodyPartBasic>().FirstOrDefault(x => x.FileName == "logo.jpg"); if (attachment != null) { // this will download *just* the attachment var part = inbox.GetBodyPart(summary.UniqueId, attachment); } } } }
还可以做排序和搜索:inbox.Search、 inbox.Sort
当然,除了下载消息外,您还可以获取匹配消息的摘要信息Summary,或者使用返回的UID进行任何其他操作。
如何浏览文件夹? MailKit也可以这样做:
// Get the first personal namespace and list the toplevel folders under it. var personal = client.GetFolder (client.PersonalNamespaces[0]); foreach (var folder in personal.GetSubfolders (false)) Console.WriteLine ("[folder] {0}", folder.Name);
也可以:
List<IMailFolder> mailFolders = client.GetFolders(client.PersonalNamespaces[0]).ToList(); mailFolders.ForEach(q => Console.WriteLine(q.FullName));
如果IMAP服务器支持SPECIAL-USE或XLIST(GMail)扩展名,则可以使用以下预定义的“全部”,“草稿”,“已标记”(又名“重要”),Junk“垃圾邮件”,“已发送”,“垃圾箱”等文件夹:
if ((client.Capabilities & (ImapCapabilities.SpecialUse | ImapCapabilities.XList)) != 0) { var drafts = client.GetFolder (SpecialFolder.Drafts); } else { // maybe check the user's preferences for the Drafts folder? }
如果IMAP服务器不支持SPECIAL-USE或XLIST扩展名,则必须提出自己的启发式方法来获取“已发送”,“草稿”,“废纸rash”等文件夹。 例如,您可能使用类似以下的逻辑:
static string[] CommonSentFolderNames = { "Sent Items", "Sent Mail", "Sent Messages", /* maybe add some translated names */ }; static IFolder GetSentFolder (ImapClient client, CancellationToken cancellationToken) { var personal = client.GetFolder (client.PersonalNamespaces[0]); return personal.GetSubfolders (false, cancellationToken).FirstOrDefault (x => CommonSentFolderNames.Contains (x.Name)); }
另一个选项可能是允许您的应用程序用户配置他或她要用作其“已发送”文件夹,“草稿”文件夹,“废纸folder”文件夹等的文件夹。
如何处理这取决于您。
创建基于MailKit和MimeKit的.NET基础邮件服务
参考:创建基于MailKit和MimeKit的.NET基础邮件服务
问题
在采用IMAP收取163邮件时,报错登录不安全
报错内容:The IMAP server replied to the 'EXAMINE' command with a 'NO' response: EXAMINE Unsafe Login. Please contact kefu@188.com for help
(IMAP服务器使用“否”响应回复了“ EXAMINE”命令:EXAMINE不安全登录。 请联系kefu@188.com寻求帮助)
解决方法:
对163邮箱设置: 收邮件NO Select Unsafe Login. Please contact kefu解决办法