通过O365发送电子邮件

时间:2017-09-01 19:41:54

标签: vb.net email office365

我们公司最近通过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方法可以正常运行,但会为具有不同域的任何收件人地址抛出SmtpFailedRecipientExceptionSmtpFailedRecipientsExceptionSendCDOEmail函数如下:

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.comSmtpClient服务器地址尝试了它,以及创建一个新的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),因此没有真正得到解决。

2 个答案:

答案 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%确定。我要等几个小时看看会发生什么。

这是我用来让它为我工作的步骤的破败(直接从他们的页面中获取):

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

  2. Sign in to Office 365

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

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

  5. 在Office 365中,单击管理,然后单击 Exchange 以转到Exchange管理中心。

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

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

    一个。要启动向导,请单击加号+。在第一个屏幕上,选择以下屏幕截图中描述的选项:
    Choose from your organization's email server to Office 365
    点击下一步,然后为连接器命名。

    湾在下一个屏幕上,选择选项,验证发送服务器的IP地址是否属于您组织的其中一个IP地址,然后添加步骤1中的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. 现在,返回设备,在设置中找到服务器或智能主机的条目,然后输入您在步骤3中记录的MX记录 POINTS TO ADDRESS

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

  11. 编辑:由于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;中。一旦我成功运行了一段时间,我可能会完全删除它,所以我最终可以实现我的目标,即删除明确定义的用户凭据。

相关问题