Recursively Decoding Nested Messages with the MIME Component
Email messages are often made up of several levels of nested MIME parts. For example, a message with an attachment is typically a multipart/mixed entity, whose first part is itself a multipart/alternative entity containing plain text and HTML versions of the body. A forwarded message may also be attached as a message/rfc822 part, which contains a complete email message of its own.
The MIME component's decode methods (such as DecodeFromString) split a message into its top-level parts only, and populate the Parts collection with them. To process every part of a message, including the parts inside nested entities, each nested entity must be decoded in turn.
Recursive Decoding Example
The helper method below decodes a MIME entity and processes each of its parts. When a part is itself a multipart entity or an attached message, the method calls itself to decode that part's contents. Plain text parts are printed, and attachments are saved to a folder.
void ProcessEntity(byte[] entity, string headers, string attachmentFolder) {
MIME mime = new MIME();
// When headers is empty, the headers are read from the beginning of the entity itself.
mime.MessageHeadersString = headers;
mime.MessageB = entity;
mime.DecodeFromString();
for (int i = 0; i < mime.Parts.Count; i++) {
MIMEPart part = mime.Parts[i];
string contentType = part.ContentType.ToLower();
if (contentType.StartsWith("multipart/")) {
// The part is a nested multipart entity. Its headers (including its boundary) are in the Headers field.
ProcessEntity(mime.DecodePart(i), part.Headers, attachmentFolder);
} else if (contentType == "message/rfc822") {
// The part is an attached message, which includes its own headers.
ProcessEntity(mime.DecodePart(i), "", attachmentFolder);
} else if (part.FileName != "") {
// The part has a file name, so it is an attachment. Decode it to a temporary file, and then move it.
string tempFile = mime.DecodePartToFile(i);
string target = Path.Combine(attachmentFolder, Path.GetFileName(part.FileName));
File.Move(tempFile, target, true);
Console.WriteLine("Saved attachment: " + target);
} else if (contentType == "text/plain") {
Console.WriteLine("Plain text: " + part.DecodedString);
}
}
}
To decode a complete email message, such as a message saved to a .eml file, pass its contents to the helper with an empty headers string.
byte[] message = File.ReadAllBytes("C:\\mail\\message1.eml");
ProcessEntity(message, "", "C:\\mail\\attachments");
A message retrieved with the POP or IMAP component can be decoded in the same way. The simplest approach is to save the complete message (including its headers) to a file, as described in our Export Email Contents to a Local File entry, and then pass the file's contents to the helper. Working with the bytes of the file, rather than a string, ensures that binary content is not altered by character set conversions.
Notes
- The text of a part is available in its DecodedString field, and the raw bytes are returned by the DecodePart method. For parts that use a character set other than the default, the ContentTypeAttr field contains the charset attribute, which can be used to decode the bytes returned by DecodePart correctly.
- Some attachments are identified by a Content-Disposition of attachment rather than by a file name. The ContentDisposition field of each part can be checked for these cases.
- DecodePartToFile decodes the part to a temporary file and returns its path, so the application is responsible for moving or deleting the file. The TempFilePath configuration setting controls where temporary files are created.
- Attachment file names come from the message, and may contain characters that are not valid in file names. The helper uses Path.GetFileName to discard any directory information, and the SanitizeFileName configuration setting can be enabled to replace other invalid characters.
- This approach can be extended to handle additional content types, such as text/html parts or inline images (identified by the ContentId field).
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.