65.9K
CodeProject 正在变化。 阅读更多。
Home

在 C# / VB.NET 中创建和发送电子邮件回复

starIconstarIconstarIconstarIconstarIcon

5.00/5 (34投票s)

2016年6月15日

CPOL

7分钟阅读

viewsIcon

84876

downloadIcon

4516

C# 和 VB.NET 控制台应用程序,演示如何在 .NET Framework 中使用 IMAP 和 SMTP 协议创建和发送电子邮件回复

您可以在此处下载演示 .NET 应用程序的最新版本,包含 C# 或 VB.NET 源代码

(上次更新于 2016-06-10)

引言

前段时间,我需要一个使用 C# 发送电子邮件回复的解决方案。在网上搜索没有找到任何可用的示例,所以我需要自己研究。我写这篇文章是希望它能帮助那些试图在 .NET 应用程序中以编程方式创建和发送电子邮件回复的人。这主要是为了让您省去阅读 RFC 文档和理解电子邮件消息格式 (MIME) 的麻烦,作为 GemBox.Email 组件的开发人员,我在这方面积累了相当多的知识。

电子邮件回复标准

MIME 标准扩展了原始的纯 ASCII 文本电子邮件格式,以支持 ASCII 以外的字符集、非文本附件和多部分消息正文。尽管该标准非常古老,但它仍在广泛使用,并且如今大多数电子邮件消息仍以这种格式发送和接收。

该格式在一些 RFC 文档中指定,但由于每个文档都已更新,我花了一段时间才找到有关编写回复消息的信息。RFC 2822 定义了“识别字段”,更广为人知的是邮件头,需要在电子邮件回复中定义。要使电子邮件消息符合回复的条件,它需要包含“In-Reply-To”和“References”邮件头。

该标准还规定,您应该在电子邮件主题前加上“Re:”前缀(源自拉丁语“res”,意为“关于”),尽管这不是强制性的。

电子邮件消息正文如何?

令我惊讶的是,RFC 2822 中的电子邮件消息回复示例不包含原始电子邮件消息正文,尤其是我收件箱中的所有电子邮件回复都包含。这是因为大多数常用电子邮件客户端默认启用此选项,但可以禁用。

尽管没有标准定义纯文本消息中引用的格式,但“>”符号是最常用的引用前缀字符。基本上,您在原始消息文本的每一行前加上一个“>”符号。您可以在此处阅读更多相关信息。对于 HTML 消息正文,根本没有标准,甚至没有约定。您可以自由地以任何您认为合适的方式包含原始文本。大多数电子邮件客户端使用 <blockquote> 标签,但有些使用带自定义样式的 <div> 标签。

Reply message screenshot

电子邮件回复截图

C# / VB.NET 代码

既然我们对这个主题有了一些了解,我们就可以看看代码了。

static void Main(string[] args)
{
    // Download unread messages from the server
    IEnumerable<MailMessage> messages = GetMessages();
    if (messages != null)
    {
        Console.WriteLine(messages.Count().ToString() + " new message(s).");

        // Create message replies
        List<MailMessage> replies = new List<MailMessage>();
        foreach (MailMessage msg in messages)
        {
            replies.Add(CreateReply(msg));
            msg.Dispose();
        }

        // Send replies
        SendReplies(replies);
    }
    else
    {
        Console.WriteLine("No new messages.");
    }

    Console.WriteLine("Press any key to exit...");
    Console.ReadKey();
}
Sub Main()
    ' Download unread messages from the server
    Dim messages As IEnumerable(Of MailMessage) = GetMessages()
    If messages IsNot Nothing Then
        Console.WriteLine(messages.Count().ToString() & " new email message(s).")

        ' Create message replies
        Dim replies As New List(Of MailMessage)()

        For Each msg As MailMessage In messages
            replies.Add(CreateReply(msg))
            msg.Dispose()
        Next

        ' Send replies
        SendReplies(replies)
    Else
        Console.WriteLine("No new email messages.")
    End If

    Console.WriteLine("Press any key to exit...")
    Console.ReadKey()
End Sub

Main 方法首先检查服务器上是否有任何新的未读电子邮件。如果 GetMessages 返回一个非 null 的电子邮件枚举,则会枚举结果,并使用 CreateReply 方法为每个项目创建回复消息。由于原始电子邮件消息不再需要,因此在创建回复后会将其释放。最后,回复会传递给 SendReplies 方法,该方法将使用 System.Net.SmtpClient 发送它们。由于这是一个控制台应用程序,因此会在控制台窗口上显示进度信息。

