ASP.NET MailKit SMTP响应

Vas*_*all 0 asp.net mailkit

我有此代码已成功发送电子邮件-除了前一天没有发送电子邮件。因此,我想检查SMTP响应,但不确定该怎么做。

现在是我的代码:

using (var client = new SmtpClient())
{
  client.LocalDomain = "xxxxxxxxxxxx";
  await client.ConnectAsync("xxxxxxxxxxxx", xx, SecureSocketOptions.StartTls).ConfigureAwait(false);
  await client.AuthenticateAsync( new System.Net.NetworkCredential("xxxxxxxxxxxx", "xxxxxxxxxxxx")).ConfigureAwait(false);
  await client.SendAsync(emailMessage).ConfigureAwait(false);
  await client.DisconnectAsync(true).ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

因此,我在这里阅读了onMessageSent,或者可以使用MessageSent函数来查看是否有响应-我真的很想看一个示例代码,但是如何在代码中使用这些函数来确定消息是否为真的收到了吗?

我确实具有包含作为的异步发送功能的函数public void,并且警告抑制消除了有关未等待呼叫的VisualStudio投诉。

public void SendEmail(string HtmlEmailContents, string SubjectLine, string CustomFromAddress = null, string CustomEmailRecipients = null)
{
  string To = getRecipientString(mainRecipients);
  string Cc = getRecipientString(ccRecipients);
  string Bcc = getRecipientString(bccRecipients);
  if(CustomEmailRecipients != null && CustomEmailRecipients != "")
  {
    To = CustomEmailRecipients;
    Cc = "";
    Bcc = "";
  }
  string finalizedFromAddress;
  if(CustomFromAddress != null)
  {
    finalizedFromAddress = CustomFromAddress;
  }
  else
  {
    finalizedFromAddress = FromAddress;
  }
  #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  MessageServices.SendEmailAsync(
    To,
    finalizedFromAddress,
    Cc,
    Bcc,
    SubjectLine,
    HtmlEmailContents
  );
  #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Run Code Online (Sandbox Code Playgroud)

[新编辑]:因此,让我们想象一下,我整理了整个异步内容,现在是时候真正捕获那些错误消息了。在这里,我已阅读到MessageSent和OnMessageSent函数可用于查看结果。当然,我无法弄清楚。我在这里寻找一些示例,可以使用MailKit进行镜像。在那里,该行client.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);似乎抓住了关键,我想知道是否在我的代码中使用client.MessageSent += ???MailKit中的对应项。

jst*_*ast 5

@mason似乎已就滥用异步问题回答了原始问题。

因此,现在回答您的新问题。

MessageSent事件就像是在.NET中的任何其他事件,可以听取了以下方法:

client.MessageSent += OnMessageSent;
Run Code Online (Sandbox Code Playgroud)

OnMessageSent方法是这样的:

void OnMessageSent (object sender, MessageSentEventArgs e)
{
    Console.WriteLine ("The message was sent!");
}
Run Code Online (Sandbox Code Playgroud)

但是,您想收听此事件的原因似乎是对它真正为您提供的信息的误解。

是的,虽然MessageSentEventArgs.Response属性包含服务器发送的实际响应,但是不太可能告诉您收件人电子邮件地址是否实际存在。

如果您正在将邮件发送到不存在的电子邮件地址,SmtpClient.Send()或者SendAsync()没有引发异常,则意味着SMTP服务器在收到RCPT TOMailKit发送的命令时很可能不验证电子邮件地址是否存在,并且会很乐意接受没有错误的消息提交,这意味着MailKit将不会引发任何异常。许多SMTP服务器这样做有两个原因:

  1. 保护用户的匿名性(垃圾邮件发送者不能使用暴力手段来弄清楚用户的帐户名是什么,这是男人禁用VRFYand EXPN命令的相同原因)。
  2. 电子邮件地址的懒惰查找-即SMTP服务器实际上不会查找电子邮件地址的存在,直到它继续将邮件转发到适当的域。

例如,如果您连接到smtp.gmail.com以向另一域上的用户发送消息,则smtp.gmail.com无法知道user@another-domain.com在实际存在之前不存在尝试将邮件转发到例如smtp.another-domain.com。

如果您实际上想获得有关电子邮件地址是否实际存在的反馈,则此过程将需要您付出更多的努力,并需要一些运气。

运气。

首先,您需要希望并祈祷您的SMTP服务器支持DSN(传递状态通知)扩展。

要检查服务器是否支持此功能,可以检查SmtpClient.Capabilities

