zoukankan      html  css  js  c++  java
  • 使用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解决办法

  • 相关阅读:
    浅析Java8新特性-Optional方法介绍(Optional.ofNullable-构造方法、map-逐层安全地拆解value、filter-过滤值、orElse/orElseThrow-最终返回、stream-转为流)及常用实践(仅作为方法返回值、清晰地表达返回值中没有结果的可能性、勿滥用Optional)、Optional的设计思想实现
    浅析Java8新特性-新的日期和时间API:起初时间存在的问题(非线程安全、设计乱、时区处理麻烦)、日期时间(LocalDate/LocalTime/LocalDateTime)、时间戳(Instant)、时间差(Duration/Period)、TemporalAdjuster时间矫正器、LocalDateTime 与 Date 互转、时间先后判断/MonthDay类的使用(生日检查)
    浅析 Employee::new / String[]::new 是什么意思?Java8新特性-方法引用、构造器引用、数组引用的了解及使用
    浅析HTTP的Referer含义理解、Referer作用(记录访问来源、防盗链、防止恶意请求)、Referrer Policy引用策略的9个值及设置用法、什么情况会导致空Referer
    浅析Java8新特性-四大内置核心函数式接口 :消费型Consumer(void accept)、供给型Supplier(T get)、函数型Funciton(R apply)、断言型Predicate(boolean test)
    浅析Java Lambda表达式、如何理解Lambda、如何使用Lambda简化代码(结合stream处理遍历、配合Optional优雅的处理null情况)
    【原文】linux systemctl命令详解
    [转]解决Ubuntu DNS覆盖写入127.0.0.53
    mysql 安装
    【转】lvextend 无法使用 virtualbox 扩容的vdi
  • 原文地址:https://www.cnblogs.com/peterYong/p/12024394.html
Copyright © 2011-2022 走看看