SMTP over SSL (Gmail.com, Live.com, certificates)

If you just need to send an e-mail through SSL, refer to Send if server requires SSL (like Gmail) topic. The current guide provides the detailed explanation of SSL and TLS related matters and security certificates.


Features supported

MailBee.NET fully supports all types of SSL connections with e-mail servers. This includes:

Nowadays, SSL is usually not just an advanced option for those users which demand better security, it's a must. MS Exchange 2010 by default won't authenticate clients unless the connection is protected with SSL encryption, not to mention such major e-mail services like Gmail.com and Live.com.

Due to the above, all individual MailBee.NET components like Smtp provide basic SSL/TLS capabilities at no additional cost. You'll need MailBee.NET Security license only for advanced use (validate server's certificate, supply client's certificate, S/MIME, and so on).


Auto-detect SSL settings based on the port and host

By default, MailBee.Global.AutodetectPortAndSslMode is true which means the following:

Some servers do not support STARTTLS on port 587. SslStartupMode.UseStartTlsIfSupported mode does not cause any problems with because MailBee.NET won't issue STARTTLS command in such case.

However, some servers require you to use STARTTLS even on port 25, but MailBee.NET never tries this automatically (SMTP protocol lets the client know if the server "supports" STARTTLS but not if the server "requires" it). If you need STARTTLS on port 25, you should explicitly set SmtpServer.SslMode property value to SslStartupMode.UseStartTlsIfSupported or SslStartupMode.UseStartTls, depending on whether SSL connection is optional or a must for your client.

You can disable the auto-detection by setting MailBee.Global.AutodetectPortAndSslMode to false. This might be required only special cases such as when the server runs a regular SMTP service on a dedicated SSL port (however, this usually means the server is configured incorrectly and ports 25 and 465 are mixed up). Another case if when you need full manual control over SSL mode. See Advanced STARTTLS topic for details.

Note for VB users. Since Global is a keyword in VB, we can't just use Global.AutodetectPortAndSslMode even if MailBee namespace is imported. Full type name with the namespace part is required. This concerns all properties of Global class. In C#, you can use both MailBee.Global.AutodetectPortAndSslMode and Global.AutodetectPortAndSslMode.


Connect to dedicated SSL port

In the default state (MailBee.Global.AutodetectPortAndSslMode is true), you just need to set the port number since the dedicated SSL port is never 25. Usually, it's 465. To see how, refer to Send if server requires SSL (like Gmail)topic.

In the current topic, let's not rely on MailBee.Global.AutodetectPortAndSslMode property value. It doesn't matter if it's true or false, this code snippet will properly configure the dedicated SSL port connection anyway:

Smtp mailer = new Smtp();
SmtpServer relay = mailer.SmtpServers.Add("mail.here.com""joe""secret");
relay.Port = 465;
relay.SslMode = SslStartupMode.OnConnect;
Dim mailer As New Smtp()
Dim relay As SmtpServer = mailer.SmtpServers.Add("mail.here.com""joe""secret")
relay.Port = 465
relay.SslMode = SslStartupMode.OnConnect

Here and below, we assume MailBee, MailBee.SmtpMail and MailBee.Security namespaces are imported and the license key is set. See Import namespaces and set license key topic for details.


Connect to regular port and use STARTTLS to activate SSL

MailBee.NET does not automatically use STARTTLS on port 25 even if it's supported so let's demonstrate how to enable it explicitly. There are two methods of how you can issue STARTTLS command:

Simple method is really simple, we just need to set a single property:

Smtp mailer = new Smtp();
SmtpServer relay = mailer.SmtpServers.Add("smtp.here.com""joe""secret");
relay.SslMode = SslStartupMode.UseStartTlsIfSupported;
Dim mailer As New Smtp()
Dim relay As SmtpServer = mailer.SmtpServers.Add("smtp.here.com""joe""secret")
relay.SslMode = SslStartupMode.UseStartTlsIfSupported

Advanced STARTTLS

Advanced method makes sense if you don't know at the moment of connecting to the server if you will use STARTTLS or not. Let's imagine you don't want to use SSL connection unless absolutely required by the server (in the end, SSL is resource consuming).

Most servers even if support STARTTLS, let you not use it if you don't want to. However, some servers even do not allow you to authenticate until the connection is secured. They do this by advertising STARTTLS and not advertising any authentication methods in clear-text mode. You can use this as an indirect sign that the server wants STARTTLS, and issue it then:

