2017-09-01 219 views
1

我们公司最近通过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如果所有收件人都在同一个(我的)域中,但方法会正常工作,但会为具有不同域的任何收件人地址发出SmtpFailedRecipientExceptionSmtpFailedRecipientsException。该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.comsmtp.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),所以没有什么可以解决的。

回答

0

正如我怀疑,这个问题显然是由Exchange配置造成的。我最终配置了一个“连接器”,使用Microsoft的Office Support site上概述的Office 365 SMTP中继来发送邮件。一旦我设置了中继连接器并放入了SPF记录,我再次通过发送到我的托管Gmail帐户而没有提供任何凭据或为SmtpClient对象设置任何其他设置。我第一次尝试时,配置显然没有完全生效,但我等了几分钟,然后再试。这一次,一切都经过了,没有碰到我的Catch区块,我的邮件在Gmail收件箱中正确显示。

不过,显然我的多功能设备中的一些信息有些问题现在被垃圾邮件过滤器捕获,即使我更新了SPF记录。我希望这只是因为DNS尚未完全传播,但是,由于我在O365管理控制面板中进行了更改,因此我还没有100%确定。我要等上几个小时才能看到会发生什么。

下面是我用得到它的工作对我来说是步骤的破败(从他们的页面直接服用):

  1. 获取公共(静态)IP地址的设备或应用程序与发送。动态IP地址不受支持或不被允许。您可以与其他设备和用户共享您的静态IP地址,但不要与公司外的任何人分享IP地址。请稍后记下此IP地址。

  2. Sign in to Office 365

  3. 选择。确保选择了您的域名,例如contoso.com。点击管理DNS并找到MX记录。 MX记录将有一个指向地址的值,该值类似于cohowineinc-com.mail.protection.outlook.com,如以下屏幕截图所示。记下MX记录指向地址的值。你稍后需要这个。
    Make a note of the MX record Points to address value.

  4. 检查应用程序或设备将发送到的域是否已经过验证。如果域未验证,则电子邮件可能会丢失,并且您将无法使用Exchange Online消息跟踪工具跟踪它们。

  5. 在Office 365,单击管理员,然后单击交易所去Exchange管理中心。

  6. 在Exchange管理中心中,单击邮件流,然后单击连接器。

  7. 检查为您的组织设置的连接器列表。如果没有从组织的电子邮件服务器列出到Office 365的连接器,请创建一个连接器。

    a。要启动向导,请点击加号+。在第一个屏幕上,选择了在下面的截图所示的选项:
    Choose from your organization's email server to Office 365
    点击接下来,并给连接器的名称。

    b。在下一屏幕上,选择选项通过验证发送服务器的IP地址相匹配,属于你的组织这些IP地址之一,并从步骤1

    添加C的IP地址。将所有其他字段保留其默认值,然后选择保存

  8. 现在您已完成配置Office 365设置,请转到您的域名注册商的网站以更新您的DNS记录。编辑你的SPF记录。包括您在步骤1中记下的IP地址。完成的字符串应类似于:v = spf1 ip4:10.5.3.2 include:spf.protection.outlook.com〜all,其中10.5.3.2是您的公有IP地址。跳过这一步可能会导致电子邮件被发送到收件人的垃圾邮件文件夹。

  9. 现在,返回到设备,并在设置中,找到服务器或智能主机的条目,并输入MX记录指向地址在步骤记录值3

  10. 要测试配置,请从您的设备或应用程序发送测试电子邮件,并确认收件人已收到该电子邮件。

编辑:它看起来像被逮住的垃圾邮件过滤器消息的问题是由于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发送方式,但我现在将其作为“故障安全”离开。一旦我成功运行了一段时间,我可能会完全删除它,这样我终于可以实现摆脱明确定义的用户凭证的目标。

0

您需要发送用户名和密码到domain-com.mail.protection.outlook.com。它不接受Exchange Online组织外部收件人的匿名邮件。

MailServer.Credentials = New System.Net.NetworkCredential("username", "password"); 
+0

感谢您的反馈,但您可能错过了原始文章中的部分内容,我表示我也尝试过这一部分,但没有成功。当我明天上午进入办公室时,我会更新我的问题,更详细地介绍我使用'Credentials'属性获得的例外情况。 –