Generating draft EML files from ASP.NET MVC

Introduction

I was working on a custom ASP.NET MVC application used in financial services industry. Our client wanted to automate the process of writing emails. Instead of creating a new email in Outlook and then populating email addresses, subject field, attaching PDF reports and copy / pasting bunch of stuff from our application, he wanted to have a single button in our application which can generate an email file and mark it as a draft so that he can open it in Outlook and make necessary changes before sending the email.

Outlook can open EML or MSG files. I didn’t find an easy (and free) way to generate MSG files, so I decided to generate EML files instead. EML files are basically plain text files which can be opened in any text editor.

How it works

After googling and searching on forums, I found out that the easiest thing to do is to do the following:

  • Use the built-in .NET classes for sending emails
  • Save emails in temp folder instead of sending them via SMTP
  • Pick the email from temp folder and return it as a FileStreamResult

I faced two challenges:

1) When you send an email using SmtpClient, you must provide the TO address. In our case, TO address could be empty, and we still had to generate an email as draft and let the user add the address in Outlook
2) If generated EML file contains X-Sender header, when you send the email using Outlook, you might get some Exchange security errors

Since EML is a plain text file, we can easily modify the generated file from TEMP folder and remove the lines that are causing the problems.

The code

public async Task<FileStreamResult> Email()
{
    string dummyEmail = "test@localhost.com";

    var mailMessage = new MailMessage();
            
    mailMessage.From = new MailAddress(dummyEmail);
    mailMessage.To.Add("dejan.caric@gmail.com");
    mailMessage.Subject = "Test subject";
    mailMessage.Body = "Test body";
            
    // mark as draft
    mailMessage.Headers.Add("X-Unsent", "1");

    // download image and save it as attachment
    using (var httpClient = new HttpClient())
    {
        var imageStream = await httpClient.GetStreamAsync(new Uri("http://dcaric.com/favicon.ico"));
        mailMessage.Attachments.Add(new Attachment(imageStream, "favicon.ico"));
    }

    var stream = new MemoryStream();
    ToEmlStream(mailMessage, stream, dummyEmail);

    stream.Position = 0;

    return File(stream, "message/rfc822", "test_email.eml");
}

private void ToEmlStream(MailMessage msg, Stream str, string dummyEmail)
{
    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();

        // create new file and remove all lines that start with 'X-Sender:' or 'From:'
        string newFile = Path.Combine(tempFolder, "modified.eml");
        using (var sr = new StreamReader(filePath))
        {
            using (var sw = new StreamWriter(newFile))
            {
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    if (!line.StartsWith("X-Sender:") &&
                        !line.StartsWith("From:") &&
                        // dummy email which is used if receiver address is empty
                        !line.StartsWith("X-Receiver: " + dummyEmail) &&
                        // dummy email which is used if receiver address is empty
                        !line.StartsWith("To: " + dummyEmail))
                    {
                        sw.WriteLine(line);
                    }
                }
            }
        }

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

If everything went ok, when you navigate to Email action in your browser, you should get an EML file which you can open in Outlook:

comments powered by Disqus