在 C# / VB.NET 中创建和发送电子邮件回复
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>
标签。
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 服务器或凭据可能存在错误,这将导致每条消息都失败,因此,我使用了一个非常简单和原始的重试模式。基本上,它使用一个布尔变量在连续两次出现异常时中断发送循环。
替代方案
由于 .NET Framework 不提供从服务器下载电子邮件消息的任何方法,因此本文中提供的代码使用 S22.Imap 组件来完成此任务。顾名思义,该库使用 IMAP 协议与服务器通信。另一个非常常用的协议是 POP,而且由于 POP 是一种更古老、更简单的协议,因此互联网上还有更多适用于它的组件。OpenPop 是最流行的,但 S22.Pop3 也是一个不错的选择,因为它使用 System.Net.Mail.MailMessage
类而不是 OpenPop 使用的自定义消息类。
Gmail 安全
作为最常用的电子邮件提供商之一,Google 的 Gmail 服务具有非常高的安全标准。默认情况下,POP、IMAP 和常见的身份验证机制处于禁用状态。为了能够在本文提供的演示应用程序中使用您的 Gmail 帐户,您需要执行以下操作
为您的帐户启用 POP 或 IMAP 访问
-
登录您的 Gmail 帐户,然后从右侧菜单中选择“设置”。
-
选择“转发和 POP/IMAP”选项卡,然后启用 POP 或 IMAP,具体取决于您要使用的协议。
允许访问安全性较低的应用
-
点击右上角的帐户图标,然后选择“我的帐户”。
-
点击“登录与安全”,然后在页面底部将“允许安全性较低的应用:”设置为“开启”。
结论
本文的目的是为了说明创建电子邮件回复非常简单。您只需添加邮件头:“In-Reply-To”和“References”。其他一切都是可选的。我希望本文和随附的控制台应用程序能帮助您未来的 .NET 电子邮件应用程序。