.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解決辦法