如何使用.NET Framework通过SSL SMTP发送电子邮件?

48

使用.NET Framework是否有一种方法可以通过端口465上的SSL SMTP服务器发送电子邮件?

通常的方法:

System.Net.Mail.SmtpClient _SmtpServer = new System.Net.Mail.SmtpClient("tempurl.org");
_SmtpServer.Port = 465;
_SmtpServer.EnableSsl = true;
_SmtpServer.Credentials = new System.Net.NetworkCredential("username", "password");
_SmtpServer.Timeout = 5000;
_SmtpServer.UseDefaultCredentials = false;

MailMessage mail = new MailMessage();
mail.From = new MailAddress(from);
mail.To.Add(to);
mail.CC.Add(cc);
mail.Subject = subject;
mail.Body = content;
mail.IsBodyHtml = useHtml;
_SmtpServer.Send(mail);

超时:

System.Net Verbose: 0 : [1024] SmtpClient::.ctor(host=ssl0.ovh.net, port=465)
System.Net Information: 0 : [1024] Associating SmtpClient#64923656 with SmtpTransport#44624228
System.Net Verbose: 0 : [1024] Exiting SmtpClient::.ctor()  -> SmtpClient#64923656
System.Net Information: 0 : [1024] Associating MailMessage#17654054 with Message#52727599
System.Net Verbose: 0 : [1024] SmtpClient#64923656::Send(MailMessage#17654054)
System.Net Information: 0 : [1024] SmtpClient#64923656::Send(DeliveryMethod=Network)
System.Net Information: 0 : [1024] Associating SmtpClient#64923656 with MailMessage#17654054
System.Net Information: 0 : [1024] Associating SmtpTransport#44624228 with SmtpConnection#14347911
System.Net Information: 0 : [1024] Associating SmtpConnection#14347911 with ServicePoint#51393439
System.Net.Sockets Verbose: 0 : [1024] Socket#26756241::Socket(InterNetwork#2)
System.Net.Sockets Verbose: 0 : [1024] Exiting Socket#26756241::Socket() 
System.Net.Sockets Verbose: 0 : [1024] Socket#23264094::Socket(InterNetworkV6#23)
System.Net.Sockets Verbose: 0 : [1024] Exiting Socket#23264094::Socket() 
System.Net.Sockets Verbose: 0 : [1024] Socket#26756241::Connect(20:465#337754884)
System.Net.Sockets Verbose: 0 : [1024] Exiting Socket#26756241::Connect() 
System.Net.Sockets Verbose: 0 : [1024] Socket#23264094::Close()
System.Net.Sockets Verbose: 0 : [1024] Socket#23264094::Dispose()
System.Net.Sockets Verbose: 0 : [1024] Exiting Socket#23264094::Close() 
System.Net Information: 0 : [1024] Associating SmtpConnection#14347911 with SmtpPooledStream#14303791
System.Net.Sockets Verbose: 0 : [1024] Socket#26756241::Receive()
System.Net.Sockets Verbose: 0 : [2404] Socket#26756241::Dispose()
System.Net.Sockets Error: 0 : [1024] Exception in the Socket#26756241::Receive - A blocking operation was interrupted by a call to WSACancelBlockingCall
System.Net.Sockets Verbose: 0 : [1024] Exiting Socket#26756241::Receive()   -> 0#0
System.Net Error: 0 : [1024] Exception in the SmtpClient#64923656::Send - Unable to read data from the transport connection: A blocking operation was interrupted by a call to WSACancelBlockingCall.
System.Net Error: 0 : [1024]    at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.Net.DelegatedStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.BufferedReadStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Mail.SmtpReplyReaderFactory.ReadLines(SmtpReplyReader caller, Boolean oneLine)
   at System.Net.Mail.SmtpReplyReaderFactory.ReadLine(SmtpReplyReader caller)
   at System.Net.Mail.SmtpConnection.GetConnection(String host, Int32 port)
   at System.Net.Mail.SmtpTransport.GetConnection(String host, Int32 port)
   at System.Net.Mail.SmtpClient.GetConnection()
   at System.Net.Mail.SmtpClient.Send(MailMessage message)
