Strip attachments from emails using MailKit / MimeKit

FrugalTPH picture FrugalTPH · Jun 11, 2014 · Viewed 12.8k times · Source

I'm using MailKit library to handle emails, which has been working well. However, I'm trying to split emails into their constituent files a) Main email (no attachments) b) Individual attachment files, to store on the filesystem.

I can save the attachments individually, but can't seem to remove them from the email body code. I.e. they're getting saved along with the main email, so duplicating data. :/

I've tried:

foreach (MimePart part in inMessage.BodyParts)
{ 
    if (part.IsAttachment)
    {
        // Remove MimePart    < This function isn't available on the collection.
    }
}

Have also tried:

var builder = new BodyBuilder();
foreach (MimePart part in inMessage.BodyParts)
{ 
    if (!part.IsAttachment)
    {
        // Add MimeParts to collection    < This function isn't available on the collection.
    }
}
outMessage.Body = builder.ToMessageBody();

If anyone can help with this, I'd much appreciate it.

Solution implemented FYI:

private string GetMimeMessageOnly(string outDirPath)
        {
            MimeMessage message = (Master as fsEmail).GetMimeMessage();

            if (message.Attachments.Any())
            {
                var multipart = message.Body as Multipart;
                if (multipart != null)
                {
                    while (message.Attachments.Count() > 0)
                    {
                        multipart.Remove(message.Attachments.ElementAt(0));
                    }
                }
                message.Body = multipart;
            }

            string filePath = outDirPath + Guid.NewGuid().ToString() + ".eml";
            Directory.CreateDirectory(Path.GetDirectoryName(outDirPath));
            using (var cancel = new System.Threading.CancellationTokenSource())
            {    
                using (var stream = File.Create(filePath)) 
                {
                    message.WriteTo(stream, cancel.Token);
                }
            }
            return filePath;
        }

And to get the attachments only:

private List<string> GetAttachments(string outDirPath)
        {
            MimeMessage message = (Master as fsEmail).GetMimeMessage();

            List<string> list = new List<string>();
            foreach (MimePart attachment in message.Attachments)
            {
                using (var cancel = new System.Threading.CancellationTokenSource())
                {
                    string filePath = outDirPath + Guid.NewGuid().ToString() + Path.GetExtension(attachment.FileName);
                    using (var stream = File.Create(filePath))
                    {
                        attachment.ContentObject.DecodeTo(stream, cancel.Token);
                        list.Add(filePath);
                    }
                }
            }
            return list;
        }

Answer

Andreas picture Andreas · Jun 11, 2014

You could retrieve all MimeParts that are attachments https://github.com/jstedfast/MimeKit/blob/master/MimeKit/MimeMessage.cs#L734 and then iterate over the all Multiparts and call https://github.com/jstedfast/MimeKit/blob/master/MimeKit/Multipart.cs#L468 for the attachments to remove.

The sample below makes a few assumptions about the mail e.g. there is only one Multipart some email client (Outlook) are very creative how mails are crafted.

static void Main(string[] args)
{
    var mimeMessage = MimeMessage.Load(@"x:\sample.eml");
    var attachments = mimeMessage.Attachments.ToList();
    if (attachments.Any())
    {
        // Only multipart mails can have attachments
        var multipart = mimeMessage.Body as Multipart;
        if (multipart != null)
        {
            foreach(var attachment in attachments)
            {
                multipart.Remove(attachment);
            }
        }
        mimeMessage.Body = multipart;
    }
    mimeMessage.WriteTo(new FileStream(@"x:\stripped.eml", FileMode.CreateNew));
}