// Disable SSL auto-detection as we need full manual control.
MailBee.Global.AutodetectPortAndSslMode = false;

Smtp mailer = new Smtp();

// You can view the log on what exactly your server advertizes
// in the supported capabilities before and after STARTTLS.
mailer.Log.Enabled = true;
mailer.Log.Filename = @"C:\Temp\log.txt";
mailer.Log.Clear();

// Add SMTP relay, use authentication but nothing special to SSL
// at this point. If your regular port is 587 rather than 25,
// you should add "relay.Port = 465" statement.
SmtpServer relay = mailer.SmtpServers.Add(
    "smtp.provider.com", "john.doe@company.com", "secret");

// Connect and say hello, will get the list of capabilities then.
mailer.Connect();
mailer.Hello();

// If STARTTLS is supported but no authentication methods are available,
// assume they will hopefully become available after STARTTLS.
if (mailer.GetExtension("starttls") != null &&
    mailer.GetSupportedAuthMethods() == AuthenticationMethods.None)
{
    mailer.StartTls();

    // Request which the capabilities are available under SSL layer.
    mailer.Hello();
}

if (mailer.GetSupportedAuthMethods() == AuthenticationMethods.None)
{
    // This is a rare case: the server does support STARTTLS and does not
    // advertize any authentication methods, but it does not hide its
    // supported authentication methods for a purpose (to urge us to use
    // STARTTLS). The server simply does not support authentication and
    // this has nothing to do with STARTTLS.
    Console.WriteLine("This server does not support authentication");
    mailer.Disconnect();
}
else
{
    // If we're here, authentication is available. Let's check
    // if it required to issue STARTTLS to make it available.
    if (mailer.IsSslConnection)
    {
        Console.WriteLine(
            "This server supports authentication in SSL mode only");
    }
    else
    {
        Console.WriteLine(
            "This server supports authentication in clear-text mode");
    }

    // Login should be possible now.
    mailer.Login();

    // Send an e-mail.
    mailer.From.Email = "john.doe@company.com";
    mailer.To.Add("jane@domain.com");
    mailer.Subject = "Test message";
    mailer.Send();

    // As we connected manually, we need to call disconnect.
    mailer.Disconnect();
}
' Disable SSL auto-detection as we need full manual control.
MailBee.Global.AutodetectPortAndSslMode = False

Dim mailer As New Smtp()

' You can view the log on what exactly your server advertizes
' in the supported capabilities before and after STARTTLS.
mailer.Log.Enabled = True
mailer.Log.Filename = "C:\Temp\log.txt"
mailer.Log.Clear()

' Add SMTP relay, use authentication but nothing special to SSL
' at this point. If your regular port is 587 rather than 25,
' you should add "relay.Port = 465" statement.
Dim relay As SmtpServer = mailer.SmtpServers.Add( _
    "smtp.provider.com", "john.doe@company.com", "secret")

' Connect and say hello, will get the list of capabilities then.
mailer.Connect()
mailer.Hello()

' If STARTTLS is supported but no authentication methods are available,
' assume they will hopefully become available after STARTTLS.
If Not (mailer.GetExtension("starttls") Is Nothing) And _
        mailer.GetSupportedAuthMethods() = AuthenticationMethods.None Then
    mailer.StartTls()

    ' Request which the capabilities are available under SSL layer.
    mailer.Hello()
End If

If mailer.GetSupportedAuthMethods() = AuthenticationMethods.None Then
    ' This is a rare case: the server does support STARTTLS and does not
    ' advertize any authentication methods, but it does not hide its
    ' supported authentication methods for a purpose (to urge us to use
    ' STARTTLS). The server simply does not support authentication and
    ' this has nothing to do with STARTTLS.
    Console.WriteLine("This server does not support authentication")
    mailer.Disconnect()
Else
    ' If we're here, authentication is available. Let's check
    ' if it required to issue STARTTLS to make it available.
    If mailer.IsSslConnection Then
        Console.WriteLine( _
            "This server supports authentication in SSL mode only")
    Else
        Console.WriteLine( _
            "This server supports authentication in clear-text mode")
    End If

    ' Login should be possible now.
    mailer.Login()

    ' Send an e-mail.
    mailer.From.Email = "john.doe@company.com"
    mailer.To.Add("jane@domain.com")
    mailer.Subject = "Test message"
    mailer.Send()

    ' As we connected manually, we need to call disconnect.
    mailer.Disconnect()
