邮件之MailKit使用

电子邮件介绍

电子邮件使用的三大协议

1.电子邮件的工作原理

Internet电子邮件系统是基于客户机/服务器方式,客户端也叫用户代理(User Agent),提供用户界面,负载邮件发送的准备工作,如邮件的起草、编辑以及向服务器发送邮件或从服务器取邮件等。服务器端也叫传输代理(Message Transfer Agent),负责邮件的传输,它采用端到端的传输的传输方式,源端主机参与邮件传输的全过程。

2.电子邮件协议

电子邮件在发送和接收的过程中还要遵循一些基本协议和标准,这些协议主要有SMTP、POP3、IMAP、MIME等。

(1)SMTP协议

SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是Internet上基于TCP/IP的应用层协议,使用于主机与主机之间的电子邮件交换。SMTP的特点是简单,它只定义了邮件发送方和接收方之间的连接传输,将电子邮件有一台计算机传送到另一台计算机,而不规定其他任何操作,如用户界面的交互、邮件的接收、邮件存储等。Internet上几乎所有主机都运行着遵循SMTP的电子邮件软件,因此使用非常普通。另一方面,SMTP由于简单,因而有其一定的局限性,它只能传送ASCII文本文件,而对于一些二进制数据文件需要进行编码后才能传送。

(2)POP3协议和IMAP协议

电子邮件用户要从邮件服务器读取或下载邮件时必须要有邮件读取协议。现在常用的邮件读取协议有两个,一个是邮局协议的第三版本(POP3,Post Office Protocol Version 3),另一个是因特网报文存取协议(IMAP,Internet Message Access Protocol)。

POP3是一个非常简单、但功能有限的邮件读取协议,大多数ISP都支持POP3。当邮件用户将邮件接收软件设定为POP3阅读电子邮件时,每当使用者要阅读电子邮件时,它都会把所有信件内容下载至使用者的计算机,此外,他可选择把邮件保留在邮件服务器上或是不保留邮件在服务器上。无IMAP是另一种邮件读取协议。当邮件用户将邮件接收设定IMAP阅读电子邮件时,它并不会把所有邮件内容下载至计算机,而只下载邮件的主题等信息。

(3)多途径Internet邮件扩展协议

多用途Internet邮件扩展协议(MIME,Multipose Internet Mail Extensions)是一种编码标准,它解决了SMTP只能传送ASCII文本的限制。MIME定义了各种类型数据,如声音、图像、表格、二进制数据等的编码格式,通过对这些类型的数据编码并将它们作为邮件中的附件进行处理,以保证这些部分内容完整、正确地传输。因此,MIME增强了SMTP的传输功能,统一了编码规范。

根据smtp协议规定,其实从本质上来说邮件就不是安全的。我们可以做到客户端到邮件服务器使用ssl加密。但是邮件服务器上的邮件使用的是明文存储。无论你多么小心使用邮件也抵挡不住邮件服务器被攻破吧。所以最有安全的方法其实就是将邮件加密。把密文放到邮件服务器上,那么密码泄露,邮件泄露就不会怕了。

参考:电子邮件使用的三大协议 

POP3、SMTP、IMAP 和 Exchange 的区别

SMTP, POP3, IMAP 都是mail server上的service。

简单地说,SMTP管‘发’,POP3/IMAP管‘收’。

举个例子,你坐在电脑边用mail client写完邮件,点击‘发送’。这时你的mail client会发消息给邮件服务器上的SMTP service。这时有两种情况:如果邮件的收信人也是处于同一个domain,比如从http://163.com发送给163的邮箱,SMTP service只需要转给local的POP3 Service即可。如果邮件收信人是另外的domain,比如http://163.com发送给http://sina.com, SMTP service需要通过询问DNS, 找到属于sina的SMTP service的hostSMTP service收到邮件后转给负责接收邮件的POP3 service。