System.Net Verbose: 0 : [1024] Exiting SmtpClient#64923656::Send() 
System.Net Information: 0 : [1024] Associating MailMessage#49584532 with Message#19699911

我进行了谷歌搜索并发现System.Net.Mail支持在端口587(默认端口为明确SSL,即未加密开始,然后发出STARTTLS然后切换到加密连接:RFC 2228),但不支持隐式SSL(整个连接都包装在SSL层中)...


2
SmtpClient.EnableSsl 属性 "另一种连接方法是在发送任何协议命令之前先建立一个 SSL 会话。这种连接方法有时被称为 SMTP/SSL、SMTP over SSL 或 SMTPS,并且默认使用端口 465。目前不支持使用 SSL 的这种替代连接方法。" FWIW - gerryLowry
12个回答

39

以下是如何通过GMail发送邮件的示例,同时使用SSL/465端口。稍微调整下面的代码即可运行!

using System.Web.Mail;
using System;
public class MailSender
{
    public static bool SendEmail(
        string pGmailEmail, 
        string pGmailPassword, 
        string pTo, 
        string pSubject,
        string pBody, 
        System.Web.Mail.MailFormat pFormat,
        string pAttachmentPath)
    {
    try
    {
        System.Web.Mail.MailMessage myMail = new System.Web.Mail.MailMessage();
        myMail.Fields.Add
            ("http://schemas.microsoft.com/cdo/configuration/smtpserver",
                          "smtp.gmail.com");
        myMail.Fields.Add
            ("http://schemas.microsoft.com/cdo/configuration/smtpserverport",
                          "465");
        myMail.Fields.Add
            ("http://schemas.microsoft.com/cdo/configuration/sendusing",
                          "2");
        //sendusing: cdoSendUsingPort, value 2, for sending the message using 
        //the network.

        //smtpauthenticate: Specifies the mechanism used when authenticating 
        //to an SMTP 
        //service over the network. Possible values are:
        //- cdoAnonymous, value 0. Do not authenticate.
        //- cdoBasic, value 1. Use basic clear-text authentication. 
        //When using this option you have to provide the user name and password 
        //through the sendusername and sendpassword fields.
        //- cdoNTLM, value 2. The current process security context is used to 
        // authenticate with the service.
        myMail.Fields.Add
        ("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate","1");
        //Use 0 for anonymous
        myMail.Fields.Add
        ("http://schemas.microsoft.com/cdo/configuration/sendusername",
            pGmailEmail);
        myMail.Fields.Add
        ("http://schemas.microsoft.com/cdo/configuration/sendpassword",
             pGmailPassword);
        myMail.Fields.Add
        ("http://schemas.microsoft.com/cdo/configuration/smtpusessl",
             "true");
        myMail.From = pGmailEmail;
        myMail.To = pTo;
        myMail.Subject = pSubject;
        myMail.BodyFormat = pFormat;
        myMail.Body = pBody;
        if (pAttachmentPath.Trim() != "")
        {
            MailAttachment MyAttachment = 
                    new MailAttachment(pAttachmentPath);
            myMail.Attachments.Add(MyAttachment);
            myMail.Priority = System.Web.Mail.MailPriority.High;
        }

        System.Web.Mail.SmtpMail.SmtpServer = "smtp.gmail.com:465";
        System.Web.Mail.SmtpMail.Send(myMail);
        return true;
    }
    catch (Exception ex)
    {
        throw;
    }
}
}

27
System.Web.Mail已经过时。 - David
1
我的库里有旧代码!因此有这个声明:“稍微调整下面的代码应该能工作!” - Andrew Siemer
这是针对于 .Net 4.0 之前的版本吗? - Rafael
3
根据链接,你说得对。 这并不被认为是一个错误,而是设计上的考虑。SMTP有两种SSL身份验证方式,我们只支持一种——显式SSL,并且只有Web.Mail能够发送SSL邮件(这也是出于设计考虑)。 - Mahdi
这似乎是Gmail现在的要求?这个解决方案在2017年修复了我的问题! - Serj Sagan
给任何认真的程序员:请使用下面Bob Mc提供的解决方案 ;-) - Stephane Ehret

