title: Sending E-mail Through O365
body: <p>Our company recently migrated from an on-premise mail server (CommunigatePro) to hosted mail through our Office 365 subscription.  Ever since then, I've encountered problems sending e-mail messages to people outside of our organization from my VB.NET applications.  I've found a workaround, but I'm wondering if I'm simply missing something in the configuration that might be causing the issues.</p>
  
  <p>My code for sending a message is pretty standard (obviously obfuscated a bit for posting), but I've had to include a separate function for sending the message via CDO if it fails:</p>
  
  <pre><code>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: " &amp; ex.Message &amp; vbCrLf
  
              If Not ex.InnerException Is Nothing Then
                  ExceptionMessage += ex.InnerException.Message &amp; vbCrLf
              End If
  
              ExceptionMessage += vbCrLf &amp; 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
  </code></pre>
  
  <p>The <code>MailServer.Send</code> method works without error if all of the recipients are within the same (my) domain, but will throw an <code>SmtpFailedRecipientException</code> or <code>SmtpFailedRecipientsException</code> for any recipient address with a different domain.  The <code>SendCDOEmail</code> function is as follows:</p>
  
  <pre><code>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("&lt;", "").Replace("&gt;", ""))
          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("&lt;", "").Replace("&gt;", "")
  
          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 &amp; " &lt;" &amp; .From.Address &amp; "&gt;"
  
          For Each ToAddress As System.Net.Mail.MailAddress In .To
              CDOMessage.To = CDOMessage.To &amp; ToAddress.DisplayName &amp; " &lt;" &amp; ToAddress.Address &amp; "&gt;; "
          Next
  
          For Each ToAddress As System.Net.Mail.MailAddress In .CC
              CDOMessage.CC = CDOMessage.CC &amp; ToAddress.DisplayName &amp; " &lt;" &amp; ToAddress.Address &amp; "&gt;; "
          Next
  
          For Each ToAddress As System.Net.Mail.MailAddress In .Bcc
              CDOMessage.BCC = CDOMessage.BCC &amp; ToAddress.DisplayName &amp; " &lt;" &amp; ToAddress.Address &amp; "&gt;; "
          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: " &amp; ex.Message
          Finally
              CDOMessage = Nothing
          End Try
      End With
  
      Return ErrorMessage
  End Function
  </code></pre>
  
  <p>This seems to work pretty much every time, but I can't help wondering if there's a way that I can totally avoid using the CDO send method.  I've tried it with both the <code>domain-com.mail.protection.outlook.com</code> and the <code>smtp.office365.com</code> server addresses for the <code>SmtpClient</code> object, as well as creating a new <code>Credentials</code> object and putting the same username/password as I use for the CDO sending method.  Any ideas as to how I can get the <code>System.Net.Mail</code> objects to work without having to resort to old-school CDO?</p>
  
  <p><strong>EDIT:</strong> For the sake of clarification and completeness, I ran another test of the function where I attempted to send a message to one of my Google-hosted e-mail accounts outside of this domain.  This time, I again manually set the <code>Credentials</code> property for the <code>SmtpClient</code> object in the Try/Catch block of my main mailing function as follows:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>To minimize the number of variables as much as possible, I copied and pasted the username/password information directly from the CDO sending method.  These credentials are for my e-mail account, which is an O365 admin account for our company.  Unfortunately, the test still failed with the exact same <code>SmtpFailedRecipientException</code>. The server response given is:</p>
  
  <p><em>"Mailbox unavailable. The server response was: 5.7.64 TenantAttribution; Relay Access Denied [SN1NAM01FT060.eop-nam01.prod.protection.outlook.com]"</em></p>
  
  <p>Personally, I'd like to get rid of using my credentials for the mail server anyway by setting up some sort of "generic" login, or implementing some method that would allow "anonymous" sending from my network only, but that's beyond the scope of this question.</p>
  
  <p>Regardless, I'm simply not sure what I'm missing.  Maybe it's a setting in my Exchange configuration, or some other "quirk" I'm overlooking.  Thank you for your help.</p>
  
  <p><strong>One other thing to note:</strong> I did some additional testing with a number of different configurations.  <em>IF</em> I changed the <code>SmtpClient</code> host to <code>smtp.office365.com</code> <em>AND</em> set the port to <code>587</code> (or <code>25</code>), <em>AND</em> set the <code>EnableSsl</code> property to <code>True</code> <em>AND</em> explicitly provided user credentials (which is the main thing I was trying to get away from), I was able to get a message to go through without it hitting the <code>Catch</code> block and using the <code>SendCDOEmail</code> method (which, as noted above, uses explicitly defined credentials).</p>
  
  <pre><code>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
  </code></pre>
  
  <p>While this method technically works, I'm really trying to get away from explicitly assigning the credentials in my application, especially since I don't have a "generic" user account on the Exchange server that I can use.  That was the purpose of using the <code>domain-com.mail.protection.outlook.com</code> for the <code>SmtpClient</code> host name.  The problem is that I <em>still</em> have to provide credentials for the CDO sending method (with <code>smtp.office365.com</code> for the host name), so nothing really gets resolved.</p>