if (client.Capabilities.HasFlag (SmtpCapability.Dsn)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

的努力。

假设你的服务器支持DSN扩展,接下来你需要继承SmtpClient,这样就可以覆盖,以提供MailKit的SmtpClient一些必要的信息/选项的一些方法。

这些方法是:

  1. GetDeliveryStatusNotifications
  2. GetEnvelopeId

这两种方法的文档已经提供了以下代码片段,但为了后代,我将其粘贴在此处:

public class DSNSmtpClient : SmtpClient
{
    public DSNSmtpClient ()
    {
    }

    /// <summary>
    /// Get the envelope identifier to be used with delivery status notifications.
    /// </summary>
    /// <remarks>
    /// <para>The envelope identifier, if non-empty, is useful in determining which message
    /// a delivery status notification was issued for.</para>
    /// <para>The envelope identifier should be unique and may be up to 100 characters in
    /// length, but must consist only of printable ASCII characters and no white space.</para>
    /// <para>For more information, see rfc3461, section 4.4.</para>
    /// </remarks>
    /// <returns>The envelope identifier.</returns>
    /// <param name="message">The message.</param>
    protected override string GetEnvelopeId (MimeMessage message)
    {
        // Since you will want to be able to map whatever identifier you return here to the
        // message, the obvious identifier to use is probably the Message-Id value.
        return message.MessageId;
    }

    /// <summary>
    /// Get the types of delivery status notification desired for the specified recipient mailbox.
    /// </summary>
    /// <remarks>
    /// Gets the types of delivery status notification desired for the specified recipient mailbox.
    /// </remarks>
    /// <returns>The desired delivery status notification type.</returns>
    /// <param name="message">The message being sent.</param>
    /// <param name="mailbox">The mailbox.</param>
    protected override DeliveryStatusNotification? GetDeliveryStatusNotifications (MimeMessage message, MailboxAddress mailbox)
    {
        // In this example, we only want to be notified of failures to deliver to a mailbox.
        // If you also want to be notified of delays or successful deliveries, simply bitwise-or
        // whatever combination of flags you want to be notified about.
        return DeliveryStatusNotification.Failure;
    }
}
Run Code Online (Sandbox Code Playgroud)

好的,既然您已完成上述操作,这将要求SMTP服务器在服务器无法将邮件传递给任何收件人时/向其发送电子邮件。

现在您可以处理收到的电子邮件了...

当你收到这些消息之一,它将有一个顶级Content-Typemultipart/report; report-type="delivery-status"将由一个代表MultipartReport

检测这种情况的方法是:

var report = message.Body as MultipartReport;
if (report != null && report.ReportType != null && report.ReportType.Equals ("delivery-status", StringComparison.OrdinalIgnoreCase)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

那么你需要做的是一个定位的MIME部(S)Content-Typemessage/delivery-status属于儿童multipart/report(每将由表示MessageDeliveryStatus):

foreach (var mds in report.OfType<MessageDeliveryStatus> ()) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要检查StatusGroups以提取所需的信息。该StatusGroups属性HeaderListCollection本质上是a键值对列表的列表。

要弄清楚什么键可用,你需要在阅读第2.2第2.3节rfc3464

至少,您需要"Original-Envelope-Id"在第一个StatusGroup中检查,才能弄清楚该报告所针对的消息(此信封ID字符串将与您返回的字符串匹配GetEnvelopeId)。

var envelopeId = mds.StatusGroups[0]["Original-Envelope-Id"];
Run Code Online (Sandbox Code Playgroud)

在以下每个StatusGroups中,您需要获取的值"Original-Recipient"(如果已设置,否则我想您可以检查"Final-Recipient")。这将是形式rfc822;user@domain.com-因此只需将';'字符拆分并使用第二个字符串即可。

最后,您将需要检查该"Action"值以了解所述接收者的状态。在您的情况下,如果值为"failed",则表示传送失败。

for (int i = 1; i < mds.StatusGroups.Length; i++) {
    var recipient = mds.StatusGroups[i]["Original-Recipient"];
    var action = mds.StatusGroups[i]["Action"];

    if (recipient == null)
        recipient = mds.StatusGroups[i]["Final-Recipient"];

    var values = recipient.Split (';');
    var emailAddress = values[1];

    ...
}
Run Code Online (Sandbox Code Playgroud)

如果将所有内容放在一起,则会得到以下内容:

public void ProcessDeliveryStatusNotification (MimeMessage message)
{
    var report = message.Body as MultipartReport;

    if (report == null || report.ReportType == null || !report.ReportType.Equals ("delivery-status", StringComparison.OrdinalIgnoreCase)) {
        // this is not a delivery status notification message...
        return;
    }

    // process the report
    foreach (var mds in report.OfType<MessageDeliveryStatus> ()) {
        // process the status groups - each status group represents a different recipient

        // The first status group contains information about the message
        var envelopeId = mds.StatusGroups[0]["Original-Envelope-Id"];

        // all of the other status groups contain per-recipient information
        for (int i = 1; i < mds.StatusGroups.Length; i++) {
            var recipient = mds.StatusGroups[i]["Original-Recipient"];
            var action = mds.StatusGroups[i]["Action"];

            if (recipient == null)
                recipient = mds.StatusGroups[i]["Final-Recipient"];

            // the recipient string should be in the form: "rfc822;user@domain.com"
            var index = recipient.IndexOf (';');
            var address = recipient.Substring (index + 1);

            switch (action) {
            case "failed":
                Console.WriteLine ("Delivery of message {0} failed for {1}", envelopeId, address);
                break;
            case "delayed":
                Console.WriteLine ("Delivery of message {0} has been delayed for {1}", envelopeId, address);
                break;
            case "delivered":
                Console.WriteLine ("Delivery of message {0} has been delivered to {1}", envelopeId, address);
                break;
            case "relayed":
                Console.WriteLine ("Delivery of message {0} has been relayed for {1}", envelopeId, address);
                break;
            case "expanded":
                Console.WriteLine ("Delivery of message {0} has been delivered to {1} and relayed to the the expanded recipients", envelopeId, address);
                break;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)