How to send an email with an attachment using AWS SES

Jens Posingies
3 min readFeb 13, 2020

--

In my last project where we used AWS cloud services, I had to implement a service that could send an email with a JPEG image attached. This sounded pretty simple at first; I decided to create a Node.js based Lambda function using the appropriate SDK for AWS SES.
I was very confident that the API / SDK would directly support this very common use case, but I was told better: sendEmail() supports HTML and text mails, but not multipart mails.

Ouch.

However, while studying the SDK documentation I noticed the function sendRawEmail() which allows to send a “raw” email, i.e. it is available as source code (according to RFC-2822, see https://tools.ietf.org/html/rfc2822).

Now I had never really dealt with this standard before, but I didn’t have much choice — the topic had to be implemented quite soon with AWS-Services, so I tried my luck.

First of all, I created a mail template:

MIME-Version: 1.0
Subject: {subject}
From: <{from}>
To: <{to}>
Bcc: <{bcc}>
Content-Type: multipart/mixed; boundary="____++++_COMMODORE_64_BASIC_V2_++++____"
--____++++_COMMODORE_64_BASIC_V2_++++____
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
{text}--____++++_COMMODORE_64_BASIC_V2_++++____
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-Disposition: inline
{image}--____++++_COMMODORE_64_BASIC_V2_++++____--

As you can see, I dropped some place holders, so I could customize the template by simply using plain string.replace() later on.

All you need to do now is to put the template and your mail parameters into a params object and hand it over to the sendRawEmail() function:

exports.sendMailWithJpg = async function sendMailWithJpg (ses, mailTemplate, jpegData) {
const recipients = 'jean-luc@foobarbaz.com; william-t@foobarbaz.com'
const sender = 'borgqueen@foobarbaz.com'
const params = {
Destinations: extractRecipientsArray(recipients),
FromArn: `arn:aws:ses:<your AWS region>:<your AWS account>:identity/${sender}`,
RawMessage: {
Data: createRawMessage(mailTemplate,
{
from: sender,
to: createRecipientsString(recipients),
bcc: 'bcc@foobarbaz.com',
subject: 'I hate multipart mails!',
text: 'emailForm.message',
image: jpegData
}
)
}
}
return ses.sendRawEmail(params).promise()
}
function createRawMessage (mailTemplate, params) {
const rawMessage = mailTemplate
.replace('{from}', params.from)
.replace('{to}', params.to)
.replace('{cc}', params.cc)
.replace('{bcc}', params.bcc)
.replace('{subject}', params.subject)
.replace('{text}', params.text)
.replace('{image}', params.image)
return rawMessage
}
function extractRecipientsArray (semicolonSeparatedRecipients) {
const recipientsArray = semicolonSeparatedRecipients.split(';')
for (let index = 0; index < recipientsArray.length; index++) {
recipientsArray[index] = recipientsArray[index].trim()
}
return recipientsArray
}
function createRecipientsString (recipientsArray) {
return recipientsArray.join('>,<')
}

As you can see, the multiple recipients require some manual work as well (in this case, we received the recipients as an Outlook like semicolon separated string).

The only part that might be a bit tricky is to get the image data right. As you can see in the corresponding mail template section, it needs to be encoded as Base64. I’ll skip the part how to load and encode your image data properly as Base64 - that totally depends on your use case (uploading an image, downloading, taking screenshots etc.); Google will easily help out on that. Let’s focus on our very specific SES topic.

As I quickly realized, it’s not enough to just encode the data as one plain Base64 string and put it into the mail source code — some mail clients have issues read such long lines.

Fortunately, the solution is pretty straight-forward: Simply insert line breaks before you submit the string to the mail action:

function lineWrapImageData (imageData) {
let lineWrappedImageData = ''
let lineLengthCounter = 0
for (let index = 0; index < imageData.length; index++) {
lineWrappedImageData += imageData.charAt(index)
lineLengthCounter++
if (lineLengthCounter === 512) {
lineWrappedImageData += '\r\n'
lineLengthCounter = 0
}
}
return lineWrappedImageData
}

That’s it! While the solution looks pretty easy, it took me some time to figure out the details properly, so I hope it’s useful for some of you out there.

P.S.: The JPEG image attachment is of course just an example; the approach works for any other media type as well, simply change the content-type in the mail template. The same holds true for the JavaScript part; the technique is easily transferable to other language’s AWS SDKs.

--

--