30
votes

I'm searching for an working C# example to send attachments with Amazon-SES.

After reading that Amazon-SES now supports sending attachments I was searching for an C# example but was unable to find one.

7

7 Answers

23
votes

I think that using AWS SDK for .NET and MimeKit is very easy and clean solution. You can send e-mails with attachments via SES API (instead of SMTP).

You can write MimeMessage directly to MemoryStream and then use it with SES SendRawEmail:

using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;
using Amazon;
using Amazon.Runtime;
using MimeKit;

private static BodyBuilder GetMessageBody()
{
    var body = new BodyBuilder()
    {
        HtmlBody = @"<p>Amazon SES Test body</p>",
        TextBody = "Amazon SES Test body",
    };
    body.Attachments.Add(@"c:\attachment.txt");
    return body;
}

private static MimeMessage GetMessage()
{
    var message = new MimeMessage();
    message.From.Add(new MailboxAddress("Foo Bar", "[email protected]"));
    message.To.Add(new MailboxAddress(string.Empty, "[email protected]"));
    message.Subject = "Amazon SES Test";
    message.Body = GetMessageBody().ToMessageBody();
    return message;
}

private static MemoryStream GetMessageStream()
{
    var stream = new MemoryStream();
    GetMessage().WriteTo(stream);
    return stream;
}

private void SendEmails()
{
    var credentals = new BasicAWSCredentials("<aws-access-key>", "<aws-secret-key>");

    using (var client = new AmazonSimpleEmailServiceClient(credentals, RegionEndpoint.EUWest1))
    {
        var sendRequest = new SendRawEmailRequest { RawMessage = new RawMessage(GetMessageStream()) };
        try
        {
            var response = client.SendRawEmail(sendRequest);
            Console.WriteLine("The email was sent successfully.");
        }
        catch (Exception e)
        {
            Console.WriteLine("The email was not sent.");
            Console.WriteLine("Error message: " + e.Message);
        }
    }
}
11
votes
public Boolean SendRawEmail(String from, String to, String cc, String Subject, String text, String html, String replyTo, string attachPath)
    {
        AlternateView plainView = AlternateView.CreateAlternateViewFromString(text, Encoding.UTF8, "text/plain");
        AlternateView htmlView = AlternateView.CreateAlternateViewFromString(html, Encoding.UTF8, "text/html");

        MailMessage mailMessage = new MailMessage();

        mailMessage.From = new MailAddress(from);

        List<String> toAddresses = to.Replace(", ", ",").Split(',').ToList();

        foreach (String toAddress in toAddresses)
        {
            mailMessage.To.Add(new MailAddress(toAddress));
        }

        List<String> ccAddresses = cc.Replace(", ", ",").Split(',').Where(y => y != "").ToList();

        foreach (String ccAddress in ccAddresses)
        {
            mailMessage.CC.Add(new MailAddress(ccAddress));
        }

        mailMessage.Subject = Subject;
        mailMessage.SubjectEncoding = Encoding.UTF8;

        if (replyTo != null)
        {
            mailMessage.ReplyToList.Add(new MailAddress(replyTo));
        }

        if (text != null)
        {
            mailMessage.AlternateViews.Add(plainView);
        }

        if (html != null)
        {
            mailMessage.AlternateViews.Add(htmlView);
        }

        if (attachPath.Trim() != "")
        {
            if (System.IO.File.Exists(attachPath))
            {
                System.Net.Mail.Attachment objAttach = new System.Net.Mail.Attachment(attachPath);
                objAttach.ContentType = new ContentType("application/octet-stream"); 
                System.Net.Mime.ContentDisposition disposition = objAttach.ContentDisposition;
                disposition.DispositionType = "attachment";
                disposition.CreationDate = System.IO.File.GetCreationTime(attachPath);
                disposition.ModificationDate = System.IO.File.GetLastWriteTime(attachPath);
                disposition.ReadDate = System.IO.File.GetLastAccessTime(attachPath);
                mailMessage.Attachments.Add(objAttach);
            }
        }

        RawMessage rawMessage = new RawMessage();

        using (MemoryStream memoryStream = ConvertMailMessageToMemoryStream(mailMessage))
        {
            rawMessage.WithData(memoryStream);
        }

        SendRawEmailRequest request = new SendRawEmailRequest();
        request.WithRawMessage(rawMessage);

        request.WithDestinations(toAddresses);
        request.WithSource(from);

        AmazonSimpleEmailService ses = AWSClientFactory.CreateAmazonSimpleEmailServiceClient(ConfigurationManager.AppSettings.Get("AccessKeyId"), ConfigurationManager.AppSettings.Get("SecretKeyId"));

        try
        {
            SendRawEmailResponse response = ses.SendRawEmail(request);
            SendRawEmailResult result = response.SendRawEmailResult;
            return true;
        }
        catch (AmazonSimpleEmailServiceException ex)
        {
            return false;
        }
    }
    public static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
    {
        Assembly assembly = typeof(SmtpClient).Assembly;
        Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
        MemoryStream fileStream = new MemoryStream();
        ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
        object mailWriter = mailWriterContructor.Invoke(new object[] { fileStream });
        MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
        sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true }, null);
        MethodInfo closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic);
        closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null);
        return fileStream;
    }

