【发布时间】:2017-09-01 19:41:54
【问题描述】:
我们公司最近通过我们的 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 和SmtpClient 对象的smtp.office365.com 服务器地址,以及创建一个新的Credentials 对象并放置与CDO 发送方法相同的用户名/密码.关于如何让 System.Net.Mail 对象工作而无需求助于老式 CDO 的任何想法?
编辑:为了清楚起见和完整起见,我对该功能进行了另一次测试,在该测试中我尝试向我在此域之外的一个 Google 托管的电子邮件帐户发送消息。这一次,我再次在我的主要邮件功能的 Try/Catch 块中为SmtpClient 对象手动设置Credentials 属性,如下所示:
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),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 作为主机名),所以没有真正得到解决。
【问题讨论】: