.NET 发送电子邮件的最佳方法(System.Net.Mail 有问题)

Posted

技术标签:

【中文标题】.NET 发送电子邮件的最佳方法(System.Net.Mail 有问题)【英文标题】:.NET Best Method to Send Email (System.Net.Mail has issues) 【发布时间】:2010-10-30 03:16:51 【问题描述】:

这似乎很简单。我需要从一些 ASP.NET 应用程序发送电子邮件。我需要始终如一地做到这一点,而不会出现奇怪的错误,也不会出现 CPU 利用率过高的情况。我不是在谈论群发电子邮件,只是偶尔发送电子邮件。

System.Net.Mail 似乎被严重破坏了。 SmtpClient 不发出 Quit 命令(可能是因为 Microsoft(R) 对以下规范不感兴趣),因此连接保持打开状态。因此,如果有人尝试在该连接最终关闭之前发送电子邮件,您可能会从 SMTP 服务器收到关于打开的连接过多的错误。这是 Microsoft(R) 对修复完全不感兴趣的错误。见这里:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=146711

另外,如果您环顾四周,建议使用此代码来解决此问题:

smtpClient.ServicePoint.MaxIdleTime = 1;
smtpClient.ServicePoint.ConnectionLimit = 1;

好的,是的,这确实“解决”了连接保持打开状态的问题。但是,如果您愿意,这始终如一地在服务器上尝试,这会导致运行进程(在本例中为 w3wp.exe)的 CPU 跳跃并保持在 100%,直到您的应用程序池被回收。无论出于何种原因,运行 mscorwks.dll!CreateApplicationContext 的线程都是罪魁祸首。

这有一个非常好的副作用,如果您在一个对持续 100% CPU 使用不满意的 Web 主机上运行,​​您将禁用您的应用程序池。因此,这并不像某些人建议的那样微不足道。

所以我的问题是怎么办?我需要做的就是这么简单;然而,让那些“打开的连接太多”错误是不可接受的,100% 的 CPU 使用率也是不可接受的。我不想购买第三方组件,不是因为我便宜,而是我购买了足够多的组件和 MSDN 订阅,以至于不得不为简单的 SMTP 功能支付 100 至 300 美元似乎很疯狂。

我读到将 MaxIdleTime 设置得更高会有所帮助,但我对此表示怀疑。我不想因为 Microsoft 不想遵循 SMTP 规范而冒险禁用我的应用程序池。

编辑:我查看了 quiksoft.com 的组件,但它不支持 SMTP 身份验证,而且价格为 500 美元。一定有办法解决这个问题。

【问题讨论】:

根据您提供的链接,该错误似乎已得到修复。您是否可能缺少服务包? 我看到上面写着“已解决”。但是我没有看到任何其他注释。我已将服务器更新到最新的 SP 以及所有这些。当我以前看到它时,通常似乎 MS 只是意味着他们不会修复它。他们还列出了我提到的“解决方法”。可能会更清楚。 【参考方案1】:

在 .NET 4.0 中,SmtpClient 现在是一次性的。 SMTP QUIT 命令在处置时发出,例如在 using 块中使用时。

【讨论】:

这个是正确答案,我自己测试过。请为此评论 +1。 这是正确的答案,你可以在 using 块中使用 SmtpClient。 我试过这个,但它似乎不像宣传的那样工作。我仍然收到“#4.x.2 Too many messages for this session”错误。其他人有同样的问题吗? 将 SmtpClient 的处置(正确)与亚马逊的 AWS SES 结合使用,为我解决了大量频繁的发送错误。就我而言,代码最初是在 .NET 2.0 下构建的,升级到 4.x,然后迁移到 AWS。 SMTP 主机被换成了 SES,并且由于 smtp 会话保持打开状态,它抛出了各种错误。一个简单的 using 块解决了它。 这个问题在 .NET Core 库中被重新引入,即使文档错误地声明它将发送 QUIT 命令。有关详细信息,请参阅github.com/dotnet/corefx/issues/34868。【参考方案2】:

我们使用hMailserver 取得了巨大成功。该配置可能需要一段时间才能习惯,但它是一款很棒的免费邮件服务器产品。