使用 IMAP 客户端读取电子邮件

有两种标准电子邮件协议可用于获取电子邮件消息,即 POP 和 IMAP。不幸的是,.NET 框架不提供对它们的支持。它只支持 SMTP,只能用于发送电子邮件。以下代码使用 S22.Imap,这是一个第三方 IMAP 客户端,用于从服务器下载消息。

private static IEnumerable<MailMessage> GetMessages()
{
    using (ImapClient client = new ImapClient(imapHost, 993, true))
    {
        Console.WriteLine("Connected to " + imapHost + '.');

        // Login
        client.Login(imapUser, imapPassword, AuthMethod.Auto);
        Console.WriteLine("Authenticated.");

        // Get a collection of all unseen messages in the INBOX folder
        client.DefaultMailbox = "INBOX";
        IEnumerable<uint> uids = client.Search(SearchCondition.Unseen());

        if (uids.Count() == 0)
            return null;

        return client.GetMessages(uids);
    }
}
Private Function GetMessages() As IEnumerable(Of MailMessage)
    Using client As New ImapClient(imapHost, 993, True)
        Console.WriteLine("Connected to " & imapHost & "."c)

        ' Login
        client.Login(imapUser, imapPassword, AuthMethod.Auto)
        Console.WriteLine("Authenticated.")

        ' Get a collection of all unseen messages in the INBOX folder
        client.DefaultMailbox = "INBOX"
        Dim uids As IEnumerable(Of UInteger) = client.Search(SearchCondition.Unseen())

        If (uids.Count = 0) Then Return Nothing

        Return client.GetMessages(uids)
    End Using
End Function

S22.Imap 使检索新消息成为一项微不足道的任务。首先,您需要初始化一个新的 ImapClient,它连接到服务器。下一步是身份验证,通过使用邮箱用户名、密码和身份验证方法作为参数调用 Login 方法来执行。使用 AuthMethod.Auto,因为它将选择服务器支持的最佳身份验证方法。

下一步是将“INBOX”设置为默认邮箱,并查询服务器以获取所有未读消息。该方法返回消息 ID 的枚举。如果该枚举不为空,则调用 GetMessages 方法,该方法将返回给定消息 ID 的 System.Net.Mail.MailMessage 实例的枚举。

创建回复消息

下载所有未读消息后,通过在循环中调用 CreateReply 方法来创建回复,如以下代码片段所示

// Create message replies
List<MailMessage> replies = new List<MailMessage>();
foreach (MailMessage msg in messages)
{
    replies.Add(CreateReply(msg));
    msg.Dispose();
}
' Create message replies
Dim replies As New List(Of MailMessage)()

For Each msg As MailMessage In messages
    replies.Add(CreateReply(msg))
    msg.Dispose()
Next

CreateReply 方法首先创建一个新的邮件消息,其中发件人地址和收件人地址已互换。

MailMessage reply = new MailMessage(new MailAddress(imapUser, "Sender"), source.From);
Dim reply As New MailMessage(New MailAddress(imapUser, "Sender"), source.From)

source.To 不能用作 from 参数,因为它是一个电子邮件地址集合而不是单个地址,并且可能包含多个地址,因此您最终可能会得到错误的地址。在示例代码中,我使用了为 IMAP 服务器身份验证定义的用户名。使用您的凭据时,请务必指定用户的完整电子邮件地址作为用户名,否则它将无法按预期工作。

回复消息需要做的第一件事是添加所需的邮件头。如电子邮件回复标准部分所述,这些是“In-Reply-To”和“References”。

string id = source.Headers["Message-ID"];
reply.Headers.Add("In-Reply-To", id);

// Try to get 'References' header from the source and add it to the reply
string references = source.Headers["References"];
if (!string.IsNullOrEmpty(references))
    references += ' ';

reply.Headers.Add("References", references + id);
Dim id As String = source.Headers("Message-ID")
reply.Headers.Add("In-Reply-To", id)

' Try to get 'References' header from the source and add it to the reply
Dim references As String = source.Headers("References")
If Not String.IsNullOrEmpty(references) Then references &= " "c

