使用MailKit发送邮件

.NET Core 使用MailKit发送电子邮件

Github:关于 MailKit

很多有经验的.NET老程序员可能会说,发邮件有什么难的,十几年前我们就能用.NET Framework自带的SmtpClient发邮件了,并且.NET Core也能用。为啥还要写这篇文章?

但是,万物皆有始有终,最近我突然发现,SmtpClient 已经被微软标记为弃用:

并且微软官方钦点了一个继任者:MailKithttps://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地址"));

以下代码演示了几个步骤:

  1. 注册邮件发送成功后的事件
  2. 连接服务器
  3. 验证账号
  4. 发送邮件
  5. 断开连接
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

参考:.NET Core 使用MailKit发送电子邮件

SmtpClient与MailKit对比

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);
            }
        }
    }
}
View Code

但是,您可能想对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的哪些属性

邮件协议之IMAP指令讲解

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);
                    }
                }
            }
}
View Code

还可以做排序和搜索: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解决办法

原文地址:https://www.cnblogs.com/peterYong/p/12024394.html