22

虽然我来晚了,但我会为任何路过的人提供我的方法,以供参考。

正如之前的答案所述,System.Net.Mail SmtpClient 类不支持隐式SSL。它支持显式SSL,这需要通过端口25上的不安全连接与SMTP服务器进行通信,以协商传输层安全性(TLS)。我在这里记录了我对这个细节的挣扎。

简单来说,SMTP over Implict SSL端口465需要在连接到SMTP服务器之前协商TLS。我并没有编写一个 .Net SMTPS 实现,而是转向一个名为 Stunnel 的实用程序。 它是一个小型服务,可以让您通过SSL将本地端口上的流量重定向到远程端口。

免责声明:Stunnel 使用 OpenSSL 库的一部分,该库最近在所有主要技术新闻媒体上发布了一个高调漏洞。我相信最新版本使用了已修补的 OpenSSL,但请自行承担风险。

一旦安装了该实用程序,就只需对配置文件进行小的修改:

; Example SSL client mode services
[my-smtps]
client = yes
accept = 127.0.0.1:465
connect = mymailserver.com:465

这段指令告诉Stunnel服务将本地对端口465的请求重新路由到我的邮件服务器的465端口。这个过程使用TLS进行,可以成功满足另一端的SMTP服务器。

使用这个实用工具,以下代码将成功传输到465端口:

using System;
using System.Net;
using System.Net.Mail;

namespace RSS.SmtpTest
{
    class Program
    {
        static void Main( string[] args )
        {
            try {
                using( SmtpClient smtpClient = new SmtpClient( "localhost", 465 ) ) { // <-- note the use of localhost
                    NetworkCredential creds = new NetworkCredential( "username", "password" );
                    smtpClient.Credentials = creds;
                    MailMessage msg = new MailMessage( "joe@schmoe.com", "jane@schmoe.com", "Test", "This is a test" );
                    smtpClient.Send( msg );
                }
            }
            catch( Exception ex ) {
                Console.WriteLine( ex.Message );
            }
        }
    }
}

因此,这里的优点在于您可以使用隐式SSL和端口465作为安全协议,同时仍然使用框架中内置的发送邮件方法。缺点是它需要使用可能只有这个特定功能有用的第三方服务。


1
谢谢Bob。令人难以置信的是,即使在2019年,这仍然是最优雅的解决方案。使用已弃用的System.Web.Mail.MailMessage是不可接受的,尤其是当您知道必须确保默认输入语言为en-US时,否则您将会遇到"COMException:未在此消息中找到请求的正文部分"的错误(顺便说一句,这应该自动触发一个WTF?的反应;-)。 - Stephane Ehret
@StephaneEhret 我同意。正如我在博客文章中所指出的,我打算编写一个隐式SSL .Net SMTP库,但从未能找到时间。有一些可用的库支持隐式SSL,但它们中的大多数都不适合我。也许我会重新考虑这个项目。 - Bob Mc

12

它可以使用已标记为过时的System.Web.Mail:

private const string SMTP_SERVER        = "http://schemas.microsoft.com/cdo/configuration/smtpserver";
private const string SMTP_SERVER_PORT   = "http://schemas.microsoft.com/cdo/configuration/smtpserverport";
private const string SEND_USING         = "http://schemas.microsoft.com/cdo/configuration/sendusing";
private const string SMTP_USE_SSL       = "http://schemas.microsoft.com/cdo/configuration/smtpusessl";
private const string SMTP_AUTHENTICATE  = "http://schemas.microsoft.com/cdo/configuration/smtpauthenticate";
private const string SEND_USERNAME      = "http://schemas.microsoft.com/cdo/configuration/sendusername";
private const string SEND_PASSWORD      = "http://schemas.microsoft.com/cdo/configuration/sendpassword";

