我的发送消息代码非常标准 (显然为了发布而混淆了一些),但如果失败了,我必须包含一个通过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发送方法的方法。 我尝试过使用
SmtpClient
对象的domain-com.mail.protection.outlook.com
和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。服务器响应如下:"Mailbox unavailable. The server response was: 5.7.64 TenantAttribution; Relay Access Denied [SN1NAM01FT060.eop-nam01.prod.protection.outlook.com]"。个人而言,我想通过设置某种“通用”登录或实现一些方法来允许仅在我的网络中进行“匿名”发送,以摆脱使用邮件服务器的凭据,但这超出了本问题的范围。无论如何,我只是不确定自己缺少什么。也许是我Exchange配置中的某个设置或其他我忽略的“怪异”之处。谢谢您的帮助。
另外需要注意的一点是:我进行了一些其他配置的额外测试。如果我将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("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
作为主机名)提供凭据,因此并没有真正解决任何问题。