How to save MailMessage object to disk as *.eml or *.msg file

93,151

Solution 1

For simplicity, I'll just quote an explanation from a Connect item:

You can actually configure the SmtpClient to send emails to the file system instead of the network. You can do this programmatically using the following code:

SmtpClient client = new SmtpClient("mysmtphost");
client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
client.PickupDirectoryLocation = @"C:\somedirectory";
client.Send(message);

You can also set this up in your application configuration file like this:

 <configuration>
     <system.net>
         <mailSettings>
             <smtp deliveryMethod="SpecifiedPickupDirectory">
                 <specifiedPickupDirectory pickupDirectoryLocation="C:\somedirectory" />
             </smtp>
         </mailSettings>
     </system.net>
 </configuration>

After sending the email, you should see email files get added to the directory you specified. You can then have a separate process send out the email messages in batch mode.

You should be able to use the empty constructor instead of the one listed, as it won't be sending it anyway.

Solution 2

Here's an extension method to convert a MailMessage to a stream containing the EML data. Its obviously a bit of a hack as it uses the file system, but it works.

public static void SaveMailMessage(this MailMessage msg, string filePath)
{
    using (var fs = new FileStream(filePath, FileMode.Create))
    {
        msg.ToEMLStream(fs);
    }
}

/// <summary>
/// Converts a MailMessage to an EML file stream.
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public static void ToEMLStream(this MailMessage msg, Stream str)
{
    using (var client = new SmtpClient())
    {
        var id = Guid.NewGuid();

        var tempFolder = Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name);

        tempFolder = Path.Combine(tempFolder, "MailMessageToEMLTemp");

        // create a temp folder to hold just this .eml file so that we can find it easily.
        tempFolder = Path.Combine(tempFolder, id.ToString());

        if (!Directory.Exists(tempFolder))
        {
            Directory.CreateDirectory(tempFolder);
        }

        client.UseDefaultCredentials = true;
        client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
        client.PickupDirectoryLocation = tempFolder;
        client.Send(msg);

        // tempFolder should contain 1 eml file

        var filePath = Directory.GetFiles(tempFolder).Single();

        // stream out the contents
        using (var fs = new FileStream(filePath, FileMode.Open))
        {
            fs.CopyTo(str);
        }

        if (Directory.Exists(tempFolder))
        {
            Directory.Delete(tempFolder, true);
        }
    }
}

You can then take the stream thats returned and do as you want with it, including saving to another location on disk or storing in a database field, or even emailing as an attachment.

Solution 3

If you are using Mailkit. Just write below code

string fileName = "your filename full path";
MimeKit.MimeMessage message = CreateMyMessage ();
message.WriteTo(fileName);

Solution 4

With the help of community I came up with an solution for .NET 5. I have combined this old solution with suggestions in this post and got inspired by Mailkit which resulted in nice extension method without unnecessary dependencies

public static class MailMessageHelper
{
    public static void WriteTo(this MailMessage mail, Stream stream)
    {
        Assembly assembly = typeof(SmtpClient).Assembly;
        Type _mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");

        // Get reflection info for MailWriter contructor
        ConstructorInfo _mailWriterConstructor =
            _mailWriterType.GetConstructor(
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new Type[] { typeof(Stream), typeof(bool) },
                null);

        // Construct MailWriter object with our FileStream
        object _mailWriter =
          _mailWriterConstructor.Invoke(new object[] { stream, true });

        // Get reflection info for Send() method on MailMessage
        MethodInfo _sendMethod =
            typeof(MailMessage).GetMethod(
                "Send",
                BindingFlags.Instance | BindingFlags.NonPublic);

        // Call method passing in MailWriter
        _sendMethod.Invoke(
            mail,
            BindingFlags.Instance | BindingFlags.NonPublic,
            null,
            new object[] { _mailWriter, true, true },
            null);

        // Finally get reflection info for Close() method on our MailWriter
        MethodInfo _closeMethod =
            _mailWriter.GetType().GetMethod(
                "Close",
                BindingFlags.Instance | BindingFlags.NonPublic);

        // Call close method
        _closeMethod.Invoke(
            _mailWriter,
            BindingFlags.Instance | BindingFlags.NonPublic,
            null,
            Array.Empty<object>(),
            null);
    }
}

Usage

MailMessage mail = new(mailFrom, mailTo, mailSubject, mailContent);
mail.WriteTo(new FileStream(@"path_to_file\new_mail.eml", FileMode.Create));

Also if you are using MemoryStream and want to get result in string, just change the return type of the extension method and at the end write

return Encoding.ASCII.GetString(stream.ToArray());

Enjoy

Share:
93,151
turnip_cyberveggie
Author by

turnip_cyberveggie

Polyglot programmer from Bangalore, India

Updated on January 05, 2022

Comments

  • turnip_cyberveggie
    turnip_cyberveggie over 2 years

    How do I save MailMessage object to the disk? The MailMessage object does not expose any Save() methods.

    I dont have a problem if it saves in any format, *.eml or *.msg. Any idea how to do this?

  • Steven Rogers
    Steven Rogers almost 13 years
    I've found that I also needed to add the <network host="...", etc. in addition to what Ryan provided.
  • buzzzzjay
    buzzzzjay over 12 years
    Is there any way to change the file name of the output .eml file? I would prefer it not to look like the following: f80f4695-551c-47d7-8879-40ad89707b23.eml Thanks!
  • Corné Hogerheijde
    Corné Hogerheijde over 11 years
    Although an old post, I would like to add an answer to the last question from @buzzzzjay: have a look here: link
  • buzzzzjay
    buzzzzjay over 11 years
    Thanks for the link, that is really helpful!
  • Rahul Jain
    Rahul Jain over 9 years
    Hi Saille... Your code works well and yes it is creating the eml or Msg file but I can't open that in MS outlook :( Need your help in doing that.
  • Rama
    Rama over 9 years
    .EML file should open in Outlook, but if you cannot, try renaming the file extension to .MHT, then open it in Internet Explorer.
  • Andy Jones
    Andy Jones over 8 years
    Saille - is it possible to save without a "from" address, so it can be sent from the user that opens it? Cheers.
  • Rama
    Rama over 8 years
    You'd have to try it. I imagine your mail client would want to set the reply-to address according to its settings.
  • Builder
    Builder about 7 years
    good, it works for me. Further how can I open Outlook to open just saved file on client side.
  • Rama
    Rama over 6 years
    @qazifarhan refer to my answer if you need access to the EML file in your code.
  • Mike
    Mike almost 6 years
    I there anyway to set the file names that get saved?
  • Hille
    Hille over 4 years
    Welcome to StackOverflow! As the question is with the tag c#, answers in other languages are not helpful, sorry.
  • Rubarb
    Rubarb over 2 years
    Don't forget to message.Headers.Add("X-Unsent", "1"); this way you'll have the send option when it is opened.
  • David Hirst
    David Hirst over 2 years
    I can confirm that this works great in .net 5, thanks!