System.Web.Mail.MailMessage mail = new System.Web.Mail.MailMessage();

mail.Fields[SMTP_SERVER] = "tempurl.org";
mail.Fields[SMTP_SERVER_PORT] = 465;
mail.Fields[SEND_USING] = 2;
mail.Fields[SMTP_USE_SSL] = true;
mail.Fields[SMTP_AUTHENTICATE] = 1;
mail.Fields[SEND_USERNAME] = "username";
mail.Fields[SEND_PASSWORD] = "password";

System.Web.Mail.SmtpMail.Send(mail);

你对过时的命名空间使用有什么看法?


2
这个答案可以改进。这里没有“收件人”,“发件人”,“主题”或“正文”。也就是说,你缺少整个电子邮件部分。当然,这并不难,但为什么不把所有内容放在一个地方呢? - Matthew

10

在VB.NET尝试连接Rackspace的SSL端口465时,我遇到了同样的问题(需要隐式SSL)。为了成功地连接,我使用了https://www.nuget.org/packages/MailKit/

以下是HTML电子邮件消息的示例。

Imports MailKit.Net.Smtp
Imports MailKit
Imports MimeKit

Sub somesub()
    Dim builder As New BodyBuilder()
    Dim mail As MimeMessage
    mail = New MimeMessage()
    mail.From.Add(New MailboxAddress("", c_MailUser))
    mail.To.Add(New MailboxAddress("", c_ToUser))
    mail.Subject = "Mail Subject"
    builder.HtmlBody = "<html><body>Body Text"
    builder.HtmlBody += "</body></html>"
      mail.Body = builder.ToMessageBody()

      Using client As New SmtpClient
        client.Connect(c_MailServer, 465, True)
        client.AuthenticationMechanisms.Remove("XOAUTH2") ' Do not use OAUTH2
        client.Authenticate(c_MailUser, c_MailPassword) ' Use a username / password to authenticate.
        client.Send(mail)
        client.Disconnect(True)
    End Using 

End Sub

这真的帮了我很多。它是一个非常好的替代方案,可以处理隐式SSL,而SmtpClient则不能。此外,它比AIM(另一个答案中建议的)更成熟,并且可以很好地使用UTF-8(和其他编码)。谢谢。 - Alexei - check Codidact

9

试着查看这个免费且开源的替代方案https://www.nuget.org/packages/AIM。它是免费使用和开源的,并使用与System.Net.Mail完全相同的方式。 要发送电子邮件到隐式SSL端口,您可以使用以下代码。

public static void SendMail()
{    
    var mailMessage = new MimeMailMessage();
    mailMessage.Subject = "test mail";
    mailMessage.Body = "hi dude!";
    mailMessage.Sender = new MimeMailAddress("you@gmail.com", "your name");
    mailMessage.To.Add(new MimeMailAddress("yourfriend@gmail.com", "your friendd's name")); 
// You can add CC and BCC list using the same way
    mailMessage.Attachments.Add(new MimeAttachment("your file address"));

//Mail Sender (Smtp Client)

    var emailer = new SmtpSocketClient();
    emailer.Host = "your mail server address";
    emailer.Port = 465;
    emailer.SslType = SslMode.Ssl;
    emailer.User = "mail sever user name";
    emailer.Password = "mail sever password" ;
    emailer.AuthenticationMode = AuthenticationType.Base64;
    // The authentication types depends on your server, it can be plain, base 64 or none. 
//if you do not need user name and password means you are using default credentials 
// In this case, your authentication type is none            
    emailer.MailMessage = mailMessage;
    emailer.OnMailSent += new SendCompletedEventHandler(OnMailSent);
    emailer.SendMessageAsync();
}