End If

Configure SSL protocol (Auto, TLS 1.2, TLS 1.3)

By default, MailBee.NET automatically uses the best available security protocol for SSL layer (in most cases, TLS 1.2). However, some servers do support TLS but do not let the client automatically select it as the best supported protocol. In such case, you'll need to specify the protocol manually. In extremely rare cases, the server may support TLS 1.0 only (inspite of they are considered weak). Or, you can force TLS 1.2 and never permit protocol degradation. That's how you can select it:

Smtp mailer = new Smtp();
SmtpServer relay = mailer.SmtpServers.Add("smtp.here.com""joe@here.com""secret");
relay.Port = 465;
relay.SslMode = SslStartupMode.OnConnect;

// Always require TLS 1.2 protocol.
relay.SslProtocol = SecurityProtocol.Tls12;
Dim mailer As New Smtp()
Dim relay As SmtpServer = mailer.SmtpServers.Add("smtp.here.com""joe@here.com""secret")
relay.Port = 465
relay.SslMode = SslStartupMode.OnConnect

' Always require TLS 1.2 protocol.
relay.SslProtocol = SecurityProtocol.Tls12

You can also set TLS 1.3 this way but please note that some .NET versions may not support it (or TLS 1.3 might be disabled on OS level). .NET Core 3.0+ is recommended.


Supply client certificate and check server certificate

All previous topics in this guide assumed the situation when you just need to use SSL because it's required by the server. They didn't put the focus on security. On other hand, this topic highlights security-related matters.

In MailBee.NET licensing model, the advanced security features which fall outside the scope of sending e-mail (like validation of server certificates) require MailBee.NET Security license. This license covers all secure e-mail functions which deal with certificates, including SSL certificates (explained here) and S/MIME (refer to Sign and encrypt e-mail with S/MIME guide to learn more).

We assume that MailBee, MailBee.SmtpMail and MailBee.Security namespaces are already imported. Also, to unlock the certificates functionality, MailBee.Global.LicenseKey must include Security license in addition to SMTP license. MailBee.NET trial keys and MailBee.NET Objects unified keys include all the required licenses.

The sample below demonstrates the use of the client's private certificate to prove its identity to the server, and the validation of the server's public certificate by the client:

Smtp mailer = new Smtp();

// Use SMTP relay server with authentication and STARTTLS over port 25.
SmtpServer relay = mailer.SmtpServers.Add(
    "mail.company.com", "j.doe@company.com", "secret");
relay.SslMode = SslStartupMode.UseStartTls;

// Supply the client's certificate (taken from a file).
relay.SslCertificates.Client =
    new Certificate("C:\\Docs\\john.pfx", CertFileType.Pfx, "password");

// Will cause MailBee.NET to throw an exception if anything is wrong
// with the server's certificate.
relay.SslCertificates.AutoValidation = CertificateValidationFlags.All;

// You can view the log on what happens during SSL negotiation.
mailer.Log.Enabled = true;
mailer.Log.Filename = "C:\\Temp\\log.txt";
mailer.Log.Clear();

// Send an e-mail.
mailer.From.Email = "j.doe@company.com";
mailer.To.Add("jane@domain.com");
mailer.Subject = "Test message";

// Try to send e-mail and validate the server's certificate
// during SSL negotiation procedure. If the certificate is
// incorrect, report all the problems we have found.
try
{
    mailer.Send();
}
catch (MailBeeCertificateValidationException e)
{
    // Server certificate is not valid.
    Console.WriteLine(e.Message);

    // Build a string which lists the names of all the flags
    // the certificate validation process has failed for.
    string reasons = string.Empty;
    CertificateValidationFlags flags = e.Status;
    int mask = 1;
    while (flags > 0)
    {
        CertificateValidationFlags flag =
            flags & (CertificateValidationFlags)mask;
        if (flag != CertificateValidationFlags.None)
        {
            if (reasons.Length > 0)
            {
                reasons += ", ";
            }
            reasons += flag.ToString();
            flags &= (CertificateValidationFlags)~mask;
        }
        mask <<= 1;
    }

    Console.WriteLine("Reasons: " + reasons);
}
Dim mailer As New Smtp()

' Use SMTP relay server with authentication and STARTTLS over port 25.
Dim relay As SmtpServer = mailer.SmtpServers.Add( _
    "mail.company.com", "j.doe@company.com", "secret")