POP3 service和IMAP的区别主要是:POP3是比较老的protocol,主要为了解决本地机器和远程邮件服务器链接的问题,每次邮件会download到本地机器,然后从远程邮件服务器上删掉(当然特殊config除外),然后进行本地编辑。这样的问题是如果从多个终端链接服务器,只有第一个下载的能看到。POP3的好处是应用广泛,坏处是无法同步消息;一旦下载服务端即消失(你可以设置在服务端保存副本,但这并不改变协议的本质);无法同步联系人、日历和子邮件目录。现在pop4正在讨论中。IMAP是比较新的protocol,可以将邮件分文件夹整理,然后这些信息也存在远程的邮件服务器上,读取邮件后,服务器上不删除。原理上IMAP应该是相当于oneline编辑,但现在的mail client基本都有在本地存copy的功能。

Exchange Server是微软公司的一套电子邮件服务组件。除传统的电子邮件的存取、储存、转发作用外,在新版本的产品中亦加入了一系列辅助功能,如语音邮件、邮件过滤筛选、OWA(基于Web的电子邮件存取)。Exchange Server支持多种电子邮件网络协议,如SMTP、POP3、IMAP4。

Exchange Server是个消息与协作系统,Exchange server可以被用来构架应用于企业、学校的邮件系统甚至于免费邮件系统。也可以用于开发工作流,知识管理系统,Web系统或者是其他消息系统。

从上我们可以看到Exchange和IMAP/SMTP/POP3并不是同一概念,前者是微软提供一个邮箱服务器产品/云服务,后者是电子邮件协议。Exchange协议可供用户同步邮件、联系人、日历及其他所有Exchange对象。由于这个协议需要部署Exchange服务器,因此通常为公司或者机构账号所用。它的好处是:全邮件同步;邮件保存在服务器上;支持绝大部分移动设备、联系人、日历和数据同步;在服务器域中邮件可撤回并修改。坏处是必须部署昂贵的Exchange服务器;邮件管理员可以控制你的终端设备权限并能看到邮件收发状态;同样会有同步问题。

参考:POP3, SMTP, IMAP 和 Exchange 的区别

MailKit

1、Mimekit官网

MimeKit与MailKit都是开源的.net处理邮件的库。

包括MailKit的介绍、常见的问题、API接口等

2、MailKit的使用心得

Mailkit 支持 Pop3、IMAP,STMP,是目前.Net端最全的邮件开源项目了。

1、Pop3:跟其他的pop3操作类没有太大区别,跟OpenPop.NET等都差不多

2、IMAP:功能上比Pop3要强大太多,优势是功能强大、可以搜索邮箱的所有文件夹,Pop3只能搜索INBOX(收件箱),如果要做收信,还是IMAP首选。缺点是每一次操作都会与服务器同步,比如读取了邮件,服务器上也会变为已读,而Pop3不会。

如果做邮箱收信的话,还是必须同时支持 Pop3、IMAP。

IMAP

1、A02 NO SELECT Unsafe Login. Please contact kefu@1888.com for help

imapClient.Identify 的使用

var clientImplementation = new ImapImplementation
{
Name = "sssssd",
Version = "2.0"
};
var serverImplementation = imapClient.Identify(clientImplementation);

这句代码在登录完后需要执行,不然无法拉取文件夹。

这个命令主要是表面客户端身份的,参数name和version的值,可以按照需要去写,如上面的163邮箱就没有要求,可以随便填,但有些邮件服务器是有要求的,只有服务器认可的客户端和版本才可以正常使用,服务器会拒绝非法客户端的,一切看邮件服务端的要求。

参考:mailkit----163邮箱登录拉取邮件的坑

2、Search

IList<UniqueId> Search(SearchQuery query, CancellationToken cancellationToken = default);

在文件夹中搜索与指定查询匹配的邮件

3、Fetch

Fetch(IList<UniqueId>, MessageSummaryItems, IEnumerable<String>, CancellationToken)

获取指定消息UID的消息摘要。

MessageSummaryItems:要获取的消息摘要项

IEnumerable<String>:所需的标题字段。

4、MoveTo

Nullable<UniqueId> IMailFolder.MoveTo(UniqueId, IMailFolder, CancellationToken)

从源文件夹,将指定的消息移到目标文件夹。

UniqueId:要移动的邮件的uid

返回:邮件在目标文件夹中的UID(如果有); 否则为null。

注意:在本地文件夹(源)得有这封邮件的UId, 返回才会有新的Uid

5、已读回执

都是用此参数  HeaderId.DispositionNotificationTo

IList<IMessageSummary> messages = folder.Fetch(uids, MessageSummaryItems.UniqueId | MessageSummaryItems.Full, fields);

        private HashSet<string> CreateHeaderFields()
        {
            //先抓取邮件头列表
            HashSet<string> fields = new HashSet<string>();
            fields.Add(HeaderId.Subject.ToHeaderName());//标题
            fields.Add(HeaderId.MessageId.ToHeaderName());//MessageId
            fields.Add(HeaderId.DispositionNotificationTo.ToHeaderName());//请求已读回执
            return fields;
        }

注意:【阿里云邮箱服务器】拉取邮件头时 可能拉取不到 DispositionNotificationTo,但是 拉取邮件体的时候 是可以拉取到的,若是在获取邮件头时判断是否有已读回执,此时需要在获取邮件体时做更新。

6、the ImapClient is currently busy processing a command in another thread. Lock the SyncRoot property to properly synchronize your threads   

有可能文件夹未关闭 导致   folder.Close() ;

参考:https://stackoverflow.com/questions/29222413/thread-safe-getfolder-using-mailkit

看下流程上,有哪里多的调用,在一个完整的流程中 尽量不要重复连接imap和重复打开关闭文件夹。

7、The IMAP server replied to the 'DELETE' command with a 'NO' response.

现象:在邮件客户端上删除文件夹时失败,报此问题。

此种情况是IMAP服务器出于某种原因 不让你(客户端)删除此文件夹。

可以用其他邮件客户端来试下,能不能删除掉【eg:foxmai】

8、一个封锁操作被对 WSACancelBlockingCall 的调用中断。

Unable to read data from the transport connection: 一个封锁操作被对 WSACancelBlockingCall 的调用中断。.

 ---> System.Net.Sockets.SocketException (10004): 一个封锁操作被对 WSACancelBlockingCall 的调用中断。

无法从传输连接读取数据

可能原因:调用Close后,线程恰好继续向网络缓冲区中读取数据

首先找到报错代码的位置,再进行修改

参考:一个封锁操作被对 WSACancelBlockingCall 的调用中断 ErrorCode=10004

9、imap连接时:The handshake failed due to an unexpected packet format

由于意外的数据包格式,握手失败

解决方案: 暂无, 记程序日志看看。

IMAP接口:

http://www.mimekit.net/docs/html/M_MailKit_Net_Imap_ImapClient_ConnectAsync_1.htm

client.Connect(strIP, mailServer.ServerPort, SecureSocketOptions.Auto, mailConnect.CancellationToken);

参数options:用SecureSocketOptions.Auto,不要用SecureSocketOptions.SslOnConnect

异常解释:

http://www.mimekit.net/docs/html/T_MailKit_Security_SslHandshakeException.htm

SSL/TLS握手期间出错时引发的异常。

发生此异常时,通常意味着您连接的IMAP、POP3或SMTP服务器使用的SSL证书已过期或不受系统信任

通常情况下,邮件服务器将使用自签名证书,而不是使用已由可信证书颁发机构签名的证书。如果系统无法验证邮件服务器的证书,因为该证书未由已知和受信任的证书颁发机构签名,则会发生此异常。

您可以通过提供自定义RemoteCertificateValidationCallback并在客户端的ServerCertificateValidationCallback属性上设置它来解决此问题。

很可能,您希望将服务器证书的指纹与已知值进行比较,并/或提示用户接受证书(类似于您可能看到的web浏览器在遇到不受信任的证书时所做的操作)。

