6
votes

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;
        }
3
Thanks, but this link is based on Mail.dll and I'd like to stick with MailKit ideally.FrugalTPH
FWIW, you don't need to create a cancellation token unless you plan to be able to cancel saving the attachment to disk. You can either use CancellationToken.None or just not pass a cancellation token at all.jstedfast

3 Answers

10
votes

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));
}
7
votes

Starting with MimeKit 0.38.0.0, you'll be able to use a MimeIterator to traverse the MIME tree structure to collect a list of attachments that you'd like to remove (and remove them). To do this, your code would look something like this:

var attachments = new List<MimePart> ();
var multiparts = new List<Multipart> ();
var iter = new MimeIterator (message);

// collect our list of attachments and their parent multiparts
while (iter.MoveNext ()) {
    var multipart = iter.Parent as Multipart;
    var part = iter.Current as MimePart;

    if (multipart != null && part != null && part.IsAttachment) {
        // keep track of each attachment's parent multipart
        multiparts.Add (multipart);
        attachments.Add (part);
    }
}

// now remove each attachment from its parent multipart...
for (int i = 0; i < attachments.Count; i++)
    multiparts[i].Remove (attachments[i]);
0
votes

I created an application, that downloads emails and attachments as well using Mailkit. I faced one problem: E-Mails sent from iOS with attached pictures were not processed correctly. MailKit did not add the images to the Attachments list.

I used this method to get only the text of the message:

private static string GetPlainTextFromMessageBody(MimeMessage message)
    {
        //content type needs to match text/plain otherwise i would store html into DB
        var mimeParts = message.BodyParts.Where(bp => bp.IsAttachment == false && bp.ContentType.Matches("text", "plain"));
        foreach (var mimePart in mimeParts)
        {
            if (mimePart.GetType() == typeof(TextPart))
            {
                var textPart = (TextPart)mimePart;
                return textPart.Text;
            }
        }
        return String.Empty;
    }

This is the method I used to download only the .jpg files:

foreach (var attachment in message.BodyParts.Where(bp => !string.IsNullOrEmpty(bp.FileName)))
{
    if (attachment.FileName.ToLowerInvariant().EndsWith(".jpg"))
    {
        //do something with the image here
    }
}