如果您想推出自己的产品(几年前我在 CDONTS 玩得很开心时就这样做了),您可以从下面的代码开始,并根据自己的喜好进行定制。它使用 TcpClient 直接创建到邮件服务器的 TCP 连接。当有这么多已建立和调试的解决方案时,我不建议这样做,但我发现这对于调试和确定预制 MS 邮件组件的问题非常有用。

    private void Send_Email() 
    
        #region Email Sending Function
        string strMail = "";

        try 
        
            // See RFC821 http://www.faqs.org/rfcs/rfc821.html for more specs
            // TcpClient is an abstraction of a TCP Socket connection
            TcpClient myTCP = new TcpClient();

            // Connect to the mail server's port 25
            myTCP.Connect(mailserver, 25);

            // Open a network stream which sends data to/from the TcpClient's socket
            System.Net.Sockets.NetworkStream ns = myTCP.GetStream();

            // The data to send to the mail server, basically a raw SMTP mail message
            strMail = "HELO\n";
            strMail += "MAIL FROM:from@address.com\n";
            strMail += "RCPT TO:" + recipient + "\n";
            strMail += "DATA\n";
            strMail += "Subject: mySubject\n";
            strMail += "To:" + recipient + "\n";
            strMail += "From: \"From Real Name\" <from@address.com>\n";
            strMail += "\n";
            strMail += " ---------------------------------------\n";
            strMail += "Name:     " + txtName.Text + "\n";
            strMail += "Address1: " + txtAddress1.Text + "\n";
            strMail += "Address2: " + txtAddress2.Text + "\n";
            strMail += "City:     " + txtCity.Text + "\n";
            strMail += "State:    " + txtState.Text + "\n";
            strMail += "Zip:      " + txtZip.Text + "\n";
            strMail += "Email:    " + txtEmail.Text + "\n";
            strMail += "Dealer:   " + txtDealer.Text + "\n";
            strMail += " ---------------------------------------\n";
            strMail += "THIS IS AN AUTOMATED EMAIL SYSTEM. DO NOT REPLY TO THIS ADDRESS.\n";
            strMail += "\n.\n";

            // Defines encoding of string into Bytes (network stream needs
            // an array of bytes to send -- can't send strings)
            ASCIIEncoding AE = new ASCIIEncoding();
            byte[] ByteArray = AE.GetBytes(strMail);

            // send the byte-encoded string to the networkstream -> mail server:25
            ns.Write(ByteArray, 0, ByteArray.Length);

            //ns.Read(ByteArray, 0, ByteArray.Length);
            //lblStatus.Text = ByteArray.ToString();

            // close the network stream
            ns.Close();

            // close the TCP connection
            myTCP.Close();
        
        catch(Exception ex) 
        
            throw new Exception("Couldn't send email: <p>" + ex.Message);
        

        #endregion

    

【讨论】:

【参考方案3】:

我遇到了与上述设置相同的 CPU 利用率问题。我最终向 Microsoft 开了一张票,以确定问题的原因。 CPU 利用率问题在于 ServicePoint 类。在 ServicePoint 类内部,有一个每 (MaxIdleTime/2) 毫秒运行一次的计时器。看到问题了吗?通过将 MaxIdleTime 值更改为 2,CPU 利用率将下降到正常水平。

【讨论】:

我不太“看到问题”,但是这个修复工作。有趣的是,默认的 MaxIdleTime 为 100000。当我们将其设置为 1 时,我们将 CPU 飙升至接近 100%。就像您报告的那样,将 MaxIdleTime 设置为 2 会使 CPU 降至正常水平。 @Scott Ferguson:问题在于计时器将以 (1/2) 毫秒运行,在整数除法中将计算为……每 0 毫秒。哎呀。【参考方案4】:

我使用 Sproc 发送大部分邮件。我什至可以附加文件。

