通过Exchange Online(Office 365)使用System.Net.Mail发送SMTP电子邮件

70
我们正在测试新的Office 365 beta版本,我在Exchange Online服务中有一个邮件帐户。现在我正在尝试连接一个可以从我的测试帐户发送SMTP电子邮件的LOB应用程序。
但是Exchange 365平台要求在端口587上进行TLS加密,并且System.Net.Mail存在不允许隐式SSL加密的“功能”。
有没有人成功地通过C#在这个平台上发送邮件?
我有以下基本代码,应该可以发送邮件 - 欢迎任何建议。
SmtpClient server = new SmtpClient("ServerAddress");
server.Port = 587;
server.EnableSsl = true;
server.Credentials = new System.Net.NetworkCredential("username@mydomain.com", "password");
server.Timeout = 5000;
server.UseDefaultCredentials = false;

MailMessage mail = new MailMessage();
mail.From = new MailAddress("recipent@anyaddress");
mail.To.Add("username@mydomain.com");
mail.Subject = "test out message sending";
mail.Body = "this is my message body";
mail.IsBodyHtml = true;

server.Send(mail);

1
所以我开始怀疑使用C#是否可能,并且System.Net.Mail命名空间对SSL设置的限制。似乎Exchange在Office 365上公开了其Web服务,许多文章都谈到利用此功能与邮箱等进行工作。但是我真的不想将整个邮箱暴露给我的LOB应用程序。SMTP中的“S”代表“简单”-我无法相信Microsoft的邮件服务器安装不能与Microsoft的编程语言一起使用。请有人告诉我我错过了什么! - Adam Stewart
我希望我有一个服务器来尝试一下。所以,它超时了,这里有一个问题。作为你的测试邮件,如果不指定正文为HTML,纯文本是否有效? - BugFinder
我已经修改了上面的代码,将mail.IsBodyHtml设置为false;但我仍然遇到超时问题。感谢您的建议 - 在这个时候,我愿意尝试任何方法来使它工作! - Adam Stewart
1
我想指出一个类似问题的答案。为了使代码示例正常工作,您需要在设置凭据之前将 UseDefaultCredentials = false 行移动到前面。https://dev59.com/Wuo6XIcBkEYKwwoYOR0q#14021685 - jeroenh
这对我的问题起作用了。http://stackoverflow.com/questions/21045026/sent-outlook-draft-from-other-computer?answertab=votes#tab-top - Eugen
显示剩余2条评论
10个回答

72

修正了上面工作代码中的一些错别字:

MailMessage msg = new MailMessage();
msg.To.Add(new MailAddress("someone@somedomain.com", "SomeOne"));
msg.From = new MailAddress("you@yourdomain.com", "You");
msg.Subject = "This is a Test Mail";
msg.Body = "This is a test message using Exchange OnLine";
msg.IsBodyHtml = true;

SmtpClient client = new SmtpClient();
client.UseDefaultCredentials = false;
client.Credentials = new System.Net.NetworkCredential("your user name", "your password");
client.Port = 587; // You can use Port 25 if 587 is blocked (mine is!)
client.Host = "smtp.office365.com";
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.EnableSsl = true;
try
{
    client.Send(msg);
    lblText.Text = "Message Sent Succesfully";
}
catch (Exception ex)
{
    lblText.Text = ex.ToString();
}

我有两个网络应用程序使用上述代码,都没有出现任何问题。


你正在运行哪个版本的Office 365?我已经有一段时间没有看过这段代码了,但我们正准备从小型企业计划P1迁移到企业计划E1,我想知道我的最初问题是否是由于我们使用P1计划引起的。 - Adam Stewart
37
抱歉,此代码已不适用于Office 365。出现的错误消息是SMTP服务器需要安全连接或客户端未经过身份验证。服务器响应为:5.7.57 SMTP; 在MAIL FROM期间未经身份验证的客户端无法发送匿名邮件[HE1PR05CA0133.eurprd05.prod.outlook.com]。 - Simon Price
是的,Simon Price是正确的,即使在25端口上也无法工作。 - Dieter
1
我们的组织已经启用了账户上的MFA,那么我们该如何配置它呢? - Sunil Soni
这真的很棒!谢谢! - Mike Malter

