Outlook.com announces IMAP support

We’re happy to announce that Microsoft finally added IMAP (and OAuth) support for Outlook.com (this includes @hotmail accounts). With yesterday’s announcement, Outlook has a richer email experience across devices and apps.

Here you can find Outlook.com settings.

Copy to public folder: “There is no replica for that mailbox on this server.”

When copying an mail from the personal folder to a Public folder, you may receive “There is no replica for that mailbox on this server.” error.

This error is generated by Exchange server and unfortunately this is the Exchange limitation. Here’s Microsoft’s response to this problem:

Copying from user mailboxes to public folders does not work with Exchange IMAP4.
Your users need to copy the message to a personal folder and then back up to the public folder (append) or forward it to an address that gets archived to a public folder.

Workaround

It seems the only way to workaround this is by downloading a message and uploading it to public folder. Important thing is that you don’t need to parse email message at all – you just download and upload raw eml data. Please also note that public folder may be in fact stored on a another IMAP server instance (different server address) – this is usually indicated during logon with REFERRAL error.

// C#

using(Imap imap = new Imap())
{
    imap.Connect("imap.example.com");   // or ConnectSSL for SSL
    imap.UseBestLogin("user", "password");

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.All);

    foreach (long uid in uids)
    {
        var eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);

        if (email.Subject.Contains("[REF"))
            UploadToPublic(email);
    }
    imap.Close();
}
' VB.NET

Using imap As New Imap()
	imap.Connect("imap.example.com")	' or ConnectSSL for SSL
	imap.UseBestLogin("user", "password")

	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search(Flag.All)

	For Each uid As Long In uids
		Dim eml = imap.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)

		If email.Subject.Contains("[REF") Then
			UploadToPublic(email)
		End If
	Next
	imap.Close()
End Using

Here is the body of UploadToPublic method:

// C# code

private void UploadToPublic(IMail email)
{
    using (Imap imap = new Imap())
    {
        imap.Connect("server");  // or ConnectSSL for SSL
        imap.Login("user", "password");
    
        imap.UploadMessage("#Public/Cases", email);
    
        imap.Close();
    }
}
' VB.NET code

Private Sub UploadToPublic(email As IMail)
	Using imap As New Imap()
		imap.Connect("server")
		' or ConnectSSL for SSL
		imap.Login("user", "password")

		imap.UploadMessage("#Public/Cases", email)

		imap.Close()
	End Using
End Sub

Get IIS pickup directory location

If you plan to use local IIS SMTP service to send emails you created using Mail.dll, you’ll need to save those emails to IIS pickup folder.

Default folder location is “c:\Inetpub\mailroot\Pickup”

There is a way to get IIS pickup folder location directly from IIS metabase. To get this path programmatically we’ll use IisPickupDirectory class. Unfortunatelly this class is not public, we’ll use its name to get its type and Activator class to invoke private static method GetPickupDirectory.

To get type reference, we need to specify fully qualified type name “System.Net.Mail.IisPickupDirectory System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”. We can also search CurrentDomain’s assemblies to find System assembly and get its full name.

Assembly system = AppDomain.CurrentDomain.GetAssemblies()
    .First(x => x.GetName().Name == "System");

Type iisPickupType = Type.GetType(
    "System.Net.Mail.IisPickupDirectory, "
    + system.GetName().FullName,
    true);
// -or- use fully qualified assembly name directly:
//Type iisPickupType = Type.GetType(
//    "System.Net.Mail.IisPickupDirectory, "
//    + "System, Version=4.0.0.0, Culture=neutral, " 
//    + "PublicKeyToken=b77a5c561934e089",
//    true);

string pickupFolder = (string)iisPickupType.InvokeMember(
    "GetPickupDirectory",
    BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
    null, null, null);

“Cannot get IIS pickup directory.” error

Unfortunately, this exception is raised when any kind of problem occurs, while trying to determine the location of IIS/SMTP pickup directory.

A common cause is simply missing IIS SMTP service.

The pickup directory is stored in the IIS Metabase, so if the account that your web-app runs as does not have access to the required nodes, this error can be thrown. Metabase permissions are separate from file permissions, so you explore it with Metabase explorer (part of the IIS resource kit).

These nodes need to have read permission given to your web-app user: \LM, \LM\Smtpsrv\ and \LM\Smtpsrv\1

Read system.net/mailSettings/smtp settings from web.config

There is a standard way of specifying SMTP settings in .NET applications. .NET uses config files (app.config or web.config in case of ASP.NET) and element to specify the appropriate SMTP parameters to send e-mail.

Sample configuration (in this case Gmail SMTP settings) looks as follows:

<configuration>

<system.net>
  <mailSettings>
    <smtp deliveryMethod="network" from="pam@gmail.com">
      <network
        host="smtp.gmail.com"
        port="465"
        enableSsl="true"
        userName="pam@gmail.com"
        password="password"
    />
    </smtp>
  </mailSettings>
</system.net>

</configuration>

If port attribute is omitted default value (25) is used. SMTP protocol typically uses ports 587 and 25 for non SSL connections, and port 465 for SSL ones.

Although Mail.dll SMTP component does not support reading from web.config directly, it is quite easy to read those settings programmatically and use them with Mail.dll classes.

Here’s the simple sample that reads from mailSettings section:

SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");

string from = section.From;
string host = section.Network.Host;
int port = section.Network.Port;
bool enableSsl = section.Network.EnableSsl;
string user = section.Network.UserName;
string password = section.Network.Password;

Use web.config’s mailSettings with Mail.dll

In most cases you want to send email via SMTP server (DeliveryMethod set to SmtpDeliveryMethod.Network). Here’s the sample that uses web.config settings and Mail.dll’s STMP component to send an email message:

SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");

IMail email = Fluent.Mail
            .Text("Hi, how are you?")
            .Subject("Hello")
            .To("to@example.com")
            .From(section.From)
            .Create();

using (Smtp client = new Smtp())
{
    client.Connect(section.Network.Host, section.Network.Port, 
        section.Network.EnableSsl);
    client.UseBestLogin(section.Network.UserName, section.Network.Password);
    client.SendMessage(email);
    client.Close();
}

IIS pickup folder

If you plan to use local IIS SMTP service to send emails you created using Mail.dll, you’ll need to save those emails to IIS pickup folder.

You can specify folder location explicitly using SpecifiedPickupDirectory.PickupDirectoryLocation (default location is “c:\Inetpub\mailroot\Pickup”). You can also use SmtpDeliveryMethod.PickupDirectoryFromIis constant – in this case we’ll get pickup folder location directly from IIS metabase.

Following is the code that recognizes different DeliveryMethods and acts accordingly:

SmtpSection section = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
IMail email = Fluent.Mail
            .Text("Hi, how are you?")
            .Subject("Hello")
            .To("lesnikowski@limilabs.com")
            .From(section.From)
            .Create();

if (section.DeliveryMethod == SmtpDeliveryMethod.Network)
{
    using (Smtp client = new Smtp())
    {
        client.Connect(section.Network.Host, section.Network.Port, 
            section.Network.EnableSsl);
        client.UseBestLogin(section.Network.UserName, section.Network.Password);
        client.SendMessage(email);
        client.Close();
    }
}
else if (section.DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory)
{
    string pickupFolder = section.SpecifiedPickupDirectory.PickupDirectoryLocation;
    email.Save(Path.Combine(pickupFolder, "email.eml"));
}
else if (section.DeliveryMethod == SmtpDeliveryMethod.PickupDirectoryFromIis)
{
    Assembly system = AppDomain.CurrentDomain.GetAssemblies()
        .First(x => x.GetName().Name == "System");
    
    Type iisPickupType = Type.GetType(
        "System.Net.Mail.IisPickupDirectory, " 
        + system.GetName().FullName, 
        true);
    // -or- use fully qualified system assembly name directly:
    //Type iisPickupType = Type.GetType(
    //    "System.Net.Mail.IisPickupDirectory, " 
    //    + "System, Version=4.0.0.0, Culture=neutral, "
    //    + "PublicKeyToken=b77a5c561934e089",
    //    true);

    string pickupFolder = (string)iisPickupType.InvokeMember(
        "GetPickupDirectory", 
        BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, 
        null, null, null);

    email.Save(Path.Combine(pickupFolder, "email.eml"));
}

The handshake failed due to an unexpected packet format

Most likely your server requires explicit SSL, sometimes also known as TLS.

It is called explicit SSL mode, because after the connection is established, client explicitly issues a command to the server that initiates SSL/TLS negotiation.

This is in contrast to implicit SSL mode, where SSL negotiation is initiated just after successful connection. In implicit mode server and client knows to use SSL, because client uses default protocol port, that is commonly used for secured traffic.

First try to connect to your server without SSL:

// C#

client.Connect("mail.example.com");
' VB.NET

client.Connect("mail.example.com")

Then, before logging-in, start explicit SSL negotiation. The command name differs for different protocols:

Explicit SSL (aka TLS)

The code is exactly the same no matter which protocol (IMAP, POP3 or SMTP) you use.

// C#

client.Connect("mail.example.com");
client.StartTLS();
' VB.NET

client.Connect("mail.example.com")
client.StartTLS()

StartTLS method negotiates security protocol with the server and secures the channel using SSL or TLS. Now, your connection is secured.

Here you can find more details on SSL vs TLS vs STARTTLS.

Please note, that your server may not need SSL/TLS at all. In such case simply use Connect method.

Enabled SSL Protocols

On very rare occasions “handshake failed…” error may indicate that TLS is incorrectly configured on the client machine or on the server.

It is possible to force SSL v3.0 usage instead of TLS in explicit mode:

// C#

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.Connect("mail.example.com");
client.StartTLS();
' VB.NET

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.Connect("mail.example.com");
client.StartTLS();

It is also possible to force SSL v3.0 usage instead of TLS in implicit mode:

// C#

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.ConnectSSL("mail.example.com");
' VB.NET

client.SSLConfiguration.EnabledSslProtocols = SslProtocols.Ssl3;
client.ConnectSSL("mail.example.com");

Self-signed certificates

Remember that you can ignore SSL certificate errors using ServerCertificateValidate event:

// C#

static void Validate(
    object sender,
    ServerCertificateValidateEventArgs e)
{
    const SslPolicyErrors ignoredErrors =
        SslPolicyErrors.RemoteCertificateChainErrors |
        SslPolicyErrors.RemoteCertificateNameMismatch;

    if ((e.SslPolicyErrors & ~ignoredErrors) == SslPolicyErrors.None)
    {
        e.IsValid = true;
        return;
    }
    e.IsValid = false;
}

client.ServerCertificateValidate += Validate;
client.Connect...
' VB.NET

Private Sub ValidateCerificate( _
    ByVal sender As Object, _
    ByVal e As ServerCertificateValidateEventArgs)

    Const ignoredErrors As SslPolicyErrors = _
        SslPolicyErrors.RemoteCertificateChainErrors Or _
        SslPolicyErrors.RemoteCertificateNameMismatch

    If (e.SslPolicyErrors And Not ignoredErrors) = SslPolicyErrors.None Then
        e.IsValid = True
        Return
    End If
    e.IsValid = False
End Sub

AddHandler client.ServerCertificateValidate, AddressOf Validate
client.Connect...