MailKit
MailKit是什么?
MailKit是一个基于MimeKit构建的跨平台邮件客户端库。
捐赠
MailKit是我个人的开源项目,我已经投入了数千小时来完善它,目标是使其成为.NET平台上最好的电子邮件框架。我需要您的帮助来实现这个目标。
捐赠有助于支付诸如网络托管、域名注册以及开发工具许可证等费用,如性能分析器、内存分析器、静态代码分析工具等。它还能激励我继续致力于这个项目。
特性
- SASL认证
- CRAM-MD5
- DIGEST-MD5
- LOGIN
- NTLM
- PLAIN
- SCRAM-SHA-1[-PLUS]
- SCRAM-SHA-256[-PLUS]
- SCRAM-SHA-512[-PLUS]
- OAUTHBEARER(部分支持 - 您需要自行获取认证令牌)
- XOAUTH2(部分支持 - 您需要自行获取认证令牌)
- 代理支持
- SMTP客户端
- 支持上述所有SASL机制
- 通过"smtps"协议支持SSL封装连接
- 支持客户端SSL/TLS证书
- 支持以下扩展:
- 所有API均可取消
- 提供异步API
- POP3客户端
- IMAP4客户端
- 支持上述所有SASL机制
- 通过"imaps"协议支持SSL封装连接
- 支持客户端SSL/TLS证书
- 支持以下扩展:
- ACL
- QUOTA
- LITERAL+
- IDLE
- NAMESPACE
- ID
- CHILDREN
- LOGINDISABLED
- STARTTLS
- MULTIAPPEND
- UNSELECT
- UIDPLUS
- CONDSTORE
- ESEARCH
- SASL-IR
- COMPRESS
- WITHIN
- ENABLE
- QRESYNC
- SORT
- THREAD
- ANNOTATE
- LIST-EXTENDED
- ESORT
- METADATA / METADATA-SERVER
- NOTIFY
- FILTERS
- LIST-STATUS
- SORT=DISPLAY
- SPECIAL-USE / CREATE-SPECIAL-USE
- SEARCH=FUZZY
- MOVE
- UTF8=ACCEPT / UTF8=ONLY
- LITERAL-
- APPENDLIMIT
- STATUS=SIZE
- OBJECTID
- REPLACE
- SAVEDATE
- XLIST
- X-GM-EXT1(X-GM-MSGID、X-GM-THRID、X-GM-RAW和X-GM-LABELS)
- 所有API均可取消
- 提供异步API
- 客户端消息排序和线程处理
目标
本项目的主要目标是为.NET世界提供强大、功能齐全且符合RFC标准的SMTP、POP3和IMAP客户端实现。
我能找到的所有其他.NET IMAP客户端实现都存在重大架构问题,例如忽略意外的未标记响应,假设字面字符串标记只用于消息正文(实际上它们可以用于响应中的几乎任何字符串标记),假设在FETCH响应中找到消息正文结尾的方法是扫描") UID"
,以及不正确处理带有国际字符的邮箱名称,仅举几个例子。
IMAP需要花费大量时间仔细阅读和反复阅读IMAP规范(以及MIME规范)以理解协议的所有细微之处,而大多数(所有?)其他开源.NET IMAP库至少都是由只关心满足自己简单需求的开发人员编写的。这样做没有什么本质上的错误,但网上充斥着半成品、不符合RFC标准的IMAP实现,因此是时候编写一个精心设计和实现的IMAP客户端库了。
对于POP3,像OpenPOP.NET这样的库实际上相当不错,尽管MIME解析器过于严格 - 每当遇到它不认识的Content-Type或Content-Disposition参数时就抛出异常,如果你查看邮件列表,就会发现这是OpenPOP.NET用户经常遇到的问题。当然,MailKit的Pop3Client没有这个问题。它还直接从套接字解析消息,而不是将消息下载到大型字符串缓冲区中再进行解析,因此你可能会发现MailKit不仅更快(MailKit的MIME解析器MimeKit从磁盘解析消息的速度比OpenPOP.NET的解析器快25倍),而且内存使用量也大大减少。
对于SMTP,大多数开发人员使用System.Net.Mail.SmtpClient,它或多或少满足了他们的需求,因此可能不是他们需求列表中的高优先级项目。然而,如果需要跨平台支持或开发人员希望能够在通过SMTP发送之前保存和重新加载MIME消息,那么MailKit中包含的SmtpClient实现是一个更好的选择。MailKit的SmtpClient还支持PIPELINING,这应该会提高发送消息的性能(尽管可能不太明显)。
许可信息
MIT许可证
版权所有 (C) 2013-2024 .NET基金会和贡献者
特此免费授予任何获得本软件副本和相关文档文件("软件")的人不受限制地处理本软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,以及允许向其提供本软件的人这样做,但须符合以下条件:
上述版权声明和本许可声明应包含在本软件的所有副本或大部分内容中。
本软件按"原样"提供,不提供任何形式的明示或暗示担保,包括但不限于对适销性、特定用途的适用性和非侵权性的担保。在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权行为还是其他方面,均源于、出于或与本软件有关,或与本软件的使用或其他交易有关。
通过NuGet安装
安装MailKit最简单的方法是通过NuGet。
在Visual Studio的包管理器控制台中, 输入以下命令:
Install-Package MailKit
获取源代码
首先,你需要从我的GitHub仓库克隆MailKit。要使用命令行版本的Git执行此操作, 你需要在终端中输入以下命令:
git clone --recursive https://github.com/jstedfast/MailKit.git
如果你在Windows上使用TortoiseGit,你需要在要克隆MailKit的目录中右击, 并在菜单中选择Git Clone...。完成后,你会看到以下对话框:
填写红色轮廓标出的区域,然后点击OK。这将递归地将MailKit克隆到你的本地机器上。
更新源代码
如果我在你下载源代码后对MailKit进行了更改,你可能偶尔想要更新本地源代码副本。要使用命令行版本的Git执行此操作,你需要在MailKit目录中的终端中输入以下命令:
git pull
git submodule update
如果你在Windows上使用TortoiseGit,你需要右击MailKit目录, 并在菜单中选择Git Sync...。完成后,你需要在以下对话框中点击Pull和Submodule Update按钮:
构建
在MailKit顶级目录中,有几个解决方案文件;它们是:
- MailKit.sln - 包括.NET Framework 4.6.2/4.7/4.8、.NETStandard 2.0/2.1、.NET6.0以及单元测试的项目。
- MailKit.Coverity.sln - 用于生成Coverity静态分析构建,一般不太有用。
- MailKit.Documentation.sln - 用于生成https://mimekit.net/docs上的文档。
一旦你在Visual Studio中打开了适当的MailKit解决方案文件, 你可以选择Debug或Release构建配置,然后进行构建。
Visual Studio 2017和Visual Studio 2019都应该能够毫无问题地构建MailKit,但像Visual Studio 2015这样的旧版本将需要对项目进行修改才能正确构建。据报道,向MimeKit和MailKit项目添加NuGet包引用Microsoft.Net.Compilers >= 3.6.0和System.ValueTuple >= 4.5.0将允许它们成功构建。
注意:Release构建将生成xml API文档,但Debug构建不会。
使用MailKit
发送消息
MailKit的一个更常见的操作是发送电子邮件消息。
using System;
using MailKit.Net.Smtp;
using MailKit;
using MimeKit;
namespace TestClient {
class Program
{
public static void Main (string[] args)
{
var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("Joey Tribbiani", "joey@friends.com"));
message.To.Add (new MailboxAddress ("Mrs. Chanandler Bong", "chandler@friends.com"));
message.Subject = "How you doin'?";
message.Body = new TextPart ("plain") {
Text = @"Hey Chandler,
I just wanted to let you know that Monica and I were going to go play some paintball, you in?
-- Joey"
};
using (var client = new SmtpClient ()) {
client.Connect ("smtp.friends.com", 587, false);
// 注意:只有在SMTP服务器需要身份验证时才需要
client.Authenticate ("joey", "password");
client.Send (message);
client.Disconnect (true);
}
}
}
}
检索消息(通过Pop3)
MailKit的另一个主要用途是从pop3服务器检索消息。
using System;
using MailKit.Net.Pop3;
using MailKit;
using MimeKit;
namespace TestClient {
class Program
{
public static void Main (string[] args)
{
using (var client = new Pop3Client ()) {
client.Connect ("pop.friends.com", 110, false);
client.Authenticate ("joey", "password");
for (int i = 0; i < client.Count; i++) {
var message = client.GetMessage (i);
Console.WriteLine ("Subject: {0}", message.Subject);
}
client.Disconnect (true);
}
}
}
}
使用IMAP
比POP3支持更重要的是IMAP支持。这里是从IMAP服务器检索消息的一个简单用例:
using System;
using MimeKit;
using MailKit;
using MailKit.Search;
using MailKit.Net.Imap;
namespace TestClient {
class Program
{
public static void Main (string[] args)
{
using (var client = new ImapClient ()) {
client.Connect ("imap.friends.com", 993, true);
client.Authenticate ("joey", "password");
// 收件箱文件夹在所有IMAP服务器上总是可用的...
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文件夹中消息的信息
IMAP相对于POP3的一个优势是IMAP协议允许客户端检索文件夹中消息的信息,而无需首先下载所有消息。
使用Fetch和FetchAsync方法重载(或方便的扩展方法),可以获取给定文件夹中任何范围消息的任何子集摘要信息。
foreach (var summary in inbox.Fetch (0, -1, MessageSummaryItems.Envelope)) {
Console.WriteLine ("[summary] {0:D2}: {1}", summary.Index, summary.Envelope.Subject);
也可以使用接受 IFetchRequest 参数的 Fetch/FetchAsync API 来更精细地控制获取内容:
// 让我们获取非 Received 头:
var request = new FetchRequest {
Headers = new HeaderSet (new HeaderId[] { HeaderId.Received }) {
Exclude = true
}
};
foreach (var summary in inbox.Fetch (0, -1, request)) {
Console.WriteLine ("[summary] {0:D2}: {1}", summary.Index, summary.Headers[HeaderId.Subject]);
Fetch 方法的结果也可以用来下载单个 MIME 部分,而不是下载整个消息。例如:
foreach (var summary in inbox.Fetch (0, -1, MessageSummaryItems.UniqueId | MessageSummaryItems.BodyStructure)) {
if (summary.TextBody != null) {
// 这将只下载 text/plain 部分
var text = inbox.GetBodyPart (summary.UniqueId, summary.TextBody);
}
if (summary.HtmlBody != null) {
// 这将只下载 text/html 部分
var html = inbox.GetBodyPart (summary.UniqueId, summary.HtmlBody);
}
// 如果你想获取图片附件...可能会像这样:
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) {
// 这将只下载附件
var part = inbox.GetBodyPart (summary.UniqueId, attachment);
}
}
}
在 IMAP 中设置消息标志
为了设置或更新特定消息的标志,实际需要的是消息的 UID 或索引以及它所属的文件夹。
想要更新消息标志的一个明显原因是在用户打开并阅读消息后将其标记为"已读"(即"已看")。
folder.Store (uid, new StoreFlagsRequest (StoreAction.Add, MessageFlags.Seen) { Silent = true });
在 IMAP 中删除消息
在 IMAP 中删除消息涉及在消息上设置 \Deleted
标志,并可选择从文件夹中清除它。
将消息标记为 \Deleted
的方式与将消息标记为 \Seen
的方式相同。
folder.Store (uid, new StoreFlagsRequest (StoreAction.Add, MessageFlags.Deleted) { Silent = true });
folder.Expunge ();
搜索 IMAP 文件夹
你可能也对排序和搜索感兴趣...
// 让我们搜索 2013 年 1 月 12 日之后收到的所有主题中包含 "MailKit" 的消息...
var query = SearchQuery.DeliveredAfter (DateTime.Parse ("2013-01-12"))
.And (SearchQuery.SubjectContains ("MailKit")).And (SearchQuery.Seen);
foreach (var uid in inbox.Search (query)) {
var message = inbox.GetMessage (uid);
Console.WriteLine ("[match] {0}: {1}", uid, message.Subject);
}
// 让我们进行相同的搜索,但这次按到达时间倒序排序
var orderBy = new [] { OrderBy.ReverseArrival };
foreach (var uid in inbox.Sort (query, orderBy)) {
var message = inbox.GetMessage (uid);
Console.WriteLine ("[match] {0}: {1}", uid, message.Subject);
}
// 你会注意到 orderBy 参数是一个数组...这是因为你
// 实际上可以根据多个列对搜索结果进行排序:
orderBy = new [] { OrderBy.ReverseArrival, OrderBy.Subject };
foreach (var uid in inbox.Sort (query, orderBy)) {
var message = inbox.GetMessage (uid);
Console.WriteLine ("[match] {0}: {1}", uid, message.Subject);
}
当然,除了下载消息,你还可以获取匹配消息的摘要信息,或者用返回的 UID 做其他任何事情。
在 IMAP 中导航文件夹
如何导航文件夹?MailKit 也可以做到:
// 获取第一个个人命名空间并列出其下的顶级文件夹。
var personal = client.GetFolder (client.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders (false))
Console.WriteLine ("[folder] {0}", folder.Name);
如果 IMAP 服务器支持 SPECIAL-USE 或 XLIST (GMail) 扩展,你可以像这样获取预定义的 All、Drafts、Flagged (又名 Important)、Junk、Sent、Trash 等文件夹:
if ((client.Capabilities & (ImapCapabilities.SpecialUse | ImapCapabilities.XList)) != 0) {
var drafts = client.GetFolder (SpecialFolder.Drafts);
} else {
// 也许检查用户对草稿文件夹的偏好?
}
在 IMAP 服务器不支持 SPECIAL-USE 或 XLIST 扩展的情况下,你必须想出自己的启发式方法来获取 Sent、Drafts、Trash 等文件夹。例如,你可能使用类似这样的逻辑:
static string[] CommonSentFolderNames = { "Sent Items", "Sent Mail", "Sent Messages", /* 可能添加一些翻译后的名称 */ };
static IFolder GetSentFolder (ImapClient client, CancellationToken cancellationToken)
{
var personal = client.GetFolder (client.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders (false, cancellationToken)) {
foreach (var name in CommonSentFolderNames) {
if (folder.Name == name)
return folder;
}
}
return null;
}
使用 LINQ,你可以将其简化为类似这样的内容:
static string[] CommonSentFolderNames = { "Sent Items", "Sent Mail", "Sent Messages", /* 可能添加一些翻译后的名称 */ };
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));
}
另一个选择可能是允许应用程序的用户配置他们想要用作 Sent 文件夹、Drafts 文件夹、Trash 文件夹等的文件夹。
如何处理这个问题由你决定。
贡献
你需要做的第一件事是将 MailKit 分叉到你自己的 GitHub 仓库。有关如何做到这一点的说明,请参阅标题为获取源代码的部分。
如果你使用 Visual Studio for Mac 或 MonoDevelop, 所有解决方案文件都配置了 MailKit 使用的编码风格。如果你在 Windows 上使用 Visual Studio 或其他编辑器,请尽最大努力保持现有的编码风格。
一旦你有了一些想要提交到官方 MailKit 仓库的更改, 向我发送一个Pull Request,我会尽快审查你的更改。
如果你想做出贡献但没有特别想要处理的功能,请查看问题跟踪器,看看是否有什么能引起你兴趣的!
报告 Bug
有 bug 或功能请求吗?请打开一个新的 bug 报告 或 功能请求。
在打开新问题之前,请搜索任何现有问题 以避免提交重复内容。也可能值得查看 FAQ以了解其他开发人员常见的问题。
如果 MailKit 无法与你的邮件服务器配合使用,请在 bug 报告中包含协议日志,否则 我无法修复问题。
如果你在 MailKit 中的某个地方遇到异常,不要只提供 Exception.Message
字符串。请同时包含 Exception.StackTrace
。仅凭 Message
通常是无用的。
文档
API 文档可以在 https://www.mimekit.net/docs 找到。
一些示例代码片段可以在 Documentation/Examples
目录中找到。
示例应用程序可以在 samples
目录中找到。
NuGet 包中还包含了 XML 格式的 API 参考文档的副本。
.NET Foundation
MailKit 是一个 .NET Foundation 项目。
该项目采用了 Contributor Covenant 定义的行为准则,以明确我们社区中的预期行为。有关更多信息,请参阅 .NET Foundation 行为准则。
一般的 .NET OSS 讨论: .NET Foundation 论坛