我们公司最近通过Office 365订阅从内部部署邮件服务器(CommunigatePro)迁移到托管邮件。从那时起,我遇到了从我的VB.NET应用程序向组织外部的人发送电子邮件的问题。我找到了一个解决方法,但我想知道我是否只是错过了可能导致问题的配置中的某些内容。
我发送邮件的代码非常标准(显然有点混淆了发布),但如果失败,我必须包含一个单独的函数来通过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("helpdesk@domain.com", "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("recipient@recipient.com", "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") = "myemailaddress@domain.com"
.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托管电子邮件帐户发送邮件。这一次,我再次在主邮件功能的Try / Catch块中手动设置Credentials
对象的SmtpClient
属性,如下所示:
Try
MailServer.Credentials = New System.Net.NetworkCredential("myemailaddress@domain.com", "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
失败。给出的服务器响应是:
“邮箱不可用。服务器响应为:5.7.64 TenantAttribution;拒绝中继访问[SN1NAM01FT060.eop-nam01.prod.protection.outlook.com]”
就个人而言,我想通过设置某种“通用”登录,或者实现一些允许“匿名”仅从我的网络发送的方法来摆脱使用我的邮件服务器凭据,但那是超出了这个问题的范围。
无论如何,我只是不确定我错过了什么。也许这是我的Exchange配置中的一个设置,或者我忽视的其他一些“怪癖”。谢谢你的帮助。
另外需要注意的一点是:我使用了许多不同的配置进行了一些额外的测试。 IF 我将SmtpClient
主机更改为smtp.office365.com
AND 将端口设置为587
(或25
),< em> AND 将EnableSsl
属性设置为True
AND 显式提供的用户凭据(这是我试图摆脱的主要内容),我是如果没有点击Catch
块并使用SendCDOEmail
方法(如上所述,使用明确定义的凭据),则能够收到消息。
Dim MailServer As New System.Net.Mail.SmtpClient("smtp.office365.com")
[...]
With MailServer
.Credentials = New System.Net.NetworkCredential("myemailaddress@domain.com", "mypassword")
.Port = 587 'or 25
.EnableSsl = True
.Send(Email)
End With
虽然这种方法在技术上有效,但我真的试图摆脱在我的应用程序中显式分配凭据,特别是因为我在Exchange服务器上没有可以使用的“通用”用户帐户。这是将domain-com.mail.protection.outlook.com
用于SmtpClient
主机名的目的。问题是我仍然必须提供CDO发送方法的凭据(主机名为smtp.office365.com
),因此没有真正得到解决。
答案 0 :(得分:0)
您需要向data
发送用户名和密码。它不接受Exchange Online组织外部收件人的匿名邮件。
controller
答案 1 :(得分:0)
我怀疑,问题显然是由Exchange配置引起的。我最终配置了一个&#34;连接器&#34;使用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记录将具有 POINTS TO ADDRESS 值,类似于cohowineinc-com.mail.protection.outlook.com,如以下屏幕截图所示。记下MX记录 POINTS TO ADDRESS 值。你以后会需要这个。
检查应用程序或设备将发送到的域是否已经过验证。如果未验证域,则电子邮件可能会丢失,您将无法使用Exchange Online邮件跟踪工具跟踪它们。
在Office 365中,单击管理,然后单击 Exchange 以转到Exchange管理中心。
在Exchange管理中心中,单击邮件流,然后单击连接器。
检查为您的组织设置的连接器列表。如果您的组织的电子邮件服务器中没有列出连接器到Office 365,请创建一个。
一个。要启动向导,请单击加号+。在第一个屏幕上,选择以下屏幕截图中描述的选项:
点击下一步,然后为连接器命名。
湾在下一个屏幕上,选择选项,验证发送服务器的IP地址是否属于您组织的其中一个IP地址,然后添加步骤1中的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地址。跳过此步骤可能会导致电子邮件发送到收件人的垃圾邮件文件夹。
现在,返回设备,在设置中找到服务器或智能主机的条目,然后输入您在步骤3中记录的MX记录 POINTS TO ADDRESS 值
要测试配置,请从您的设备或应用程序发送测试电子邮件,并确认收件人已收到该电子邮件。
编辑:由于DNS未完全传播,因此垃圾邮件过滤器 WAS 中的邮件会出现问题。等了24个多小时后,我还没有看到内部系统的任何邮件被隔离。
BTW,为了完全公开,我们在内部DNS服务器上有一个mail.domain
的CNAME(别名)记录,指向domain-com.mail.protection.outlook.com
,使我的编程更轻松。此CNAME记录不会传播到公共DNS服务器,仅供我们的内部网络使用(因此排除.com
部分)。我已经编写了很多代码来编写使用SMTP服务器的mail.domain
地址,以便更容易设置DNS以处理快速转换并将其重定向到Microsoft的服务器。在我上面问题的示例代码中,我将地址替换为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("helpdesk@domain.com", "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("recipient@recipient.com", "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发送方法了,但是现在我把它留在了&#34; failafe&#34;中。一旦我成功运行了一段时间,我可能会完全删除它,所以我最终可以实现我的目标,即删除明确定义的用户凭据。