Outlook.com IMAP looses attachment contents on COPY

Outlook.com IMAP looses attachment contents on COPY operation.

Coping an email with an attachment on Outlook.com using IMAP works correct for small attachments:

C: 7554e85569d0465d UID COPY 100828 "Destination"
S: 7554e85569d0465d OK [COPYUID 43582022 100828 100003] COPY completed

With large attachments (~3MB), although COPY command succeeds:

C: 7734b17263a84259 UID COPY 100823 "Destination"
S: 7734b17263a84259 OK [COPYUID 43582022 100823 100001] COPY completed

All contents of the attachments are removed by the server:

------_=_NextPart_001_01CF7412.6651E594
Content-Type: image/jpeg; name="100557_1_DSC_0041 (2).jpg"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="100557_1_DSC_0041 (2).jpg"
MS-Blob-Excluded: messageId=7C7BA5A2-E006-11E3-94A8-2C59E5464128; blobId=0; encodedLength=2925184; resolveError=true; blobHash=F206EC6BB4B95BF30B1382C38A33C9BEC3DBFB4A

------_=_NextPart_001_01CF7412.6651E594--

After COPY operation email message is modified by the server:
attachment contents are removed (attachment must be large enough ~3MB)
– new MIME header: MS-Blob-Excluded is added to the MIME entity representing the attachment.

All IMAP clients are affected.

It seems that this behavior is coded-in on purpose, however it’s a bug and violation of the IMAP protocol.

It would be much wiser to fail on COPY command with appropriate error message, if for some reason (performance?) the operation would result in loosing data.

iCloud: IMAP and SMTP settings

iCloud supports access via IMAP and SMTP protocols, POP3 is not supported. Below you can find the configuration settings for those protocols.

Both (IMAP and SMTP) use implicit SSL (use ConnectSSL method) and explicit SSL (you can use Connect method and then secure the channel using StartTLS method)

IMAP

Server: imap.mail.me.com
SSL: true-implicit
Port: 993 (default)
User: pat (not pat@icloud.com)

SMTP

Server: smtp.mail.me.com
SSL: true-explicit
Port: 587 (default)
User: pat@icloud.com (not pat)

Following are the code samples for Mail.dll .NET IMAP, POP3 and SMTP component.

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.mail.me.com");
    client.UseBestLogin("pat", "password");
    ...
}

using (Smtp client = new Smtp ())
{
    client.Connect("smtp.mail.me.com");
    client.StartTLS();
    client.UseBestLogin("pat@icloud.com", "password");
    ...
}
' VB.NET

Using client As New Imap()
	client.ConnectSSL("imap.mail.me.com")
	client.UseBestLogin("pat", "password")
	...
End Using

Using client As New Smtp()
	client.Connect("smtp.mail.me.com")
	client.StartTLS()
	client.UseBestLogin("pat@icloud.com", "password")
	...
End Using

Access shared/delegate mailbox of Exchange Server

1. Enable IMAP and POP3 protocols

Make sure you have enabled IMAP and POP3. Following articles can help you with that:

2. Turn on basic authentication

Using Exchange Management Console

  • Open EMC, expand to Server Configuration->Client Access.
  • In the middle panel, click your exchange CAS server, click POP3 and IMAP4 tab, right click IMAP4 and choose properties.
  • In Authentication tab, select “Plain text logon (Basic authentication)”, then click OK.
  • Open services.msc, restart Microsoft Exchange Transport services.

Using Power Shell

Set-IMAPSettings -Server -LoginType PlainTextLogin
Set-POPSettings -Server -LoginType PlainTextLogin

Open services.msc, restart Microsoft Exchange Transport services.

3. Add permissions to the shared mailbox

Give one user full access permission to the shared mailbox:

Add-MailboxPermission Shared.Mailbox1 -user John.Doe -AccessRights FullAccess

This also can be configured from EMC (Exchange Management Console) gui, by selecting the mailbox and clicking on “Manage Full Access Permission…” at the right pane of the window.

Note: You can not add permissions for user without mailbox, Powershell is the only option in such case.

4. Access the shared mailbox

Exchange 2003

Use the following user format DomainName\Username\SharedMailboxAlias (note the use of SharedMailboxAlias it’s often same as the name but it may be different)
(e.g. DOMAIN\John.Doe\Shared.Mailbox1) to log into the shared mailbox.

Exchange 2007

Install this patch: http://support.microsoft.com/?kbid=949926

Use the following user format DomainName\Username\SharedMailboxAlias (note the use of SharedMailboxAlias it’s often same as the name but it may be different)
(e.g. DOMAIN\John.Doe\Shared.Mailbox1) to log into the shared mailbox.