创建过程 [dbo].[sendMail_With_CDOMessage] @to VARCHAR(64), @CC VARCHAR(1024)='', @BCC VARCHAR(1024)='', @subject VARCHAR(500)='', @body VARCHAR(8000)='' , @来自 VARCHAR(64), @filename VARCHAR(255)='', @priority INT = 0 作为 开始 设置无计数 宣布 @handle INT, @return 整数, @s VARCHAR(64), @sc VARCHAR(1024), @up CHAR(27), @server VARCHAR(255) SET @s = '"http://schemas.microsoft.com/cdo/configuration/' 选择 @s = 'Configuration.Fields(' + @s, @up = 'Configuration.Fields.Update', @server = 'smtp.yourdomain.com' EXEC @return = sp_OACreate 'CDO.Message', @handle OUT SET @sc = @s + 'sendusing").Value' EXEC @return = sp_OASetProperty @handle, @sc, '2' SET @sc = @s + 'smtpserver").Value' 执行@return = sp_OASetProperty @handle、@sc、@server EXEC @return = sp_OAMethod @handle, @up, NULL EXEC @return = sp_OASetProperty @handle, 'To', @to EXEC @return = sp_OASetProperty @handle, 'CC', @CC EXEC @return = sp_OASetProperty @handle, 'BCC', @BCC EXEC @return = sp_OASetProperty @handle, 'From', @from EXEC @return = sp_OASetProperty @handle, 'Subject', @subject EXEC @return = sp_OASetProperty @handle, 'HTMLBody', @body EXEC @return = sp_OASetProperty @handle, 'Priority', 'cdoHigh' 如果@filename 不为空 EXEC @return = sp_OAMethod @handle, 'AddAttachment', NULL, @filename EXEC @return = sp_OAMethod @handle, '发送', NULL 如果 @return 0 开始 PRINT '邮件失败。' 如果 @from 为空 PRINT '来自未定义的地址。 别的 ' PRINT '检查服务器是否有效。 结尾 别的 PRINT '邮件已发送。 执行@return = sp_OADestroy @handle 结尾

【讨论】:

【参考方案5】:

我过去使用过 Quicksoft,没有任何抱怨。您可以尝试的另一件事是将 SMTP 配置切换为使用拾取文件夹而不是使用网络发送,这应该可以解决“它不发送 QUIT”问题。

【讨论】:

【参考方案6】:

我一直使用 Quiksoft 的 EasyMail .NET 组件,完全没有问题。

产品首页:http://www.quiksoft.com/emdotnet/

如果您只需要发送电子邮件,他们也有该组件的免费版本:

http://www.quiksoft.com/freesmtp/

【讨论】:

我同意。在 System.Net.Mail 出现许多问题后,我们切换到 Quiksoft,并且从未回头。 这很有趣,我会看看这个。如果其他人有其他建议,我也将不胜感激! 免费版不支持使用用户名和密码的 SMTP 身份验证。这要花 500 美元。 Quiksoft 不见了。【参考方案7】:

虽然到目前为止我在 System.Net.Mail 方面没有遇到任何具体问题,但您始终可以使用旧的 System.Web.Mail API,它是 CDOSYS 的包装器。

【讨论】:

就 System.Net.Mail 没有问题而言,您是否尝试过从您的一个页面发送一封电子邮件,然后立即从该页面发送另一封邮件?您的服务器可能允许多个连接。我敢打赌,如果您运行 TCPView,您的连接会长时间保持打开状态。另外,System.Web.Mail 不依赖于本地 SMTP 服务吗?我需要通过已正确配置 MX 等的远程主机发送电子邮件,以避免被标记为垃圾邮件。 我不否认它有问题。我只是说我没有遇到它。我用的不多(只是简单的“联系我们”表格)。 System.Web.Mail 不依赖本地 SMTP 服务器,支持使用远程 SMTP 服务器。我已经广泛使用了。它不是很优雅,但一直运行良好。 哦,我不是在争论,只是好奇。 :) 我也不是。只是想确保我没有认可它。

以上是关于.NET 发送电子邮件的最佳方法(System.Net.Mail 有问题)的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET 中发送大量电子邮件的最佳方式是啥?

将 ASP.Net Core Web App 项目发送给某人的最佳实践?

(Win/C#/.Net) Applet 需要每隔一小时轮询一次——关于最佳方法的建议?

使用 rake 任务时检查电子邮件是不是发送一次的最佳方法是啥?

从 Zend Framework 应用程序向数百个收件人发送电子邮件的最佳方法是啥?

防止机器人向您的博客发送垃圾邮件的最佳方法是啥?