// A simple call back function:
private void OnMailSent(object sender, AsyncCompletedEventArgs asynccompletedeventargs)
{
if (e.UserState!=null)
    Console.Out.WriteLine(e.UserState.ToString());
if (e.Error != null)
{
    MessageBox.Show(e.Error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else if (!e.Cancelled)
{
    MessageBox.Show("Send successfull!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
} 

1
你能提供这个 AIM 的文档吗?在 SourceForge 上没有找到任何文档或网站。 - Fuzed Mass
最好的方法是下载示例应用程序和代码。 - Nil Null

8

我知道我加入讨论得比较晚,但我认为这对其他人可能有用。

我想避免使用过时的东西,在大量试验后,我找到了一种简单的发送到需要隐式SSL的服务器的方法:使用NuGet并向项目添加MailKit包。(我使用的是VS2017,针对.NET 4.6.2,但它应该适用于较低的.NET版本...)

然后你只需要像这样做:

using MailKit.Net.Smtp;
using MimeKit;

var client = new SmtpClient();
client.Connect("server.name", 465, true);

// Note: since we don't have an OAuth2 token, disable the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove ("XOAUTH2");

if (needsUserAndPwd)
{
    // Note: only needed if the SMTP server requires authentication
    client.Authenticate (user, pwd);
}

var msg = new MimeMessage();
msg.From.Add(new MailboxAddress("sender@ema.il"));
msg.To  .Add(new MailboxAddress("target@ema.il"));
msg.Subject = "This is a test subject";

msg.Body = new TextPart("plain") {
    Text = "This is a sample message body"
};

client.Send(msg);
client.Disconnect(true);

当然,您也可以对其进行调整以使用显式SSL或根本不使用传输安全。

我正在使用基于您上面的代码发送电子邮件的代码,但是我遇到了错误。您能否在这里看到?如果您能帮忙解决,我将非常感激。 https://dev59.com/OKXja4cB1Zd3GeqPRnmO - Fuzed Mass
我回复了你的帖子并添加了更多选项,但是你的问题似乎太广泛了,除非你尝试排除一些可能的原因,否则很难得到简单的答案。 - Luke

5

4
您也可以通过465端口连接,但由于System.Net.Mail命名空间的一些限制,您可能需要修改代码。这是因为该命名空间不提供隐式SSL连接的功能。有关详细信息,请参见此处的讨论。
可以使用Microsoft CDO(协作数据对象)进行隐式连接,而无需使用现已过时的System.Web.Mail命名空间。我在另一个讨论中提供了如何使用CDO的示例(GMail SMTP via C# .Net errors on all ports)。
希望这可以帮助您!

2

1
我需要将其减一。引用的博客页面只有一个评论参考,并没有确认。该评论提到了另一个俄语博客,也没有任何确认的注释。在有确切证实这是一个事实解决方案之前,迄今为止只有一个人跟着一个人跟着一个人...这对于SO答案来说还不够好。 - TonyG
1
补充说明:这个答案描述了在端口x上连接,然后被转移到端口y。这就是STARTTLS的定义,而OP说他不想要它。System.Net.Mail命名空间仅支持显式SSL。 显式SSL从端口25开始未加密,然后发出STARTTLS信号,然后切换到加密连接。-参考:https://support.microsoft.com/en-us/help/950260/you-cannot-use-system-net-mail-smtpclient-to-send-an-e-mail-message-wi我已经验证了System.Net.Mail.SmtpClient支持使用端口587进行STARTTLS连接。 我无法在25上连接。 - TonyG

0

对于 Gmail,这些设置适用于我,ServicePointManager.SecurityProtocol 行是必需的。因为我已经设置了两步验证,所以我需要从 Google 应用程序密码生成器 获取应用程序密码。

SmtpClient mailer = new SmtpClient();
mailer.Host = "smtp.gmail.com";
mailer.Port = 587;
mailer.EnableSsl = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

注意事项: SecurityProtocolType.Tls 表示仅支持 TLS 1.0。如果您想要允许其他协议(截至2021年10月,微软已停止使用TLS 1.0),您需要将它们包含在内。如果您的目标是 .Net 4.8 或更高版本,则可以使用 SecurityProtocolType.SystemDefault,这样操作系统会选择最新的协议(或设置为系统默认值的协议)。 - Suncat2000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接