reply.Headers.Add("References", references & id)

尽管“Message-ID”邮件头不是强制性的,但大多数消息都会包含它,尤其是来自 Gmail、Yahoo、Outlook 等主要提供商的消息。因此,未检查 id 变量。

接下来,如果尚未添加前缀,则在回复主题前加上“Re:”。

// Add subject
if (!source.Subject.StartsWith("Re:", StringComparison.OrdinalIgnoreCase))
    reply.Subject = "Re: ";

reply.Subject += source.Subject;
' Add subject
If Not source.Subject.StartsWith("Re:", StringComparison.OrdinalIgnoreCase) Then
    reply.Subject = "Re: "
End If

reply.Subject &= source.Subject

最后,根据源消息正文类型,组合回复正文。

StringBuilder body = new StringBuilder();
if (source.IsBodyHtml)
{
    body.Append("<p>Thank you for your email!</p>");
    body.Append("<p>We are currently out of the office, 
    but we will respond as soon as possible.</p>");
    body.Append("<p>Best regards,<br/>");
    body.Append(senderName);
    body.Append("</p>");
    body.Append("<br/>");

    body.Append("<div>");
    if (source.Date().HasValue)
        body.AppendFormat
        ("On {0},", source.Date().Value.ToString(CultureInfo.InvariantCulture));

    if (!string.IsNullOrEmpty(source.From.DisplayName))
        body.Append(source.From.DisplayName + ' ');

    body.AppendFormat("<<a href=\"mailto:{0}\">{0}</a>> 
    wrote:<br/>", source.From.Address);

    if (!string.IsNullOrEmpty(source.Body))
    {
        body.Append("<blockqoute style=\"margin: 0 0 0 5px;
        border-left:2px blue solid;padding-left:5px\">");
        body.Append(source.Body);
        body.Append("</blockquote>");
    }

    body.Append("</div>");
}
else
{
    body.AppendLine("Thank you for your email!");
    body.AppendLine();
    body.AppendLine("We are currently out of the office, 
    but we will respond as soon as possible.");
    body.AppendLine();
    body.AppendLine("Best regards,");
    body.AppendLine(senderName);
    body.AppendLine();

    if (source.Date().HasValue)
        body.AppendFormat("On {0}, 
        ", source.Date().Value.ToString(CultureInfo.InvariantCulture));

    body.Append(source.From);
    body.AppendLine(" wrote:");

    if (!string.IsNullOrEmpty(source.Body))
    {
        body.AppendLine();
        body.Append("> " + 
        source.Body.Replace("\r\n", "\r\n> "));
    }	
}

reply.Body = body.ToString();
reply.IsBodyHtml = source.IsBodyHtml;
Dim body As New StringBuilder()
If source.IsBodyHtml Then
    body.Append("<p>Thank you for your email!</p>")
    body.Append("<p>We are currently out of the office, _
    but we will respond as soon as possible.</p>")
    body.Append("<p>Best regards,<br/>")
    body.Append(senderName)
    body.Append("</p>")
    body.Append("<br/>")

    body.Append("<div>")
    If source.Date().HasValue Then body.AppendFormat("On {0}, _
    ", source.Date().Value.ToString(CultureInfo.InvariantCulture))
    If Not String.IsNullOrEmpty(source.From.DisplayName) _
    Then body.Append(source.From.DisplayName & " "c)

    body.AppendFormat("<<a href=""mailto:{0}"">_
    {0}</a>> wrote:<br/>", source.From.Address)

    If Not String.IsNullOrEmpty(source.Body) Then
        body.Append("<blockqoute style=""margin:0 0 0 5px;_
        border-left:2px blue solid;padding-left:5px"">")
        body.Append(source.Body)
        body.Append("</blockquote>")
    End If

    body.Append("</div>")
Else
    body.AppendLine("Thank you for your email!")
    body.AppendLine()
    body.AppendLine("We are currently out of the office, _
    but we will reply as soon as possible.")
    body.AppendLine()
    body.AppendLine("Best regards,")
    body.AppendLine(senderName)
    body.AppendLine()

    If source.Date().HasValue Then body.AppendFormat("On {0}, _
    ", source.Date().Value.ToString(CultureInfo.InvariantCulture))

    body.Append(source.From)
    body.AppendLine(" wrote:")

    If Not String.IsNullOrEmpty(source.Body) Then
        body.AppendLine()
        body.Append("> " & source.Body.Replace(vbCrLf, vbCrLf & ">"c))
    End If
End If

reply.Body = body.ToString()
reply.IsBodyHtml = source.IsBodyHtml

我使用了非常简单的文本作为回复正文,但您可以看到 <blockqoute> 标签用于 HTML 正文,而“>”前缀用于纯文本正文。

使用 SMTP 客户端发送电子邮件

新创建的消息回复使用标准的 .NET SmtpClient 发送。这个过程非常简单,如以下代码所示

using (SmtpClient client = new SmtpClient(smtpHost, 587))
{
    // Set SMTP client properties
    client.EnableSsl = true;
    client.UseDefaultCredentials = false;
    client.Credentials = new NetworkCredential(smtpUser, smtpPassword);
    client.DeliveryFormat = SmtpDeliveryFormat.International;

    // Send
    bool retry = true;
    foreach (MailMessage msg in replies)
    {
        try
        {
            client.Send(msg);
            retry = true;
        }
        catch (Exception ex)
        {
            if (!retry)
            {
                Console.WriteLine("Failed to send reply to " +
                msg.To.ToString() + '.');
                Console.WriteLine("Exception: " + ex.Message);
                return;
            }

            retry = false;
        }
        finally
        {
            msg.Dispose();
        }
    }

    Console.WriteLine("All replies successfully sent.");
}
Using client As New SmtpClient(smtpHost, 587)
    ' Set SMTP client properties
    client.EnableSsl = True
    client.UseDefaultCredentials = False
    client.Credentials = New NetworkCredential(smtpUser, smtpPassword)

    ' Send
    Dim retry As Boolean = True
    For Each msg As MailMessage In replies
        Try
            client.Send(msg)
            retry = True
        Catch ex As Exception
            If Not retry Then
                Console.WriteLine("Failed to send email reply to _
                " & msg.To.ToString() & "."c)
                Console.WriteLine("Exception: " & ex.Message)
                Exit Sub
            End If

            retry = False
        Finally
            msg.Dispose()
        End Try
    Next

    Console.WriteLine("All email replies successfully sent.")

为了防止应用程序在单个消息错误时崩溃,所有异常都被忽略。但是,SMTP 服务器或凭据可能存在错误,这将导致每条消息都失败,因此,我使用了一个非常简单和原始的重试模式。基本上,它使用一个布尔变量在连续两次出现异常时中断发送循环。

Application console screenshot

EmailReply 应用程序控制台窗口截图

替代方案

由于 .NET Framework 不提供从服务器下载电子邮件消息的任何方法,因此本文中提供的代码使用 S22.Imap 组件来完成此任务。顾名思义,该库使用 IMAP 协议与服务器通信。另一个非常常用的协议是 POP,而且由于 POP 是一种更古老、更简单的协议,因此互联网上还有更多适用于它的组件。OpenPop 是最流行的,但 S22.Pop3 也是一个不错的选择,因为它使用 System.Net.Mail.MailMessage 类而不是 OpenPop 使用的自定义消息类。

Gmail 安全

作为最常用的电子邮件提供商之一,Google 的 Gmail 服务具有非常高的安全标准。默认情况下,POP、IMAP 和常见的身份验证机制处于禁用状态。为了能够在本文提供的演示应用程序中使用您的 Gmail 帐户,您需要执行以下操作

为您的帐户启用 POP 或 IMAP 访问

  1. 登录您的 Gmail 帐户,然后从右侧菜单中选择“设置”。

    Gmail setings menu
  2. 选择“转发和 POP/IMAP”选项卡,然后启用 POP 或 IMAP,具体取决于您要使用的协议。

    Forwarding and POP/IMAP settings

允许访问安全性较低的应用

  1. 点击右上角的帐户图标,然后选择“我的帐户”。

    Goggle account menu
  2. 点击“登录与安全”,然后在页面底部将“允许安全性较低的应用:”设置为“开启”。

    Allow less secure apps: ON

结论

本文的目的是为了说明创建电子邮件回复非常简单。您只需添加邮件头:“In-Reply-To”和“References”。其他一切都是可选的。我希望本文和随附的控制台应用程序能帮助您未来的 .NET 电子邮件应用程序。

© . All rights reserved.