16

Is there any easy way to convert a System.Net.Mail.MailMessage object to the raw mail message text, like when you open a eml file in notepad.

Craig
  • 36,306
  • 34
  • 114
  • 197

4 Answers4

19

Here's the same solution, but as an extension method to MailMessage.

Some of the reflection overhead is minimized by grabbing the ConstructorInfo and MethodInfo members once in the static context.

/// <summary>
/// Uses reflection to get the raw content out of a MailMessage.
/// </summary>
public static class MailMessageExtensions
{
    private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
    private static readonly Type MailWriter = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
    private static readonly ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(Flags, null, new[] { typeof(Stream) }, null);
    private static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", Flags);
    private static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", Flags);

    /// <summary>
    /// A little hack to determine the number of parameters that we
    /// need to pass to the SaveMethod.
    /// </summary>
    private static readonly bool IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;

    /// <summary>
    /// The raw contents of this MailMessage as a MemoryStream.
    /// </summary>
    /// <param name="self">The caller.</param>
    /// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
    public static MemoryStream RawMessage(this MailMessage self)
    {
        var result = new MemoryStream();
        var mailWriter = MailWriterConstructor.Invoke(new object[] { result });
        SendMethod.Invoke(self, Flags, null, IsRunningInDotNetFourPointFive ? new[] { mailWriter, true, true } : new[] { mailWriter, true }, null);
        result = new MemoryStream(result.ToArray());
        CloseMethod.Invoke(mailWriter, Flags, null, new object[] { }, null);
        return result;
    }
}

To grab the underlying MemoryStream:

var email = new MailMessage();
using (var m = email.RawMessage()) {
    // do something with the raw message
}
hangy
  • 10,765
  • 6
  • 43
  • 63
allonhadaya
  • 1,297
  • 7
  • 19
  • 2
    Does this work (with the additional parameter for `Send`) in .NET 4.5 on your PC? I had to copy the content of `result` to another `MemoryStream` before calling `CloseMethod`, because the current implementation will actually close the underlying stream. – hangy Jan 15 '13 at 13:40
  • This works for .NET 4.0 -- I haven't tried it in 4.5. I'd be happy to include your update in my answer (and give you credit, of course). – allonhadaya Feb 07 '13 at 17:31
  • 1
    Yes, it should work like that, but I also added a check for .NET 4.5. I could update the answer with what works, if that's okay with you? – hangy Feb 11 '13 at 08:51
  • 1
    FYI, I noticed that the Send method actually strips out the BCC since it's prepping it for sending. If you are just going to pass this to Amazon SES or some other type of service, that isn't what you want. I ended up just manually creating the MIME string instead. – scojomodena Dec 13 '13 at 18:51
  • @allonhadaya, it seems like the answer does not return the [MIME encoded-word syntax](http://en.wikipedia.org/wiki/MIME#Encoded-Word) for non-ASCII characters? – Gan Jan 22 '14 at 08:05
  • @Gan, very curious... This just leverages the internal message making mechanism. Have you set all the necessary encoding properties on MailMessage? Search for encoding on this page to see them: http://msdn.microsoft.com/en-us/library/system.net.mail.mailmessage(v=vs.110).aspx – allonhadaya Jan 22 '14 at 17:36
  • @allonhadaya, yes I did set all the encoding related properties. After I call the RawMessage method, I look into the debug watch window, those non-ascii characters are still showing as they are, not MIME encoded. The only way I managed to see the MIME encoded message is by sending it to an SMTP server and view the .eml file manually. Can you try your solution with non-ascii characters (e.g. Chinese, Japanese)? – Gan Jan 23 '14 at 06:19
  • @Gan Give this a shot if you have the time. I'd be interested in how it turns out: http://stackoverflow.com/questions/16028757/subject-encoding-on-smtpclient-mailmessage – allonhadaya Jan 23 '14 at 21:42
9

I've implemented logic in MimeKit to allow you to cast a System.Net.Mail.MailMessage into a MimeKit.MimeMessage. Once you do that, you can simply write the message to a stream:

var message = (MimeMessage) CreateSystemNetMailMessage ();
using (var stream = File.Create ("C:\\message.eml"))
    message.WriteTo (stream);

This does not require reflecting into internal methods which means that it isn't dependent on the runtime, making it far more portable that the other answers given so far.

jstedfast
  • 35,744
  • 5
  • 97
  • 110
  • I noticed that the reflection method does not get my SubjectEncoding (UTF8) for sending via Amazon SES right. Mimekit does. Thanks! – Sebastian Brand Dec 10 '14 at 20:08
  • 1
    Awesome! Glad MimeKit has been helpful! – jstedfast Dec 12 '14 at 04:34
  • Looks like you're [just mapping by hand](https://github.com/jstedfast/MimeKit/blob/1ea79dddc1a1dd96890e2f3fe2395232f8165a4c/MimeKit/MimeMessage.cs#L1951), so to speak, right? Not that that's horribly bad or a deal breaker, but it's theoretically lossy if I'm not missing something. (??) (Trying to adapt my code to MonoMac now, and was about to give up & do a mapping-by-hand myself.) – ruffin Jan 17 '15 at 23:24
  • 1
    Yes, it's just mapping by hand but it should be complete. Obviously mapping is not ideal as a long-term solution and so I would recommend using MimeKit to construct the message as the long-term solution, but the mapping method is useful if you are incrementally porting your S.N.Mail code in the short-term... if that makes sense. – jstedfast Jan 18 '15 at 04:48
  • 1
    Oh, and if you need SMTP, I have a library called [MailKit](https://github.com/jstedfast/MailKit) which adds SMTP, POP3 and IMAP support on top of MimeKit. – jstedfast Jan 18 '15 at 04:52
2

The code I've seen to do this relies on reflection. I adapted the samples found online to create this method:

    private static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
    {
        BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
        Assembly assembly = typeof(SmtpClient).Assembly;
        MemoryStream stream = new MemoryStream();
        Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
        ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(flags, null, new[] { typeof(Stream) }, null);
        object mailWriter = mailWriterContructor.Invoke(new object[] { stream });
        MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", flags);
        sendMethod.Invoke(message, flags, null, new[] { mailWriter, true }, null);
        MethodInfo closeMethod = mailWriter.GetType().GetMethod("Close", flags);                
        closeMethod.Invoke(mailWriter, flags, null, new object[] { }, null);
        return stream;
    }

You can then convert the MemoryStream to a string or whatever you need.

Update: A method signature has changed in .NET 4.5, which breaks the above: Getting System.Net.Mail.MailMessage as a MemoryStream in .NET 4.5 beta

Community
  • 1
  • 1
Cymen
  • 14,079
  • 4
  • 52
  • 72
-1
byte[] allBytes = new byte[attachment.ContentStream.Length];
int bytesRead = attachment.ContentStream.Read(allBytes, 0, (int)attachment.ContentStream.Length);
Encoding encoding = Encoding.UTF8;
String contenidoCorreo = encoding.GetString(allBytes);
derloopkat
  • 6,232
  • 16
  • 38
  • 45