Save That Email. As an Email.

Applications typically send a lot of emails.  At least, they should, since it’s a good, archive-able method for communication and confirmation.  Archive-able for the receiver, sure, but what about the sender – the application?  You have a couple of options: you can parse out all the fields of the email and stick them in a database or you can CC or BCC the email to a mailbox for archival.  What if you want to store the actual email that was sent?  You want the actual EML file.

Wouldn’t it be nice if the MailMessage object had a .SaveAs method?  It doesn’t.  Well then, wouldn’t it be nice if the SmtpClient object had a .SaveTo property?  It does, kind of.  And by using those properties, we can capture the actual EML file that is typically sent to a SMTP server for delivery.  Once we have that EML file data, we can save it to a file or to a database or wherever.

Quite simply, you need to set two properties on the SmtpClient object: DeliveryMethod and PickupDirectoryLocation.  This tells the SmtpClient to write the EML to a specified folder and the mail server will monitor that folder and pull it from there.  This is the code:

    Private Function GetEmailBytes(ByVal eml As Mail.MailMessage) As Byte()
        Dim smtp As Mail.SmtpClient
        Dim customFolderName As String
        Dim fileBytes() As Byte

        customFolderName = IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, Guid.NewGuid.ToString)

        IO.Directory.CreateDirectory(customFolderName)

        smtp = New Mail.SmtpClient
        With smtp
            .Host = "localhost"
            .DeliveryMethod = Mail.SmtpDeliveryMethod.SpecifiedPickupDirectory
            .PickupDirectoryLocation = customFolderName
            .Send(eml)
            .Dispose()
        End With

        fileBytes = IO.File.ReadAllBytes(New IO.DirectoryInfo(customFolderName).GetFiles.First.FullName)

        IO.Directory.Delete(customFolderName, True)

        Return fileBytes

    End Function

To explain the extra legwork involving directories, the SmtpClient writes the EML file with a GUID as a filename.  This prevents emails from overwriting each other.  In a multi-user environment though, how could we know which file was just written so we read the right file? So to be sure what we’re reading is our email, we create a unique folder to write the EML to and we know there will be only one file in there to read.

The GetEmailBytes method just returns the bytes of an EML file.  That’s the most flexible way to work with the data.  If you want to save that to another place with another name, just use IO.File.WriteAllBytes, like so:

    Private Sub SaveMessage()
        Dim eml As Mail.MailMessage
        Dim bytes() As Byte

        eml = New Mail.MailMessage
        With eml
            .To.Add(New Mail.MailAddress("anyone@home.com"))
            .From = New Mail.MailAddress("nobody@home.com")
            .Subject = "test save"
            .Body = "this is the body"
        End With

        bytes = GetEmailBytes(eml)

        IO.File.WriteAllBytes(IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.Desktop, "output.eml"), bytes)

        eml.Dispose()

    End Sub