How to save MailMessage object to disk as *.eml or *.msg file
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
Comments
-
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 almost 13 yearsI've found that I also needed to add the <network host="...", etc. in addition to what Ryan provided.
-
buzzzzjay over 12 yearsIs 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 over 11 yearsAlthough an old post, I would like to add an answer to the last question from @buzzzzjay: have a look here: link
-
buzzzzjay over 11 yearsThanks for the link, that is really helpful!
-
Rahul Jain over 9 yearsHi 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 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 over 8 yearsSaille - is it possible to save without a "from" address, so it can be sent from the user that opens it? Cheers.
-
Rama over 8 yearsYou'd have to try it. I imagine your mail client would want to set the reply-to address according to its settings.
-
Builder about 7 yearsgood, it works for me. Further how can I open Outlook to open just saved file on client side.
-
Rama over 6 years@qazifarhan refer to my answer if you need access to the EML file in your code.
-
Mike almost 6 yearsI there anyway to set the file names that get saved?
-
Hille over 4 yearsWelcome to StackOverflow! As the question is with the tag c#, answers in other languages are not helpful, sorry.
-
Rubarb over 2 yearsDon't forget to message.Headers.Add("X-Unsent", "1"); this way you'll have the send option when it is opened.
-
David Hirst over 2 yearsI can confirm that this works great in .net 5, thanks!