Software Development

Sending more than a basic email with Amazon SES

Previously, I wrote about getting started with Amazon’s Simple Email Service, and I included details of how to send a basic email. The SendEmail method is excellent at sending basic emails with HTML or Text bodies. However, it doesn’t handle attachments. For that, you need to use SendRawEmail.

SendRawEmail doesn’t give you much functionality. In fact, you have to do all the work to construct the email yourself. However, it does mean that you can do pretty much what you need with the email.

There are still some limitations. Amazon imposes a 50 recipient limit per email, a maximum 10Mb per email, and you can only add a small number of file types as an attachment. This is, I suspect, in order to reduce the ability for people to use the service to spam and infect other people while permitting most of all legitimate uses for the service.

Building an email

When I said that you have to do all the work to construct the email, I really did mean that. You have to figure out the headers, the way the multi-part MIME is put together the character encoding (because email is always sent using a 7-bit encoding) and so on.

I tried to do this, and it it was most frustrating work. The tiniest thing seemed to put Amazon SES into a sulk.

However, I did find a piece of code that someone else had written to do the heavy work for me. Essentially, what he’s doing is constructing a mail message using the built in System.Net.Mail.MailMessage type in .NET and then using .NET’s own classes to create the raw mail message as a MemoryStream, which is what Amazon SES wants.

I’ve refactored the code in the linked post so that it is slightly more efficient if you are calling it multiple times. It uses reflection, and some of the operations need only be carried out once regardless of the number of times you generate emails, so it removes those bits off to a static initialiser so that they only happen the once.

Here’s my refactored version of the code:

public class BuildRawMailHelper
{
    private const BindingFlags nonPublicInstance =
        BindingFlags.Instance | BindingFlags.NonPublic;

    private static readonly ConstructorInfo _mailWriterContructor;
    private static readonly MethodInfo _sendMethod;
    private static readonly MethodInfo _closeMethod;

    static BuildRawMailHelper()
    {
        Assembly systemAssembly = typeof(SmtpClient).Assembly;
        Type mailWriterType = systemAssembly
            .GetType("System.Net.Mail.MailWriter");

        _mailWriterContructor = mailWriterType
            .GetConstructor(nonPublicInstance, null,
                new[] { typeof(Stream) }, null);

        _sendMethod = typeof(MailMessage).GetMethod("Send",
            nonPublicInstance);

        _closeMethod = mailWriterType.GetMethod("Close",
            nonPublicInstance);
    }

    public static MemoryStream ConvertMailMessageToMemoryStream(
        MailMessage message)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            object mailWriter = _mailWriterContructor.Invoke(
                new object[] {memoryStream});

            _sendMethod.Invoke(message, nonPublicInstance, null,
                                new[] {mailWriter, true}, null);

            _closeMethod.Invoke(mailWriter, nonPublicInstance,
                null, new object[] {}, null);

            return memoryStream;
        }
    }
}

At first glance, the fact that the MemoryStream is disposed of does seem a bit counter-intuitive, however some methods of MemoryStream still function when the stream is closed, such as ToArray().

Incidentally, if you want to see what the raw email looks like you can use a piece of code like this to get the raw email as a string:

MemoryStream memoryStream =
    BuildRawMailHelper.ConvertMailMessageToMemoryStream(mailMessage);
byte[] data = rawMessage.Data.ToArray();
using (StreamReader reader = new StreamReader(new MemoryStream(data)))
{
    string rawMail = reader.ReadToEnd();
    Console.Write(rawEmail);
}

Using SendRawEmail

Because you’re doing all the work, the code that actually interacts with Amazon SES is very simple.

// mailMessage is an instance of a System.Net.Mail.MailMessage
var config = new AmazonSimpleEmailServiceConfig();
var client = new AmazonSimpleEmailServiceClient(config);
SendRawEmailRequest request = new SendRawEmailRequest();
request.RawMessage = new RawMessage();
request.RawMessage.Data = BuildRawMailHelper
    .ConvertMailMessageToMemoryStream(mailMessage);
var response = client.SendRawEmail(request);

And that’s it. You can now send emails with attachments, and anything else you can do with a MailMessage.

6 thoughts on “Sending more than a basic email with Amazon SES

  1. I have been playing around with SES and raw emails. I have yet to actually get an attachment to work. My email will show the attachment when I receive it but it is 0 bytes. I am trying to send a PDF as an attachment. I was sending these emails via Google’s SMTP, creating my own MailMessage to do so. I am hosting on Godaddy now so I need a 3rd party service for sending emails quickly.

    Have you tried using this code to attach files to an email and send them?
    Also, can you provide some comments on the conversion to memory stream for the email?

    1. Have you tried using this code to attach files to an email and send them?

      Yes, you can send attachments using the above code through Amazon SES. Just add the attachment to the MailMessage object and the code above will convert it.

      Also, can you provide some comments on the conversion to memory stream for the email?

      I don’t understand this question, can you elaborate?

  2. Hi,
    At the top of the post you mention:
    “Amazon imposes a 50 recipient limit per email, a maximum 10Mb per email, and you can only add a small number of file types as an attachment.”

    1. Do you know if there is a list of file types that can be sent as attachments?
    2. Can I still send email with HTML body using SendRawEmail() method? Do you have an example of that?

  3. Just a warning- there may be threadsafety issues using this method. I tried encoding exactly the same email with two attachments (a JPG and the 500k PDF) multiple times and dumped the memory stream out to file.

    Doing this in a single thread seems to work consistently, i.e. the dumped files are all the same, but when I multi-thread this, the file lengths are different! Comparing two of the different files, I can see that the base 64 encoded content is the same for the HTML body and the JPG, but different for the PDF!

  4. When ever it gets to ‘ _sendMethod.Invoke(message, nonPublicInstance, null,
    new[] { mailWriter, true }, null);’
    It always throws an error for parameter miscount. Anyone else get any weird errors like that?

    1. If you are running .net 4.5 then this code won’t work because it uses internal code from the framework that was not meant to be invoked directly so it can change. I’ll post an update soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s