relay.SslMode = SslStartupMode.UseStartTls

' Supply the client's certificate (taken from a file).
relay.SslCertificates.Client = _
    New Certificate("C:\Docs\john.pfx", CertFileType.Pfx, "password")

' Will cause MailBee.NET to throw an exception if anything is wrong
' with the server's certificate.
relay.SslCertificates.AutoValidation = CertificateValidationFlags.All

' You can view the log on what happens during SSL negotiation.
mailer.Log.Enabled = True
mailer.Log.Filename = "C:\Temp\log.txt"
mailer.Log.Clear()

' Send an e-mail.
mailer.From.Email = "j.doe@company.com"
mailer.To.Add("jane@domain.com")
mailer.Subject = "Test message"

' Try to send e-mail and validate the server's certificate
' during SSL negotiation procedure. If the certificate is
' incorrect, report all the problems we have found.
Try
   mailer.Send()
Catch e As MailBeeCertificateValidationException
   ' Server certificate is not valid.
   Console.WriteLine(e.Message)

   ' Build a string which lists the names of all the flags
   ' the certificate validation process has failed for.
   Dim reasons As String = String.Empty
   Dim flags As CertificateValidationFlags = e.Status
   Dim mask As Integer = 1
   While flags > 0
      Dim flag As CertificateValidationFlags = flags And _
            CType(mask, CertificateValidationFlags)
      If flag <> CertificateValidationFlags.None Then
         If reasons.Length > 0 Then
            reasons &= ", "
         End If
         reasons &= flag.ToString()
         flags = flags And CType(Not mask, CertificateValidationFlags)
      End If
      mask <<= 1
   End While

   Console.WriteLine("Reasons: " & reasons)
End Try

In this sample, the client's private certificate is taken from a file. To learn how to take it from the system store (Windows Registry), refer to ClientServerCertificates class documentation.

You can also use "supply client certificate" and "check server certificate" parts of this sample independently. For instance, a client can validate server certificates but never provide its own. This approach is used in the next sample.


Validate server certificate selectively

In the previous topic, MailBee.NET threw an exception on any certificate validation errors. In case if you want to ignore some errors, you can adjust this line:

relay.SslCertificates.AutoValidation = CertificateValidationFlags.All;
relay.SslCertificates.AutoValidation = CertificateValidationFlags.All

and replace it with somewhat like:

relay.SslCertificates.AutoValidation = (CertificateValidationFlags)
    (CertificateValidationFlags.All - CertificateValidationFlags.IsUntrustedRoot);
relay.SslCertificates.AutoValidation = CType( _
    CertificateValidationFlags.All - CertificateValidationFlags.IsUntrustedRoot, _
    CertificateValidationFlags)

The above will ignore "certificate is self-signed" errors (CertificateValidationFlags.IsUntrustedRoot flag will not be checked). All other certificate errors will still cause MailBee.NET to throw an exception.


Validate server certificate without throwing exception

You may want to validate the server's certificate without getting validation exceptions. For instance, you neither want to close the connection in case of a validation error nor want to ignore such errors. The most typical case is to show the discovered errors to the user so that it would he the user who decides whether to proceed.

In the previous sample, although you could narrow the list of certificate errors to be detected, you couldn't proceed if the error which is on that list has still been found in the certificate. It would be nice if you could examine which errors have been detected, ask the user, and continue the SMTP session without reconnection if the user decided to ignore these errors.

The sample below validates the server certificate in the "no-exceptions" way. The sample cancels the SMTP session in case if the server's certificate contains any critical errors but tolerates "certificate is self-signed" error. However, unlike the previous sample where this error was simply ignored, this sample warns the user if it has happened:

// Full manual control needed since we use Smtp.StartTls method.
MailBee.Global.AutodetectPortAndSslMode = false;

Smtp mailer = new Smtp();

// Log file is helpful for trouble-shooting.
mailer.Log.Enabled = true;
mailer.Log.Filename = "C:\\Temp\\log.txt";
mailer.Log.Clear();

// Use SMTP relay server with authentication.
SmtpServer relay = mailer.SmtpServers.Add(
    "smtp.company.com", "j.doe@company.com", "secret");

// Send an e-mail.
mailer.From.Email = "j.doe@company.com";
mailer.To.Add("jane@domain.com");
mailer.Subject = "Test message";