35
在2020年,这些代码似乎会返回异常,例如System.Net.Mail.SmtpStatusCode.MustIssueStartTlsFirst或The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.7.57 SMTP; Client was not authenticated to send anonymous mail during MAIL FROM。 这段代码对我来说是可行的。
            using (SmtpClient client = new SmtpClient()
            {
                Host = "smtp.office365.com",
                Port = 587,
                UseDefaultCredentials = false, // This require to be before setting Credentials property
                DeliveryMethod = SmtpDeliveryMethod.Network,
                Credentials = new NetworkCredential("alias@fulldomain.com", "password"), // you must give a full email address for authentication 
                TargetName = "STARTTLS/smtp.office365.com", // Set to avoid MustIssueStartTlsFirst exception
                EnableSsl = true // Set to avoid secure connection exception
            })
            {

                MailMessage message = new MailMessage()
                {
                    From = new MailAddress("alias@fulldomain.com"), // sender must be a full email address
                    Subject = subject,
                    IsBodyHtml = true,
                    Body = "<h1>Hello World</h1>",
                    BodyEncoding = System.Text.Encoding.UTF8,
                    SubjectEncoding = System.Text.Encoding.UTF8,

                };
                var toAddresses = recipients.Split(',');
                foreach (var to in toAddresses)
                {
                    message.To.Add(to.Trim());
                }

                try
                {
                    client.Send(message);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }

这看起来非常有前途,但我遇到了一个错误,如下所示:System.Net.Mail.SmtpException: Failure sending mail. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden by its access permissions 52.97.133.146:587 ... 你有什么想法吗? - Dieter
我也想知道在哪里可以找到“完整的电子邮件地址进行身份验证”。你能详细说明一下吗? - Dieter
1
“完整的电子邮件地址用于身份验证”是用于发送电子邮件的电子邮件帐户。例如,我的公司电子邮件是my-name@my-company.com。我创建另一个电子邮件地址noreply@my-company.com来发送电子邮件。我将在那里填写noreply帐户的凭据。 - Lam Le
当启用双因素身份验证时怎么办? - Gayan
如果您启用了2FA,则它是交互模式,可能无法通过此脚本工作。 - Jirapong
我爱你!非常感谢,这个有效! - Paul

15

简单回答:发件人地址必须完全匹配你正在发送邮件的帐户,否则你将收到错误5.7.1客户端没有权限以此发件人身份发送邮件。

我猜这是为了防止你的Office 365帐户被恶意伪造,否则你可能会能够以sballmer@microsoft.com的名义发送邮件。

另一个尝试的方法是在验证中,填写第三个字段的域名,例如:

Dim smtpAuth = New System.Net.NetworkCredential(
    "TheDude", "hunter2password", "MicrosoftOffice365Domain.com")

如果这不起作用,请再次检查您是否可以登录到以下网址的帐户:https://portal.microsoftonline.com

另一个需要注意的事项是,您的杀毒软件解决方案可能会阻止对25和587号端口进行程序访问以防止垃圾邮件。诺顿和麦克菲可能会悄悄地阻止对这些端口的访问。只有启用电子邮件和套接字调试才能让您注意到它(请参见下文)。

最后需要注意的一点是,Send方法是异步的。如果你在调用send之后立刻调用dispose,你很可能会在邮件发送之前关闭你的连接。让你的smtpClient实例监听OnSendCompleted事件,并从那里调用dispose。你必须使用SendAsync方法,因为Send方法不会触发这个事件。


详细答案:使用Visual Studio(VB.NET或C#都可以),我创建了一个简单的表单,其中包含一个创建邮件消息的按钮,与上面的示例类似。然后,我将以下代码添加到应用程序.exe.config文件中(在我的项目的bin/debug目录中)。这会使输出标签页具有详细的调试信息。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <sources>
            <source name="System.Net">
                <listeners>
                    <add name="System.Net" />
                </listeners>
            </source>
            <source name="System.Net.Sockets">
                <listeners>
                    <add name="System.Net" />
                </listeners>
            </source>
        </sources>
        <switches>
            <add name="System.Net" value="Verbose" />
            <add name="System.Net.Sockets" value="Verbose" />
        </switches>
        <sharedListeners>
            <add name="System.Net"
              type="System.Diagnostics.TextWriterTraceListener"
              initializeData="System.Net.log"
            />
        </sharedListeners>
        <trace autoflush="true" />
    </system.diagnostics>
</configuration>

1
我猜 hunter2password 不是你的密码吧? - rhughes
11
嘿,是的,那是对IRC模因的一个老引用。我的密码是***************。来源:http://ars.userfriendly.org/cartoons/?id=19990814 - The Dude
1
如果您使用O365授权的“发送邮件”电子邮件地址添加MailMessage.Sender属性,则MailMessage.From不必是主帐户电子邮件。有关详细信息,请参见我的帖子:https://dev59.com/Xm025IYBdhLWcg3wEhZb#33613150。 - ShaneLS
@jklemmack 谢谢,我一直在想bash.org现在的名字是什么。--- 你是说名字还没有改变吗!? :) - The Dude

10

Office 365使用两个服务器,即SMTP服务器和保护扩展服务器。

第一个服务器是smtp.office365.com(smtp客户端的主机属性),第二个服务器是STARTTLS/smtp.office365.com(smtp客户端的TargetName属性)。另一件事是在设置网络凭据之前必须将Usedefaultcredential=false置为false。

    client.UseDefaultCredentials = False
    client.Credentials = New NetworkCredential("user@domain.com", "Password")
    client.Host = "smtp.office365.com"
    client.EnableSsl = true
    client.TargetName = "STARTTLS/smtp.office365.com"
    client.Port = 587

    client.Send(mail)

9

6

这里有一个小提示,对于那些可能正在寻找此问题答案的人来说(在实施此解决方案之前,请务必阅读底部的注意事项)。我遇到了一个问题,即无法向一个客户发送电子邮件,因为我的 MS Office 365 订阅中不存在该客户的用户或域名。我试图通过我的 Me@MyDomain.com 365 帐户进行 SMTP,但 .NET 邮件信息的地址是从 Client@ClientDomain.com 发送的。这就是当我遇到“5.7.1 客户端没有权限”错误时发生的情况。为了解决这个问题,需要将 MailMessage 类的 Sender 属性设置为一个我的 SMTP 凭据允许“发送作为”的电子邮件地址。在下面的代码中,我选择使用我的主帐户电子邮件 (Me@MyDomain.com)。请记住,我可以使用任何我的 O365 帐户具有“发送作为”权限的电子邮件地址(例如,Support@MyDomain.com,no-reply@MyDomain.com 等)。

using System;
using System.Net.Mail;

namespace ConsoleApplication1
{
   class Program
   {
      static void Main(string[] args)
      {
         using (
            MailMessage message = new MailMessage
            {
               To = { new MailAddress("Recipient1@Recipient1Domain.com", "Recipient 1") },
               Sender = new MailAddress("Me@MyDomain.com", "Me"),
               From = new MailAddress("Client@ClientDomain.com", "Client"),
               Subject=".net Testing"
               Body="Testing .net emailing",
               IsBodyHtml=true,
            }
         )
         {
            using (
               SmtpClient smtp = new SmtpClient
               {
                  Host = "smtp.office365.com",
                  Port = 587,
                  Credentials = new System.Net.NetworkCredential("Me@MyDomain.com", "Pa55w0rd"),
                  EnableSsl = true
               }
            )
            {
               try { smtp.Send(message); }
               catch (Exception excp)
               {
                  Console.Write(excp.Message);
                  Console.ReadKey();
               }
            }
         }
      }
   }
}

请注意,SmtpClient仅在.NET Framework 4中可用,并可使用Using块进行处理。
使用.NET Framework 2到3.5的用户应将SmtpClient用作...
SmtpClient smtp = new SmtpClient
{
   Host = "smtp.office365.com",
   Port = 587,
   Credentials = new System.Net.NetworkCredential("Me@MyDomain.com", "Pa55w0rd"),
   EnableSsl = true
};
try { smtp.Send(message); }
catch (Exception excp)
{
   Console.Write(excp.Message);
   Console.ReadKey();
}

生成的电子邮件标题将类似于这样:
Authentication-Results: spf=none (sender IP is )  
   smtp.mailfrom=Me@MyDomain.com;  
Received: from MyPC (192.168.1.1) by  
   BLUPR13MB0036.namprd13.prod.outlook.com (10.161.123.150) with Microsoft SMTP  
   Server (TLS) id 15.1.318.9; Mon, 9 Nov 2015 16:06:58 +0000  
MIME-Version: 1.0  
From: Client <Client@ClientDomain.com>  
Sender: Me <Me@MyDomain.com>  
To: Recipient 1 <Recipient1@Recipient1Domain.com>  

--请小心--
请注意,某些邮件客户端可能会将发件人地址显示为备注。例如,在阅读窗格的标题中,Outlook将显示以下内容:

我 <Me@MyDomain.com> 代表 客户 <Client@ClientDomain.com>

然而,只要收件人使用的电子邮件客户端不是完全垃圾,这不应该影响回复地址。回复地址仍应使用发件人地址。为了涵盖所有情况,您还可以利用MailMessage.ReplyToList属性,为客户提供正确的回复地址。

此外,请注意,一些电子邮件服务器可能会直接拒绝代表另一个公司发送的任何邮件,并引用域所有者策略限制。一定要进行彻底的测试并查找任何退信。我可以告诉您,我的个人Hotmail(mail.live.com)电子邮件帐户是会拒绝我代表某个客户发送的消息之一,但其他客户则可以通过。虽然我怀疑这与我的客户的域TXT“spf1”记录有关,但我不知道为什么它会拒绝代表一个域发送的电子邮件而不是另一个域。也许知道的人可以给我们解释一下?


只是一个提醒 - Office 365似乎不喜欢添加发件人标签。 - markthewizard1234

5

我尝试将用于与smtp.google.com:587连接的c#代码移植到office365上,但没有成功。尝试了在使用Ssl之前/之后使用凭据的所有组合以及互联网上几乎所有建议 - 但均未成功(出现5.7.1错误)。

最终从某个地方找到了这一行代码作为最后的办法,在 .Send(message) 之前使用:

smtpClient.TargetName = "STARTTLS/smtp.office365.com";

自那时起,每次发送(send())都取得了巨大的成功。

0

2023年2月:

这是我在两年内第二次审查有关此主题的所有问答,也是我第二次认为解决方案是在AD中禁用安全默认值以启用SMTP身份验证。

如果您仍然遇到这些问题:

  1. 前往Azure并切换到您的目录
  2. 选择Active Directory-->属性
  3. 在底部,单击“管理安全默认值
  4. 禁用它并保存。

等待几分钟,然后再尝试您的代码,如果您按照说明和所有SO问题操作,则现在应该可以正常工作...请注意您的安全配置。

希望对您有所帮助。


0

我用以下方法使它工作:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

-3

终于,成功了!

smtpClient.Credentials = credentials;之后加上smtpClient.UseDefaultCredentials = false;,问题得到解决!

            SmtpClient smtpClient = new SmtpClient(smtpServerName);                          
            System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(smtpUName, smtpUNamePwd);

            smtpClient.Credentials = credentials;
            smtpClient.UseDefaultCredentials = false;  <-- Set This Line After Credentials

            smtpClient.Send(mailMsg);
            smtpClient = null;
            mailMsg.Dispose();

在设置了Credentials = xxyy之后,将UseDefaultCredentials = false;设置为false会将您的凭据重置为null。这样您就不会发送任何凭据了。(是的,我知道我在9年后回答这个问题有点晚了...)请参见https://dev59.com/wWw15IYBdhLWcg3wYKmo#7009204 - Jan 'splite' K.
很明显,这与建议相矛盾。UseDefaulCredentials = false应该在设置凭据之前。https://dev59.com/9XE95IYBdhLWcg3wEpvo - KiwiSunGoddess

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