直接从 Azure 上的 C# .NET 发送电子邮件,无需邮件服务器





5.00/5 (20投票s)
一种在 Azure 上无需邮件服务器即可轻松发送电子邮件的方法,使用 DNS MX 和 SMTP
引言
我编写的大多数系统的后台部分都涉及某种消息传递……通常,默认是各种电子邮件通知。当我需要进行大规模邮件发送时,我总是使用主要的 Azure 服务之一,但当我只需要临时邮件操作或低容量时,我则结合使用 SMTP 客户端和 DNS MX 查找。MX 什么?……查找!他说,查找!:)
快速解释:通常,我们的消息从发件人发送到发件人的邮件服务器,由它处理所有事情……这对应于上面 A -> B -> C -> D。我们的替代方法是丢弃中间服务器“B”,然后直接自己去“C”。
旁注 1:如果您时间有限,不想了解背景,只需下载附件代码,它相当不言自明
旁注 2:虽然我将此代码用于 Azure,但它也应该适用于其他云提供商以及标准 Windows 应用程序。
背景
当我们向朋友“bob@codeproject.com”发送电子邮件时,我们通常将其交给 SMTP 服务器 代发。但它实际上是如何发送的,以及沿途发生了什么?……多年前,我非常愉快地编写了一个自定义邮件服务器——这涉及到创建 POP3 和 SMTP 服务器。在构建它们的过程中,我了解了邮件交换中使用的协议,并了解了存储在 DNS 服务器中的 MX 或“邮件交换”记录。当您打开浏览器并输入“CodeProject.com”作为要访问的地址时,您的系统会做的一件事是访问其本地 DNS(域名服务器),并询问“网站 CodeProject.com 所在的服务器/资源的实际 IP 地址是多少?……根据系统请求的内容,它将仅返回 Web 服务器的 IP 地址,或同时返回其他信息。DNS 不仅存储有关 Web 服务器服务的信息,还存储有关基于 DNS 的网络通信的各种其他信息。DNS 记录可以有许多条目,以下是一些不同类型及其一般用途的示例
- “
A
”记录……返回 32 位 IPv4 地址 - “
CNAME
”……返回另一个记录的别名 - “
MX
”……返回专门处理邮件的 1:n 服务器列表
(如果您想了解更多,这里有不同 DNS 记录类型的列表。)
我们对 MX 或邮件交换记录感兴趣的原因是,它将使我们能够绕过 SMTP 服务器发送消息的需求,直接发送到接收目标电子邮件服务器。为了理解我们如何做到这一点,让我们首先快速了解用于发送电子邮件的协议,然后了解 SMTP 服务器如何使用它来管理邮件传输。
MX 记录查找
作为邮件服务器,当我们有一条要发送的消息时,我们需要找出发送到哪里——仅仅因为某人有一个“outlook.com”地址并不意味着您将其直接发送到托管主域的服务器。要查找 MX 记录,我们需要将根域作为查询发送到 DNS 服务器,并从答案中提取 MX 记录。对于大多数大型邮件主机,将返回多个邮件服务器。您收到的列表可能不一致,并且列出的服务器顺序也可能更改——这有助于负载平衡等。
为了直观地了解其工作原理,我将使用“网络工具”网站上的 DNS 查询工具,特别是 NSLOOKUP
服务:http://network-tools.com/nslook/。
按如下方式输入 CodeProject.com 的查询
现在让我们执行并查看我们得到的第一个结果,特别注意第一个条目、给出的其他替代方案以及它们列出的顺序……
现在让我们再次做同样的事情,并注意变化……我用红色标记了*以前*的顺序/位置。
通常发生的流程是邮件服务器执行 MX 查找,返回结果列表(如上),然后它遍历列表,依次连接到每个列出的邮件服务器,直到其中一个成功接受消息。
我们这里感兴趣的重要部分是 MX 记录的“交换”部分。这告诉我们服务器在哪里,该服务器将接受我们尝试连接的根域(在本例中为 CodeProject.com)的消息。
SMTP 协议
一旦我们知道需要连接的邮件服务器,我们接下来就需要一种一致的方法来连接到服务器并发送我们的消息——我们使用 SMTP 消息协议来完成此操作。
SMTP 代表“简单邮件传输协议”。它最早开发于 1982 年的史前时代——您可以在 IEFT RFC 821 网页上阅读其辉煌。邮件服务器使用 SMTP 传输和接收消息,而电子邮件客户端通常使用 SMTP 将邮件转发到服务器,然后使用 POP3 或 IMAP 从服务器接收消息。SMTP 协议是两个端点之间的“聊天”,它大概是这样的……
客户端:“嘿,我有一条消息要发送给您服务器上的某人……”
服务器:“太好了,给我吧!”
客户端:“给你……<发送消息>”
服务器:“收到,谢谢,我会尽快送达,再见,谢谢来电!”
客户端:“酷,再见!”
该协议有一个商定的标准,用于连接两个端点、传递消息以及再次关闭连接。如果您愿意,您实际上可以使用内置的 Windows 应用程序“telnet”从命令行发送电子邮件!……让我们稍作旁路,看看那个,因为它很有趣!
Telnet 是一个命令行工具,允许您连接到远程计算机。它通常在 Windows 中默认关闭——要打开它,请打开控制面板,然后添加/删除程序并“打开或关闭 Windows 功能”——在这里查找“telnet 客户端”——确认复选框已选中并关闭模式窗口——它应该会安装,然后您就可以使用了。
要使用 telnet 并连接到邮件服务器,请打开一个命令窗口。在提示符下,输入 telnet,然后是您尝试联系的“交换”名称,然后是邮件服务器的端口号。这通常是端口 25。
telnet aspmx3.googlemail.com 25
连接后,您将看到以下内容
我们已连接到邮件服务器,它正在等待我们的第一个命令。我们通过说“Helo
”(不,这不是拼写错误!)并宣布我们是谁来告诉它我们是谁……
邮件服务器通过代码“250
”回应我们,并表示它已准备好接收下一个命令,所以现在我们可以进行整个对话,发送我们的消息并退出
另一方面,当我检查电子邮件时,我可以看到消息进来了
所以,在幕后,电子邮件就是这样发送的,但这并不是全部,而这正是 SMTP 邮件服务器的全部功能发挥作用的地方。
SMTP 服务器
SMTP 服务器通常集成到完整的邮件服务器中,负责接收来自其他系统的邮件,以及如果收到的邮件是外部地址,则将其发送到与这些地址关联的域。因此,如果一个服务器处理域“codeproject.com”,我们向“bob@codeproject.com”发送邮件,它将内部存储,直到 Bob 收集该邮件。另一方面,如果我是 Bob,并向该服务器发送一条消息给“anjit@microsoft.com”,那么 SMTP 服务器将接收 Bob 的邮件,并负责连接到 Microsoft 的 SMTP 服务器,并将 Anjit 的邮件发送到该 Microsoft 服务器。在接收和发送邮件时,SMTP 服务器实现了 SMTP 协议(如上)。但这并非 SMTP 服务器的全部故事……它不仅仅是将邮件从一个地方发送到另一个地方。
通常,SMTP 服务器将负责电子邮件消息从一个地方到另一个地方的正确路由。这意味着,如果它接受来自 Bob@codeproject 发送给 Anjit@microsoft 的消息,但由于某种原因*无法*连接并将消息传递到 Microsoft 邮件服务器,那么它将负责存储消息一段时间,然后重试 X 次,如果所有重试都失败,它将向发件人发送一条消息通知他们。还有更多,例如存储和转发、检查垃圾邮件、病毒等。当然,有人会说这些功能并非*严格*属于 SMTP 服务器的领域,而是相关的支持/管理服务。我同意,但是,这样说的目的是为了表明发送邮件不仅仅是简单地连接并期望一切都在第一次连接时顺利进行。
这就引出了这一切的重点——我们如何利用从 DNS 记录的 MX 记录中获取的信息,并将其与 SMTP 客户端结合起来,然后使用它来创建我们自己的简单邮件传输机制。
工作流
在我们的 C# 应用程序中无需中间服务器即可发送电子邮件的过程非常简单,涉及几个简单的步骤
- 从您希望发送消息的电子邮件地址中提取域名
- 连接到 DNS 服务器并查询域名以提取“mx 记录”
- 使用简单的 SMTP 类连接到“mx 记录”中给出的 SMTP 邮件服务器,并发送电子邮件
安装
感谢开源和包管理器的奇妙世界,我们需要将我们的解决方案整合在一起并使其运行所需的一切都可以在 NuGet 上找到。启动一个新的 Visual Studio 项目,并将以下包添加到您的项目中
- DNS 客户端 - http://dnsclient.michaco.net/
- MailKit - https://github.com/jstedfast/MailKit
Using the Code
首先,在您的 USING
部分导入一些库
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DnsClient;
using MailKit.Net.Smtp;
using MailKit;
using MimeKit;
我的代码示例演示了在一个简单的 MVC 项目中如何工作,但它可以在任何地方使用。
在此示例中,我将向我的 gmail 帐户发送一封测试电子邮件……
MX 查找
我们的初始代码设置了一个新的 MX 'Lookup
' 客户端……
var client = new LookupClient();
然后,我们使用 'MX
' 查询参数查询目标域 'gmail.com'。
var result = client.Query("gmail.com", QueryType.MX);
客户端查询返回一个“答案记录”集合,其中应列出一个或多个“交换邮件”服务器的详细信息。我们的任务是遍历这些记录并尝试发送我们的消息。如果成功,我们可以跳出迭代循环,因为任务已完成;否则,我们可以处理失败。
因此,在查询 DNS 服务器获取 MX 记录后,让我们进入发送尝试循环……
foreach (var x in result.Answers)
{
var X = x as DnsClient.Protocol.MxRecord;
var y = X.Exchange;
这里需要注意的关键部分是:
- 我们遍历从 DNS 服务器收到的 ANSWERS
- 我们将其类型转换为 MX 记录
- 我们提取记录的 EXCHANGE 部分——这是我们将连接的*实际目标邮件服务器*
现在我们有了邮件服务器信息,我们需要构建我们的消息并尝试发送它。我们使用之前加载的 MailKit
库来完成此操作。
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Mr Test", "me@myaddress.co.uk"));
message.To.Add(new MailboxAddress("Mrs Recipient Name", "them@gmail.com"));
message.Subject = "Weekly report from system";
message.Body = new TextPart("plain")
{
Text = @"Hi there, your weekly test message is available here"
};
要发送消息,我们创建一个 SMTP 客户端,并将我们的消息附加到它以进行发送
try
{
using (var smclient = new SmtpClient())
{
// For demo-purposes, accept all SSL certificates
// (in case the server supports STARTTLS)
smclient.ServerCertificateValidationCallback = (s, c, h, e) => true;
smclient.Connect(y, 25, false);
// Note: only needed if the SMTP server requires authentication
//mclient.Authenticate("joey", "password");
smclient.Send(message);
smclient.Disconnect(true);
break; // assuming we get to this point, we have sent the message... else will fail
}
}
我们这里感兴趣的主要代码是“connect
”和“send
”。请注意,当我们连接时,我们告诉 smtpClient
从 MX 查找接收到的服务器主机/邮件交换 ('y'),并告知它 SMTP 主机在端口 25 上。
一旦连接,我们发送消息,然后断开连接。假设一切顺利,我们就会跳出循环,否则就会遇到异常并再次从循环顶部开始。
就是这样!……我已经在控制台应用程序、桌面应用程序、Azure 中的 Web 应用程序以及 Azure 上的函数代码中对此进行了测试。和往常一样,这是示例代码,您需要自己使其健壮并投入生产。
此处提供的解决方案通常适用于低流量消息。如果您需要更可靠、更快的消息,以通过反垃圾邮件过滤器等,您应该考虑使用商业云邮件提供商。
示例代码已附在文章中 - 请登录并根据需要下载和使用。像往常一样,如果您觉得这篇文章有用,请考虑在页面顶部给它一个星级评分!
历史
- 2018 年 2 月 4 日:版本 1