mailer.Connect();
mailer.Hello();
mailer.StartTls();

// Validate the server's certificate. This does not throw validation
// exception though, just returns the status.
CertificateValidationFlags flags =
    relay.SslCertificates.Server.Validate();

if ((flags & ~CertificateValidationFlags.IsUntrustedRoot) > 0)
{
    // Build a string which lists the names of all the flags
    // the certificate validation process has failed for.
    string reasons = string.Empty;
    int mask = 1;
    while (flags > 0)
    {
        CertificateValidationFlags flag =
            flags & (CertificateValidationFlags)mask;
        if (flag != CertificateValidationFlags.None)
        {
            if (reasons.Length > 0)
            {
                reasons += ", ";
            }
            reasons += flag.ToString();
            flags &= (CertificateValidationFlags)~mask;
        }
        mask <<= 1;
    }

    // We don't want to proceed then. Just report the problems
    // to the user and disconnect then.
    Console.WriteLine("Server's certificate problems: " + reasons);
}
else
{
    // Warn the user about self-signed certificate but proceed.
    if ((flags & CertificateValidationFlags.IsUntrustedRoot) > 0)
    {
        Console.WriteLine(
            "Server's certificate is self-signed but the rest is OK");
    }
    else
    {
        Console.WriteLine(
            "Server's certificate is perfectly OK");
    }

    // After STARTTLS, must send hello again. Not required for dedicated
    // SSL port connections.
    mailer.Hello();

    // Authenticate through the secure and trusted channel. Note that you
    // usually need to provide the client's credentials and authenticate
    // only in case if you do not supply the client's certificate.
    mailer.Login();

    // Send through the secure and trusted channel.
    mailer.Send();
}

// As we connected manually, we need to call disconnect.
mailer.Disconnect();
' Full manual control needed since we use Smtp.StartTls method.
MailBee.Global.AutodetectPortAndSslMode = False

Dim mailer As New Smtp()

' Log file is helpful for trouble-shooting.
mailer.Log.Enabled = True
mailer.Log.Filename = "C:\Temp\log.txt"
mailer.Log.Clear()

' Use SMTP relay server with authentication.
Dim relay As SmtpServer = mailer.SmtpServers.Add( _
    "smtp.company.com", "j.doe@company.com", "secret")

' Send an e-mail.
mailer.From.Email = "j.doe@company.com"
mailer.To.Add("jane@domain.com")
mailer.Subject = "Test message"

mailer.Connect()
mailer.Hello()
mailer.StartTls()

' Validate the server's certificate. This does not throw validation
' exception though, just returns the status.
Dim flags As CertificateValidationFlags = _
    relay.SslCertificates.Server.Validate()

If (flags And Not CertificateValidationFlags.IsUntrustedRoot) > 0 Then
    ' Build a string which lists the names of all the flags
    ' the certificate validation process has failed for.
    Dim reasons As String = String.Empty
    Dim mask As Integer = 1
    While flags > 0
        Dim flag As CertificateValidationFlags = flags And _
            CType(mask, CertificateValidationFlags)
        If flag <> CertificateValidationFlags.None Then
            If reasons.Length > 0 Then
                reasons &= ", "
            End If
            reasons &= flag.ToString()
            flags = flags And CType(Not mask, CertificateValidationFlags)
        End If
        mask <<= 1
    End While

    ' We don't want to proceed then. Just report the problems
    ' to the user and disconnect then.
    Console.WriteLine("Server's certificate problems: " & reasons)
Else
    ' Warn the user about self-signed certificate but proceed.
    If (flags And CertificateValidationFlags.IsUntrustedRoot) > 0 Then
        Console.WriteLine( _
            "Server's certificate is self-signed but the rest is OK")
    Else
        Console.WriteLine("Server's certificate is perfectly OK")
    End If

    ' After STARTTLS, must send hello again. Not required for dedicated
    ' SSL port connections.
    mailer.Hello()

    ' Authenticate through the secure and trusted channel. Note that you
    ' usually need to provide the client's credentials and authenticate
    ' only in case if you do not supply the client's certificate.
    mailer.Login()

    ' Send through the secure and trusted channel.
    mailer.Send()
End If

' As we connected manually, we need to call disconnect.
mailer.Disconnect()

With the approach above, you can implement really flexible processing and validation of server certificates.


Send feedback to AfterLogic

Copyright © 2006-2023 AfterLogic Corporation. All rights reserved.