更多参考:

https://www.limilabs.com/blog/the-handshake-failed-due-to-an-unexpected-packet-format

由于意外的数据包格式,握手失败

很可能您的服务器需要显式SSL,有时也称为TLS。

它被称为显式SSL模式,因为在建立连接之后,客户机显式地向服务器发出一个命令,以启动SSL/TLS协商。

这与隐式SSL模式不同,在隐式SSL模式中,SSL协商是在成功连接之后启动的。在隐式模式下,服务器和客户机知道使用SSL,因为客户机使用默认协议端口,这通常用于安全通信。

首先尝试不使用SSL连接到服务器:

client.Connect("mail.example.com");

然后,在登录之前,启动显式SSL协商。不同协议的命令名不同:

显式SSL(又名TLS

无论使用哪种协议(IMAP、POP3或SMTP),代码都完全相同。

client.Connect("mail.example.com");
client.StartTLS();

StartTLS方法与服务器协商安全协议,并使用SSL或TLS保护通道。现在,你的连接安全了。

SSL vs TLS vs STARTTLS

请注意,您的服务器可能根本不需要SSL/TLS。在这种情况下,只需使用Connect方法。

启用的SSL协议

在极少数情况下,“握手失败…”错误可能表示TLS在客户机或服务器上配置不正确。

在显式模式下,可以强制使用SSL v3.0而不是TLS:

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.Connect("mail.example.com");
client.StartTLS();

也可以强制使用SSL v3.0,而不是隐式模式下的TLS:

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.ConnectSSL("mail.example.com");

自签名证书

请记住,可以使用ServerCertificateValidate事件忽略SSL证书错误:

static void Validate(object sender, ServerCertificateValidateEventArgs e)
        {
            const SslPolicyErrors ignoredErrors =
                SslPolicyErrors.RemoteCertificateChainErrors |
                SslPolicyErrors.RemoteCertificateNameMismatch;

            if ((e.SslPolicyErrors & ~ignoredErrors) == SslPolicyErrors.None)
            {
                e.IsValid = true;
                return;
            }
            e.IsValid = false;
        }

        client.ServerCertificateValidate += Validate;
        client.Connect...

10、用客户端发邮件时,已发送 文件夹中有两封一样的邮件

因为 客户端发邮件时,会将邮件保存在已发送,然后同步到服务器。

sentFolder.Open(FolderAccess.ReadWrite); 
UniqueId? uid = sentFolder.Append(message, MessageFlags.Seen);//添加为已读
        if (uid == null)
          {
            uid = sentFolder.UidNext;
            mailInfo.Uid = (uid.Value.Id - 1).ToString();
          }
         else
          {
            mailInfo.Uid = uid.Value.Id.ToString();
          }

163邮箱:网页上有设置,如下,服务器会有一份保存在 已发送。

 

因为 客户端发邮件时,会将邮件保存在已发送,然后同步到服务器,此时163服务器uid返回不为null,假设为100,自己保存的uid=99【网页本身设置的保存】。

所以客户端上点击 已发送时,会将网页上的同步下来。所以就有两封了。

阿里云邮箱:也有设置

但是阿里云邮箱内部有处理, uid 会返回null。通过uid = sentFolder.UidNext;

取到同步上去的邮件的uid,假设为99,自己保存的其实也是99,即同一份。所以本地和远程只会有一份。

11、Mailkit 突然就卡住

在使用过程中出现跑着跑着Mailkit 突然就卡住了,既不报错,也没有异常,就在那卡着,无论是Connect、Login又或者是其他任何操作,即便你设置了imapClient.Timeout、pop3Client.Timeout。

查看了源码之后发现,虽然调用的是同步方法(Mailkit 支持异步),可是Mailkit 内部实际还是用的等待异步来执行的,而异步执行中有一个很重要的参数cancellationToken没有填,因为这个参数支持默认值,一开始我以为既然有默认值,而且同步执行应该可以不需要设置这个参数,设置 Timeout就可以了,事实证明还是太年轻了。cancellationToken、Timeout分管的是两个不同的东西。

解决方法:在每个带有cancellationToken参数的方法中都带上自定义参数

var TokenSource = new CancellationTokenSource(30000);//30s超时
folder.Open(FolderAccess.ReadWrite, cancellationToken: TokenSource.Token);
var msg = folder.GetMessage(Index, cancellationToken: TokenSource.Token); 
folder.Close(cancellationToken: TokenSource.Token);
imapClient.Identify(clientImplementation, cancellationToken: TokenSource.Token);

IMAP、Pop3都需要!!!设置好之后再也没有出现卡住不动的情况

POP3

1、Pop3Client.SupportsUids

获取Pop3Client是否支持通过UID引用消息

说明:并非所有服务器都支持通过UID引用消息,因此应在使用GetMessageUid(Int32,CancellationToken)和GetMessageUids(CancellationToken)之前检查此属性。

如果服务器不支持UID,则所有采用UID参数以及GetMessageUid(Int32,CancellationToken)和GetMessageUids(CancellationToken)的方法都将失败。

2、Pop3Client.Count

获取POP3服务器上可用消息的数量。

一旦通过身份验证,Count属性将设置为POP3服务器上可用消息的数量。

3、Pop3Client.GetMessageHeaders(IList<Int32>, CancellationToken)

获取指定索引处的邮件头

当POP3服务器支持管道扩展时,此方法可能比为每条消息使用GetMessageHeaders(Int32,CancellationToken)更为有效,因为它将批量处理命令以减少延迟。

枚举:Pop3Capabilities Enumeration

Pipelining:服务器支持PIPELINING扩展,从而允许客户端一次向服务器批处理多个请求。

4、Pop3Client.GetMessageUidsAsync

异步获取可用消息UID的完整列表

说明:并非所有服务器都支持UID,因此您应该首先检查UIDL标志的Capabilities属性或SupportsUids便利属性。

SMTP

1、SendMail throw a exception:MailKit.Net.Smtp.SmtpCommandException: RCPT (xx@yyy.com) dosn't exist

意思是 收件人地址在服务器上已经不存在

RCPT是 SMTP协议里的一个命令,用于标识邮件的收件人;以 RCPT TO: 的形式使用

注意

1、163邮箱收件时间 

默认设置了 收取最近30天邮件。

2、回执

阿里云网页版邮箱的收信设置

  • 始终不发送回执:不管发件方是否 勾选了 要发送回执,收到邮件后都不用发回执
  • 始终发送回执:要对方勾选了已读回执,才会发已读回执。
  • 有需要时发送回执:收到邮件后,会提示你发送回执与否,按钮:“确定”、“取消”

3、163邮箱采用IMAP协议收取邮件时,报错登录不安全

报错内容: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解决办法

4、163邮箱采用IMAP发送邮件失败,DT:SPM 163 smtp X

网上看说是 邮件被163当做垃圾邮件了,这时一般会接到163的退信 邮件。

554 DT:SPM smtp3 退信解决办法
出现提示:DT:SPM smtp1, ********–.******** http://mail.163.com/help/help_spam_16.htm?ip=********&hostid=smtp1&time=********
DT:SPM 是出错信息的关键词,可以在这个网页中找到出错的原因http://help.163.com/09/1224/17/5RAJ4LMH00753VB8.html
原因:550 DT:SPM 邮件正文带有很多垃圾邮件特征或发送环境缺乏规范性。需调整邮件内容或优化发送环境;
原因分析
邮件中带有敏感关键词,例如促销,发票等。
邮件中包含超级链接,或者超级链接太多。
垃圾邮件特征比较明显,例如:只有一张图片,或只有一张图片。
发送相同的邮件内容太多了。处理这种情况的方法是:换其他邮箱发送,或调整邮件内容。

再者,还可以将 发件人自己加入到 抄送中。

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