MailKit: How to download all attachments locally from a MimeMessage
Asked Answered
R

1

5

I have looked at other examples online, but I am unable to figure out how to download and store ALL the attachments from a MimeMessage object. I did look into the WriteTo(), but I could not get it to work. Also wondering whether attachments will be saved according to the original file name, and type inside the email. Here is what I have so far:

using (var client = new ImapClient())
{
    client.Connect(Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
    client.AuthenticationMechanisms.Remove(Constant.GoogleOAuth);
    client.Authenticate(Constant.GoogleUserName, Constant.GenericPassword);

    if (client.IsConnected == true)
    {
        FolderAccess inboxAccess = client.Inbox.Open(FolderAccess.ReadWrite);
        IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder);
        IList<UniqueId> uids = client.Inbox.Search(SearchQuery.All);

        if (inboxFolder != null & inboxFolder.Unread > 0)
        {
            foreach (UniqueId msgId in uids)
            {
                MimeMessage message = inboxFolder.GetMessage(msgId);

                foreach (MimeEntity attachment in message.Attachments)
                {
                    //need to save all the attachments locally
                }
            }
        }
    }
}
Robbierobbin answered 10/4, 2017 at 19:0 Comment(1)
You'll need to do some sanitization to file names. Check for dupes, remove bad characters...Veneaux
I
24

This is all explained in the FAQ in the "How do I save attachments?" section.

Here is a fixed version of the code you posted in your question:

using (var client = new ImapClient ()) {
    client.Connect (Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
    client.AuthenticationMechanisms.Remove (Constant.GoogleOAuth);
    client.Authenticate (Constant.GoogleUserName, Constant.GenericPassword);

    client.Inbox.Open (FolderAccess.ReadWrite);
    IList<UniqueId> uids = client.Inbox.Search (SearchQuery.All);

    foreach (UniqueId uid in uids) {
        MimeMessage message = client.Inbox.GetMessage (uid);

        foreach (MimeEntity attachment in message.Attachments) {
            var fileName = attachment.ContentDisposition?.FileName ?? attachment.ContentType.Name;

            using (var stream = File.Create (fileName)) {
                if (attachment is MessagePart) {
                    var rfc822 = (MessagePart) attachment;

                    rfc822.Message.WriteTo (stream);
                } else {
                    var part = (MimePart) attachment;

                    part.Content.DecodeTo (stream);
                }
            }
        }
    }
}

A few notes:

  1. There's no need to check if client.IsConnected after authenticating. If it wasn't connected, it would have thrown an exception in the Authenticate() method. It would have thrown an exception in the Connect() method as well if it didn't succeed. There is no need to check the IsConnected state if you literally just called Connect() 2 lines up.
  2. Why are you checking inboxFolder.Unread if you don't even use it anywhere? If you just want to download unread messages, change your search to be SearchQuery.NotSeen which will give you only the message UIDs that have not been read.
  3. I removed your IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder); logic because you don't need it. If you are going to do the SEARCH using client.Inbox, then don't iterate over the results with a different folder object.
Instep answered 10/4, 2017 at 23:14 Comment(6)
This will not work for TnefPart (winmail.dat) . ref: github.com/jstedfast/MimeKit/issues/234Contradictory
Sure it will. The above code will save the winmail.dat attachment just fine. That bug report has nothing to do with saving the winmail.dat attachment.Instep
Just pointing out to other readers that want to process email attachments that they should also handle the winmail.dat tnef structure. If you are unaware of this you might discart winmail.dat while important files might reside in it. Thx for the great library btw!Contradictory
Just wanted to comment and say thank you, still thank you all this time on. Your libraries and hard work is awesome! I wish i could give you more upvotes and thanks!Inclinometer
I figured this is the best place to ask for this - if we have an IMessageSummary fetched only with BodyStructure instead of a MimeMessage, will the Attachments property contain a TnefPart and will you be able to call ExtractAttachments() on it? Asking because of cases where we have a huge mail message which we'd not want to download, but rather only get the TNEF part of it and extract it. (along with the rest of the non-TNEF attachments, which your answer fully covers)Mitchellmitchem
The IMessageSummary.Body/Attachments/etc properties use a different data structure than MimeMessage does, so it won't contain a TnefPart object. Instead, it will contain a BodyPartBasic with the application/vnd.ms-tnef mime-type. But yes, onmce you find that BodyPartBasic, you can fetch just that part from the IMAP server using the ImapFolder.GetBodyPart() method.Instep

© 2022 - 2024 — McMap. All rights reserved.