How to create message body for Amazon SES sendRawEmail

Snowball picture Snowball · Jan 16, 2019 · Viewed 8.1k times · Source

I am trying to customize the Headers while using the AWS Node.js SDK.

It seems that the sendEmail API endpoint and accompanying SDK method doesn't support customizing Headers.

So the appropiate method to use would be sendRawEmail.

Unfortunately I couldn't find good information or a package that actually helps composing the raw email. Meaning sending both plain text and html formats and creating the message body and headers in accordance to RFC 2822 and RFC 5322.

How would a simple method that composes the raw body written in JavaScript look like?

Answer

Snowball picture Snowball · Jan 17, 2019

Here is a barebones function I came up with, which has only the AWS Javascript SDK as a dependency. One thing I did wrong in the beginning was Base 64 encoding the RawMessage.Data, but the AWS SDK already takes care of that.

The empty lines \n are important as well.

const sendRawEmail = async () => {

    // Set up from_name, from_email, to, subject, message_id, plain_text, html and configuration_set variables from database or manually

    var date = new Date();

    var boundary = `----=_Part${ Math.random().toString().substr( 2 ) }`;

    var rawMessage = [
        `From: "${ from_name }" <${ from_email }>`, // Can be just the email as well without <>
        `To: ${ to }`,
        `Subject: ${ subject }`,
        `MIME-Version: 1.0`,
        `Message-ID: <${ message_id }@eu-west-1.amazonses.com>`, // Will be replaced by SES
        `Date: ${ formatDate( date ) }`, // Will be replaced by SES
        `Return-Path: <${ message_id }@eu-west-1.amazonses.com>`, // Will be replaced by SES
        `Content-Type: multipart/alternative; boundary="${ boundary }"`, // For sending both plaintext & html content
        // ... you can add more headers here as decribed in https://docs.aws.amazon.com/ses/latest/DeveloperGuide/header-fields.html
        `\n`,
        `--${ boundary }`,
        `Content-Type: text/plain; charset=UTF-8`,
        `Content-Transfer-Encoding: 7bit`,
        `\n`,
        plain_text,
        `--${ boundary }`,
        `Content-Type: text/html; charset=UTF-8`,
        `Content-Transfer-Encoding: 7bit`,
        `\n`,
        html,
        `\n`,
        `--${ boundary }--`
    ]

    // Send the actual email
    await ses.sendRawEmail( {
        Destinations: [
            to
        ],
        RawMessage: {
            Data: rawMessage.join( '\n' )
        },
        Source: from_email, // Must be verified within AWS SES
        ConfigurationSetName: configuration_set, // optional AWS SES configuration set for open & click tracking
        Tags: [
            // ... optional email tags
        ]

    } ).promise();

}

These are the utility methods for formatting the Date Header. You could use as well simply use the moment library like moment().format('ddd, DD MMM YYYY HH:MM:SS ZZ'); but it doesn't really matter as the Date Header gets overwritten by AWS SES anyway.

// Outputs timezone offset in format ZZ
const getOffset = ( date ) => {

    var offset          = - date.getTimezoneOffset();
    var offsetHours     = Math.abs( Math.floor( offset / 60 ) );
    var offsetMinutes   = Math.abs( offset ) - offsetHours * 60;

    var offsetSign      = ( offset > 0 ) ? '+' : '-';

    return offsetSign + ( '0' + offsetHours ).slice( -2 ) + ( '0' + offsetMinutes ).slice( -2 );

}

// Outputs two digit inputs with leading zero
const leadingZero = ( input ) => ( '0' + input ).slice( -2 );

// Formats date in ddd, DD MMM YYYY HH:MM:SS ZZ
const formatDate = ( date ) => {

    var weekdays = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ];

    var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];

    var weekday = weekdays[ date.getDay() ];

    var day = leadingZero( date.getDate() );

    var month = months[ date.getMonth() ];

    var year = date.getFullYear();

    var hour = leadingZero( date.getHours() );

    var minute = leadingZero( date.getMinutes() );

    var second = leadingZero( date.getSeconds() );

    var offset = getOffset( date );

    return `${ weekday }, ${ day } ${ month } ${ year } ${ hour }:${ minute }:${ second } ${ offset }`;

}