Exchange 2010, Exchange 2013, Exchange 2016, Exchange 2019

Use the following user format DomainName\Username\SharedMailboxAlias (note the use of SharedMailboxAlias it’s often same as the name but it may be different)
(e.g. DOMAIN\John.Doe\Shared.Mailbox1) to log into the shared mailbox.

Office 365/Exchange Online

You can find more details here: https://www.limilabs.com/blog/shared-mailbox-office365

Use the following user format Username@DomainName\Shared@DomainName

You must use Login method:

client.Login(@"Username@DomainName\Shared@DomainName", "UserPassword"); 

-or- LoginPlain method:

client.LoginPlain("Shared@DomainName", "User@DomainName", UserPassword");

Don’t use UseBestLogin for Office365 shared mailboxes.

Using LoginPLAIN to login as a different user

You can try 3rd parameter of LoginPLAIN to log in as a different user:

using (Imap imap = new Imap) 
{ 
    imap.ConnectSSL("outlook.office365.com"); 
    imap.LoginPLAIN("SharedMailboxAlias", "Username@DomainName", "UserPassword"); 
    //... 
    imap.Close(); 
}

EnvelopedCms decrypt problem

.NET’s EnvelopedCms class represents a CMS/PKCS #7 structure for enveloped data. Such data are for example used in S/MIME encrypted messages.

EnvelopedCms class contains several Decrypt method overloads:

  • Decrypt()
  • Decrypt(RecipientInfo)
  • Decrypt(X509Certificate2Collection)
  • Decrypt(RecipientInfo, X509Certificate2Collection)

Each Decrypt method decrypts the contents of the decoded enveloped CMS/PKCS #7 data. Each searches the current user and local machine My stores for the appropriate certificate and private key.

Last two overloads allow passing additional collection of X509 certificates, that should be searched to find matching certificate for decryption.

There are 2 most common errors that can occur while decrypting:

  • “The enveloped-data message does not contain the specified recipient.” – Which means that no matching certificate was found in My stores and in the additional certificate collection.
  • “Cannot find object or property.” – Certificate was found, but there is no private key in it, so it can not be used to decryped the enveloped data.

There are 3 ways of adding certificates to My store, 2 of those don’t import private key:

  • “Personal” tab in CertMgr (does not import private key).
  • MMC “Certificates” snap-in (“My user account”) (does not import private key).
  • Double clicking the pfx file (imports private key).

In many cases data are encrypted using 2 or more certificates (so both sender and receiver are able to decrypt the message)

The problem with all Decrypt methods is that they try to use only the first certificate they find. The matching is based on certificate SN. First, both My stores are search, then extra store (passed certificate collection).

This leads to a problem when, both certificates are in My store, but only one contains a private key.

.NET is going to use the first certificate it finds – if it’s the one without the private key, “Cannot find object or property” exception is going to be thrown.

The only workaround for this problem is to try to decrypt the message for each ReceipientInfo separately. This way we are sure that all certificates will be tried.

Mail.dll .NET secure email component does this automatically, so no additional code is required.

Upload email to Sent folder after sending

Some SMTP servers automatically put sent messages in the sent folder, but this is not a SMTP protocol requirement and many servers don’t do that.

In such case, you’ll need to manually upload message to the sent folder using IMAP. Unfortunately there is no standard that would allow checking, if SMTP server puts send messages in Sent folder.

Create email message

First we’ll create new email message:

// C#

MailBuilder builder = new MailBuilder();
builder.Subject = @"Subject";
builder.Html = @"Html with an image: <img src=""cid:lena"" />";
builder.From.Add(new MailBox("alice@mail.com", "Alice"));
builder.To.Add(new MailBox("bob@mail.com", "Bob"));

MimeData visual = builder.AddVisual(@"c:\lena.jpeg");
visual.ContentId = "lena";

MimeData attachment = builder.AddAttachment(@"c:\tmp.doc");
attachment.FileName = "document.doc";

IMail email = builder.Create();
' VB.NET

Dim builder As MailBuilder = New MailBuilder()
builder.Subject = "Subject"
builder.Html = "Html with an image: <img src=""cid:lena"" />"
builder.From.Add(New MailBox("alice@mail.com", "Alice"))
builder.[To].Add(New MailBox("bob@mail.com", "Bob"))

Dim visual As MimeData = builder.AddVisual("c:\lena.jpeg")
visual.ContentId = "lena"

Dim attachment As MimeData = builder.AddAttachment("c:\tmp.doc")
attachment.FileName = "document.doc"

Dim email As IMail = builder.Create()

Alternatively you can use fluent interface to create an email:

// C# 

IMail email = Limilabs.Mail.Fluent.Mail
    .Html(@"Html with an image: <img src=""cid:lena"" />")
    .AddVisual(@"c:\lena.jpeg").SetContentId("lena")
    .AddAttachment(@"c:\tmp.doc").SetFileName("document.doc")
    .To("to@example.com")
    .From("from@example.com")
    .Subject("Subject")
    .Create();
' VB.NET

Dim email As IMail = Mail _
    .Html("Html with an image: <img src=""cid:lena"" />") _
    .AddVisual("C:\lena.jpeg").SetContentId("lena") _
    .AddAttachment("C:\tmp.doc").SetFileName("document.doc") _
    .To("to@example.com") _
    .From("from@example.com") _
    .Subject("Subject") _
    .Create()

Send email message

Then we’ll send it using SMTP protocol:

// C#

ISendMessageResult result;
using (Smtp smtp = new Smtp())
{
    smtp.Connect("smtp.server.com");     // or ConnectSSL for SSL
    smtp.UseBestLogin("user", "password");

    result = smtp.SendMessage(email);

    smtp.Close();
}
' VB.NET

Dim result As ISendMessageResult
Using smtp As New Smtp()
    smtp.Connect("smtp.server.com")     ' or ConnectSSL for SSL
    smtp.UseBestLogin("user", "password")

    result = smtp.SendMessage(email)

    smtp.Close()
End Using

Upload email message

Finally we’ll connect to IMAP server, get Sent folder and upload message to it:

// C#

if (result.Status == SendMessageStatus.Success)
{
    using (Imap imap = new Imap())
    {
        imap.Connect("imap.server.com");     // or ConnectSSL for SSL
        imap.UseBestLogin("user", "password");

        FolderInfo sent = new CommonFolders(imap.GetFolders()).Sent;
        imap.UploadMessage(sent, email);

        imap.Close();
    }
}
' VB.NET

If result.Status = SendMessageStatus.Success Then
    Using imap As New Imap()
        imap.Connect("imap.server.com")		' or ConnectSSL for SSL
        imap.UseBestLogin("user", "password")

        Dim sent As FolderInfo = New CommonFolders(imap.GetFolders()).Sent
        imap.UploadMessage(sent, email)

        imap.Close()
   End Using
End If

Entire code

Here’s the full code:

// C#

MailBuilder builder = new MailBuilder();
builder.Subject = @"Subject";
builder.Html = @"Html with an image: <img src=""cid:lena"" />";
builder.From.Add(new MailBox("alice@mail.com", "Alice"));
builder.To.Add(new MailBox("bob@mail.com", "Bob"));

MimeData visual = builder.AddVisual(@"c:\lena.jpeg");
visual.ContentId = "lena";

MimeData attachment = builder.AddAttachment(@"c:\tmp.doc");
attachment.FileName = "document.doc";

IMail email = builder.Create();

ISendMessageResult result;
using (Smtp smtp = new Smtp())
{
    smtp.Connect("smtp.server.com");    // or ConnectSSL for SSL
    smtp.UseBestLogin("user", "password");

    result = smtp.SendMessage(email);

    smtp.Close();
}

if (result.Status == SendMessageStatus.Success)
{
    using (Imap imap = new Imap())
    {
        imap.Connect("imap.server.com");     // or ConnectSSL for SSL
        imap.UseBestLogin("user", "password");

        FolderInfo sent = new CommonFolders(imap.GetFolders()).Sent;
        imap.UploadMessage(sent, email);

        imap.Close();
    }
}

' VB.NET

Dim builder As MailBuilder = New MailBuilder()
builder.Subject = "Subject"
builder.Html = "Html with an image: <img src=""cid:lena"" />"
builder.From.Add(New MailBox("alice@mail.com", "Alice"))
builder.[To].Add(New MailBox("bob@mail.com", "Bob"))

Dim visual As MimeData = builder.AddVisual("c:\lena.jpeg")
visual.ContentId = "lena"

Dim attachment As MimeData = builder.AddAttachment("c:\tmp.doc")
attachment.FileName = "document.doc"

Dim email As IMail = builder.Create()

Dim result As ISendMessageResult
Using smtp As New Smtp()
    smtp.Connect("smtp.server.com")	' or ConnectSSL for SSL
    smtp.UseBestLogin("user", "password")

    result = smtp.SendMessage(email)

    smtp.Close()
End Using

If result.Status = SendMessageStatus.Success Then
    Using imap As New Imap()
        imap.Connect("imap.server.com")		' or ConnectSSL for SSL
        imap.UseBestLogin("user", "password")

        Dim sent As FolderInfo = New CommonFolders(imap.GetFolders()).Sent
        imap.UploadMessage(sent, email)

    imap.Close()
End Using
End If