通过O365发送电子邮件
我们公司最近通过Office 365订阅将内部邮件服务器(CommunigatePro)迁移到托管邮件。从那以后,我遇到了从我的VB.NET应用程序向组织外部的人发送电子邮件的问题。我找到了一个解决方法,但是我想知道是否在配置中丢失了可能导致问题的东西。通过O365发送电子邮件
我发送消息的代码是非常标准的(显然混淆了一下用于发布),但我必须包括一个独立的功能通过CDO发送消息,如果它失败:
Private Function SendFTPSuccessMail() As Boolean
Dim Email As New System.Net.Mail.MailMessage
Dim MailServer As New System.Net.Mail.SmtpClient("domain-com.mail.protection.outlook.com")
Dim SenderAddress As New System.Net.Mail.MailAddress("[email protected]", "IT HelpDesk")
Dim BodyText As String = String.Empty
With Email
.From = SenderAddress
.Bcc.Add(SenderAddress)
BodyText = "SOME TEXT FOR THE E-MAIL MESSAGE"
.To.Add(New System.Net.Mail.MailAddress("[email protected]", "Recipient"))
.Subject = "MESSAGE SUBJECT"
.Body = BodyText
End With
Try
MailServer.Send(Email)
Return True
Catch ex As Exception
Dim CDOError As String = SendCDOEmail(Email, ex)
If Not String.IsNullOrEmpty(CDOError) Then
Dim ExceptionMessage As String = ".NET SEND ERROR: " & ex.Message & vbCrLf
If Not ex.InnerException Is Nothing Then
ExceptionMessage += ex.InnerException.Message & vbCrLf
End If
ExceptionMessage += vbCrLf & CDOError
MessageBox.Show(ExceptionMessage, "NOTIFICATION E-MAIL FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
Else
Return True
End If
Finally
If Not Email Is Nothing Then
Email.Dispose()
End If
MailServer = Nothing
End Try
End Function
的MailServer.Send
如果所有收件人都在同一个(我的)域中,但方法会正常工作,但会为具有不同域的任何收件人地址发出SmtpFailedRecipientException
或SmtpFailedRecipientsException
。该SendCDOEmail
功能如下:
Option Strict Off
Public Function SendCDOEmail(ByRef OriginalMessage As System.Net.Mail.MailMessage, ByVal OriginalException As Exception) As String
Dim ErrorMessage As String = String.Empty
If TypeOf (OriginalException) Is System.Net.Mail.SmtpFailedRecipientsException Then
Dim SMTPEX As System.Net.Mail.SmtpFailedRecipientsException = CType(OriginalException, System.Net.Mail.SmtpFailedRecipientsException)
Dim FailedAddresses As New List(Of String)
Dim NewTo As New List(Of System.Net.Mail.MailAddress)
Dim NewCC As New List(Of System.Net.Mail.MailAddress)
Dim NewBCC As New List(Of System.Net.Mail.MailAddress)
For Each InnerEx As System.Net.Mail.SmtpFailedRecipientException In SMTPEX.InnerExceptions
FailedAddresses.Add(InnerEx.FailedRecipient.ToString.Replace("<", "").Replace(">", ""))
Next
For Each Recipient As System.Net.Mail.MailAddress In OriginalMessage.To
For Each KeepAddress As String In FailedAddresses
If Recipient.Address = KeepAddress Then
NewTo.Add(Recipient)
Exit For
End If
Next
Next
OriginalMessage.To.Clear()
For Each Recipient As System.Net.Mail.MailAddress In NewTo
OriginalMessage.To.Add(Recipient)
Next
For Each Recipient As System.Net.Mail.MailAddress In OriginalMessage.CC
For Each KeepAddress As String In FailedAddresses
If Recipient.Address = KeepAddress Then
NewCC.Add(Recipient)
Exit For
End If
Next
Next
OriginalMessage.CC.Clear()
For Each Recipient As System.Net.Mail.MailAddress In NewCC
OriginalMessage.CC.Add(Recipient)
Next
For Each Recipient As System.Net.Mail.MailAddress In OriginalMessage.Bcc
For Each KeepAddress As String In FailedAddresses
If Recipient.Address = KeepAddress Then
NewBCC.Add(Recipient)
Exit For
End If
Next
Next
OriginalMessage.Bcc.Clear()
For Each Recipient As System.Net.Mail.MailAddress In NewBCC
OriginalMessage.Bcc.Add(Recipient)
Next
ElseIf TypeOf (OriginalException) Is System.Net.Mail.SmtpFailedRecipientException Then
Dim SMTPEX As SmtpFailedRecipientException = CType(OriginalException, SmtpFailedRecipientException)
Dim NewTo As New List(Of System.Net.Mail.MailAddress)
Dim NewCC As New List(Of System.Net.Mail.MailAddress)
Dim NewBCC As New List(Of System.Net.Mail.MailAddress)
Dim FailedAddress As String = SMTPEX.FailedRecipient.ToString.Replace("<", "").Replace(">", "")
For Each Recipient As System.Net.Mail.MailAddress In OriginalMessage.To
If Recipient.Address = FailedAddress Then
NewTo.Add(Recipient)
End If
Next
OriginalMessage.To.Clear()
For Each Recipient As System.Net.Mail.MailAddress In NewTo
OriginalMessage.To.Add(Recipient)
Next
For Each Recipient As System.Net.Mail.MailAddress In OriginalMessage.CC
If Recipient.Address = FailedAddress Then
NewCC.Add(Recipient)
End If
Next
OriginalMessage.CC.Clear()
For Each Recipient As System.Net.Mail.MailAddress In NewCC
OriginalMessage.CC.Add(Recipient)
Next
For Each Recipient As System.Net.Mail.MailAddress In OriginalMessage.Bcc
If Recipient.Address = FailedAddress Then
NewBCC.Add(Recipient)
End If
Next
OriginalMessage.Bcc.Clear()
For Each Recipient As System.Net.Mail.MailAddress In NewBCC
OriginalMessage.Bcc.Add(Recipient)
Next
End If
Dim CDOMessage As Object = CreateObject("CDO.Message")
With CDOMessage.Configuration.Fields
.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "smtp.office365.com"
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
.Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = 1
.Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1
.Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "[email protected]"
.Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "mypassword"
.Update()
End With
With OriginalMessage
CDOMessage.Subject = .Subject
CDOMessage.From = .From.DisplayName & " <" & .From.Address & ">"
For Each ToAddress As System.Net.Mail.MailAddress In .To
CDOMessage.To = CDOMessage.To & ToAddress.DisplayName & " <" & ToAddress.Address & ">; "
Next
For Each ToAddress As System.Net.Mail.MailAddress In .CC
CDOMessage.CC = CDOMessage.CC & ToAddress.DisplayName & " <" & ToAddress.Address & ">; "
Next
For Each ToAddress As System.Net.Mail.MailAddress In .Bcc
CDOMessage.BCC = CDOMessage.BCC & ToAddress.DisplayName & " <" & ToAddress.Address & ">; "
Next
Dim FileNames = .Attachments.[Select](Function(a) a.ContentStream).OfType(Of System.IO.FileStream)().[Select](Function(fs) fs.Name)
For Each File As String In FileNames
CDOMessage.AddAttachment(File)
Next
CDOMessage.TextBody = .Body
Try
CDOMessage.Send()
Catch ex As Exception
ErrorMessage = "CDO SEND ERROR: " & ex.Message
Finally
CDOMessage = Nothing
End Try
End With
Return ErrorMessage
End Function
这似乎每次都工作得很多,但我不禁想,如果有一种方法,我可以完全避免使用CDO发送方法。我已经用domain-com.mail.protection.outlook.com
和smtp.office365.com
服务器地址为SmtpClient
对象尝试了它,并且创建了一个新的Credentials
对象并将相同的用户名/密码设置为我用于CDO发送方法的对象。任何关于如何让System.Net.Mail
对象工作而不必诉诸老派CDO的想法?
编辑:为了澄清和完整性,我对该功能进行了另一项测试,在该测试中,我试图向该域以外的Google托管电子邮件帐户发送邮件。这一次,我再手动设置Credentials
属性在我的主要的邮件功能的try/catch块的SmtpClient
对象如下:
Try
MailServer.Credentials = New System.Net.NetworkCredential("[email protected]", "mypassword")
MailServer.Send(Email)
Return True
Catch ex As Exception
Dim CDOError As String = SendCDOEmail(Email, ex)
[...]
Finally
If Not Email Is Nothing Then
Email.Dispose()
End If
MailServer = Nothing
End Try
为了减少变量的数目尽可能地,我复制并粘贴用户名/密码信息直接来自CDO发送方法。这些凭证用于我的电子邮件帐户,这是我们公司的O365管理员帐户。不幸的是,测试仍然失败,完全相同SmtpFailedRecipientException
。给出的服务器响应为:
“邮箱不可用的服务器响应为:64年5月7日TenantAttribution;中继接入拒绝[SN1NAM01FT060.eop-nam01.prod.protection.outlook.com]”
个人,我想通过设置某种“通用”登录,或者实现一些允许从我的网络发送“匿名”的方法来摆脱为邮件服务器使用我的凭据,但这超出了范围这个问题。
无论如何,我只是不知道我错过了什么。也许这是我在Exchange配置中的设置,或者是我忽略的其他“怪癖”。感谢您的帮助。
另一件需要注意的事情:我做了一些额外的测试,其中有许多不同的配置。IF我改变了SmtpClient
主机smtp.office365.com
和设置端口587
(或25
),和的EnableSsl
属性设置为True
和明确提供的用户凭证(这是我试图让主要的事情远离),我能够得到一条消息,而不会触及Catch
区块并使用SendCDOEmail
方法(如上所述,它使用明确定义的凭证)。
Dim MailServer As New System.Net.Mail.SmtpClient("smtp.office365.com")
[...]
With MailServer
.Credentials = New System.Net.NetworkCredential("[email protected]", "mypassword")
.Port = 587 'or 25
.EnableSsl = True
.Send(Email)
End With
虽然这种方法在技术上的作品,我真的想从我的应用程序明确指定凭据脱身,尤其是因为我没有在Exchange服务器上的“通用”用户帐户,我可以使用。这是为SmtpClient
主机名使用domain-com.mail.protection.outlook.com
的目的。问题是我仍然必须提供CDO发送方法的凭据(主机名为smtp.office365.com
),所以没有什么可以解决的。
正如我怀疑,这个问题显然是由Exchange配置造成的。我最终配置了一个“连接器”,使用Microsoft的Office Support site上概述的Office 365 SMTP中继来发送邮件。一旦我设置了中继连接器并放入了SPF记录,我再次通过发送到我的托管Gmail帐户而没有提供任何凭据或为SmtpClient
对象设置任何其他设置。我第一次尝试时,配置显然没有完全生效,但我等了几分钟,然后再试。这一次,一切都经过了,没有碰到我的Catch
区块,我的邮件在Gmail收件箱中正确显示。
不过,显然我的多功能设备中的一些信息有些问题现在被垃圾邮件过滤器捕获,即使我更新了SPF记录。我希望这只是因为DNS尚未完全传播,但是,由于我在O365管理控制面板中进行了更改,因此我还没有100%确定。我要等上几个小时才能看到会发生什么。
下面是我用得到它的工作对我来说是步骤的破败(从他们的页面直接服用):
获取公共(静态)IP地址的设备或应用程序与发送。动态IP地址不受支持或不被允许。您可以与其他设备和用户共享您的静态IP地址,但不要与公司外的任何人分享IP地址。请稍后记下此IP地址。
选择域。确保选择了您的域名,例如contoso.com。点击管理DNS并找到MX记录。 MX记录将有一个指向地址的值,该值类似于cohowineinc-com.mail.protection.outlook.com,如以下屏幕截图所示。记下MX记录指向地址的值。你稍后需要这个。
检查应用程序或设备将发送到的域是否已经过验证。如果域未验证,则电子邮件可能会丢失,并且您将无法使用Exchange Online消息跟踪工具跟踪它们。
在Office 365,单击管理员,然后单击交易所去Exchange管理中心。
在Exchange管理中心中,单击邮件流,然后单击连接器。
-
检查为您的组织设置的连接器列表。如果没有从组织的电子邮件服务器列出到Office 365的连接器,请创建一个连接器。
a。要启动向导,请点击加号+。在第一个屏幕上,选择了在下面的截图所示的选项:
点击接下来,并给连接器的名称。b。在下一屏幕上,选择选项通过验证发送服务器的IP地址相匹配,属于你的组织这些IP地址之一,并从步骤1
添加C的IP地址。将所有其他字段保留其默认值,然后选择保存。
现在您已完成配置Office 365设置,请转到您的域名注册商的网站以更新您的DNS记录。编辑你的SPF记录。包括您在步骤1中记下的IP地址。完成的字符串应类似于:v = spf1 ip4:10.5.3.2 include:spf.protection.outlook.com〜all,其中10.5.3.2是您的公有IP地址。跳过这一步可能会导致电子邮件被发送到收件人的垃圾邮件文件夹。
现在,返回到设备,并在设置中,找到服务器或智能主机的条目,并输入MX记录指向地址在步骤记录值3
要测试配置,请从您的设备或应用程序发送测试电子邮件,并确认收件人已收到该电子邮件。
编辑:它看起来像被逮住的垃圾邮件过滤器消息的问题是由于DNS没有得到充分传播。在等待了24小时之后,我还没有看到我的内部系统发送的任何消息被隔离。
顺便说一句,为了充分披露,我们在我们的内部DNS服务器上有一个mail.domain
的CNAME(别名)记录,指向domain-com.mail.protection.outlook.com
,使我的编程更容易。此CNAME记录不会传播到公共DNS服务器,并且仅用于我们的内部网络(因此不包括.com
部分)。我已经有了很多编写用于SMTP服务器的mail.domain
地址的代码,因此设置DNS以处理快速转换并将其重定向到Microsoft服务器更为简单。在上面的问题我的示例代码,我替换为微软服务器地址的地址从任何进一步的混乱的问题不断,但我的代码实际上是这样的:
Private Function SendFTPSuccessMail() As Boolean
Dim Email As New System.Net.Mail.MailMessage
Dim MailServer As New System.Net.Mail.SmtpClient("mail.domain")
Dim SenderAddress As New System.Net.Mail.MailAddress("[email protected]", "IT HelpDesk")
Dim BodyText As String = String.Empty
With Email
.From = SenderAddress
.Bcc.Add(SenderAddress)
BodyText = "SOME TEXT FOR THE E-MAIL MESSAGE"
.To.Add(New System.Net.Mail.MailAddress("[email protected]", "Recipient"))
.Subject = "MESSAGE SUBJECT"
.Body = BodyText
End With
Try
MailServer.Send(Email)
Return True
Catch ex As Exception
Dim CDOError As String = SendCDOEmail(Email, ex)
If Not String.IsNullOrEmpty(CDOError) Then
Dim ExceptionMessage As String = ".NET SEND ERROR: " & ex.Message & vbCrLf
If Not ex.InnerException Is Nothing Then
ExceptionMessage += ex.InnerException.Message & vbCrLf
End If
ExceptionMessage += vbCrLf & CDOError
MessageBox.Show(ExceptionMessage, "NOTIFICATION E-MAIL FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
Else
Return True
End If
Finally
If Not Email Is Nothing Then
Email.Dispose()
End If
MailServer = Nothing
End Try
End Function
它看起来像我应该能够现在摆脱CDO发送方式,但我现在将其作为“故障安全”离开。一旦我成功运行了一段时间,我可能会完全删除它,这样我终于可以实现摆脱明确定义的用户凭证的目标。
您需要发送用户名和密码到domain-com.mail.protection.outlook.com
。它不接受Exchange Online组织外部收件人的匿名邮件。
MailServer.Credentials = New System.Net.NetworkCredential("username", "password");
感谢您的反馈,但您可能错过了原始文章中的部分内容,我表示我也尝试过这一部分,但没有成功。当我明天上午进入办公室时,我会更新我的问题,更详细地介绍我使用'Credentials'属性获得的例外情况。 –