Send email with AWS SES in a CloudFlare Worker

To send an email via a CloudFlare Worker you will need to call a email provider. AWS Simple Email Service (SES) is a robust transactional email provider that I already use in a number of other projects, and simplifies my billing. However the JavaScript SDK provided by AWS is very large and is not compatible with the CloudFlare Worker runtime. So we will want to roll our own simple API call to the SES.

Perquisites and Setup

  • Setup the domain identity in SES. This will require you to add 3 CNAME records to your domain name.
  • You can then create a new IAM user with an IAM Inline Policy to restrict access to a particular email address.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ses:FromAddress": "[email protected]"
                }
            }
        }
    ]
}
  • You will need to create access key for this new user, which will provide the credentials you will use in the code.
  • For the contact sending we will use code from the AWS 4 Fetch project on GitHub - This project will handle the authentication required to use SES.

npm install aws4fetch

  • Below is the code I am using. (Remember to change your SES region).
export async function sendEmail(toEmail, subjectLine, message) {
	const aws = new AwsClient({ accessKeyId: IAM_ACCESS_KEY, secretAccessKey: IAM_ACCESS_KEY_SECRET' });
	let resp = await aws.fetch('https://email.eu-west-1.amazonaws.com/v2/email/outbound-emails', {
		method: 'POST',
		headers: {
			'content-type': 'application/json',
		},
		body: JSON.stringify({
			Destination: 
			{
				ToAddresses: [ toEmail ],
				BccAddresses: [ '[email protected]' ],
			},
			FromEmailAddress: 'Fifty Pence Direct Debit <[email protected]>',
			Content: {
				Simple: {
					Subject: {
						Data: subjectLine
					},
					Body: {
						Text: {
							Data: message.replace(/<br\s*[\/]?>/gi, "\n"),
						},
						Html: {
							Data: '<body><div align="center" style="font-family:Calibri, Arial, Helvetica, sans-serif;"><table width="600" cellpadding="0" cellspacing="0" border="0" style="font-family:Calibri, Arial, Helvetica, sans-serif"><tr style="background-color:white;"><td><table width="600" cellpadding="0"><tr><td><h1>Fifty Pence Direct Debit</h1><p>' + message +'</p></td></tr></table></td></tr></table></div></body>',
						}
					}
				},
			},
		}),
	});

	const respText = await resp.json();
	console.log(resp.status + " " + resp.statusText);
	console.log(respText);
	if (resp.status != 200 && resp.status != 201) {
		throw new Error('Error sending email: ' + resp.status + " " + resp.statusText + " " + respText);
	}
	return resp.status;
}
  • Send the email via a function call:

ctx.waitUntil(sendEmail('Email To Address', 'Subject Line', 'Html Message here'));

The SES parameters that can be used in the API call can be found in the AWS documentation: https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html

Daniel Mitchell

Daniel Mitchell