Found that here

Update: A method signature has changed in .NET 4.5, which breaks the above: Getting System.Net.Mail.MailMessage as a MemoryStream in .NET 4.5 beta

4
votes

This is a very simple implementation using MimeKit

using Amazon;
using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;
using MimeKit;
using System.IO;

namespace SendEmailWithAttachments
{
 class Program
 {
    static void Main(string[] args)
    {
        //Remember to enter your (AWSAccessKeyID, AWSSecretAccessKey) if not using and IAM User with credentials assigned to your instance and your RegionEndpoint
        using (var client = new AmazonSimpleEmailServiceClient("YourAWSAccessKeyID", "YourAWSSecretAccessKey", RegionEndpoint.USEast1))
        using (var messageStream = new MemoryStream())
        {
            var message = new MimeMessage();
            var builder = new BodyBuilder() { TextBody = "Hello World" };

            message.From.Add(new MailboxAddress("[email protected]"));
            message.To.Add(new MailboxAddress("[email protected]"));
            message.Subject = "Hello World";

            //I'm using the stream method, but you don't have to.
            using (FileStream stream = File.Open(@"Attachment1.pdf", FileMode.Open)) builder.Attachments.Add("Attachment1.pdf", stream);
            using (FileStream stream = File.Open(@"Attachment2.pdf", FileMode.Open)) builder.Attachments.Add("Attachment2.pdf", stream);

            message.Body = builder.ToMessageBody();
            message.WriteTo(messageStream);

            var request = new SendRawEmailRequest()
            {
                RawMessage = new RawMessage() { Data = messageStream }
            };

            client.SendRawEmail(request);
        }
    }
  }
}

I have the code in my repository https://github.com/gianluis90/amazon-send-email

1
votes

You can setup IIS SMTP to relay through SES as well.

You need to install stunnel and set it up

Then you can just set the IIS SMTP Smart Host and some other options and it will relay your email through SES.

Instructions from above linked gist:

Instructions taken from Amazon's docs and modified as necessary.

1. Install stunnel:

  • Download from stunnel's download page
  • Run installer w/ default options, create self signed cert by answering questions
  • Open the c:\program files (x86)\stunnel\stunnel.conf file in notepad
  • Clear all the server configs (under Example SSL server mode services section, won't have a client = yes line)
  • Create a new client config:

[smtp-tls-wrapper]
accept = 127.0.0.1:2525
client = yes
connect = email-smtp.us-east-1.amazonaws.com:465

  • Start stunnel.exe and ensure no errors are reported (you will get a little systray icon)
  • If it succeeds you can optionally install as a service, by running stunnel.exe -install at command line (note this installs the service but doesn't start it, so start it)
  • Test the connection, at cmd line run telnet localhost 2525 and you should see an SMTP response from Amazon's server (if telnet isn't installed, add the feature in Server Manager / Features / Add Feature)

2. Configure IIS SMTP

  • Set Smart Host as [127.0.0.1] (include the brackets)
  • In the Outbound Connections section, set outgoing port to 2525 (like the stunnel.conf file)
  • In the Outbound Security section, set authentication information to your Amazon SMTP credentials, set it to basic authentication (NOTE: DO NOT CHECK THE TLS CHECKBOX)
0
votes

I'm not sure if this is what you are looking for, but it's the only resource I've been able to find on the subject. I would love a better answer to this question, too.

http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/

It states how to use it, but is very cryptic, at least to me.

Any better guides out there?

0
votes

I also need help with this, but so far I've found you need to send a multipart MIME message, with the attachment encoded in base64.

0
votes

I think you need to follow the instructions on this link. Amazon doesn't let you add attachments or other more complicated message types (iCalendar events). Essentially you need to handcraft the message body by string building and manipulation.

Currently I do this for iCalendar format emails on a legacy system. It's a bit of a pain in the ass, but if you read RFC 2822, it tells you pretty clearly what the format is. Special things to pay attention to:

  • Base64 encoding of the data
  • MIME types
  • Make sure your multipart boundaries match up (and are unique)
  • Finicky problems with the number of line breaks (\n) after certain lines

Best of luck. I don't know if there is an open library that will do this sort of thing for you in C#. If you can find one then try use it, because dealing with the intricacies of the RFC should have a medical notice for increased blood pressure and hair loss.