Skip to content

Instantly share code, notes, and snippets.

@garlou
Created April 7, 2021 10:50
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save garlou/51f6840be05364a93a5688761b00aa26 to your computer and use it in GitHub Desktop.
Save garlou/51f6840be05364a93a5688761b00aa26 to your computer and use it in GitHub Desktop.
Send raw email with attachment with AWS SES using Typescript
/*
* This solution was built based on stackoverflow answer https://stackoverflow.com/a/54233102
* placed by user https://stackoverflow.com/users/2485910/snowball
*/
import AWS from 'aws-sdk';
import {Template} from 'aws-sdk/clients/ses';
interface Attachment {
encoding: 'base64';
filename: string; // test.pdf
content: string; // base64 value
contentType: 'text/plain' | 'application/pdf' | 'image/gif';
}
/**
* Send email with attachment
* @param address
* @param template
* @param attachment
* @returns
*/
const sendRawEmail = (
address: string[],
template: Template,
attachment: Attachment
): Promise<any> => {
return new Promise((resolve, reject) => {
const ses = new AWS.SES({
accessKeyId: process.env.AMZ_ACCESS_KEY,
secretAccessKey: process.env.AMZ_SECRET_KEY,
region: process.env.REGION,
});
const date = new Date();
const boundary = `----=_Part${Math.random().toString().substr(2)}`;
const rawMessage = [
`From: ${process.env.FROM_EMAIL}`, // Can be just the email as well without <>
`To: ${address[0]}`,
`Subject: ${template.SubjectPart}`,
`MIME-Version: 1.0`,
`Date: ${formatDate(date)}`, // 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`,
template.TextPart,
`--${boundary}`,
`Content-Type: text/html; charset=UTF-8`,
`Content-Transfer-Encoding: 7bit`,
`\n`,
template.HtmlPart,
`\n`,
`--${boundary}`,
`Content-Type: text/plain; name="${attachment.filename}"`,
`Content-Description: ${attachment.filename}`,
`Content-Disposition: attachment;filename="${attachment.filename}";`,
`creation-date="${formatDate(date)}"`,
`Content-Transfer-Encoding: ${attachment.encoding}`,
`\n`,
attachment.content,
`\n`,
`--${boundary}--`,
];
const params: AWS.SES.Types.SendRawEmailRequest = {
Destinations: address,
RawMessage: {
Data: rawMessage.join('\n'),
},
Source: process.env.FROM_EMAIL, // Must be verified within AWS SES
// ConfigurationSetName: configuration_set, // optional AWS SES configuration set for open & click tracking
Tags: [
// ... optional email tags
],
};
ses.sendRawEmail(params, (err: any, data: any) => {
if (err) {
return reject(err);
} else {
return resolve(data);
}
});
});
};
/**
* Outputs timezone offset in format ZZ
* @param date
* @returns
*/
const getOffset = (date: 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
* @param input
* @returns
*/
const leadingZero = (input: number) => ('0' + input).slice(-2);
/**
* Formats date in ddd, DD MMM YYYY HH:MM:SS ZZ
* @param date
* @returns
*/
const formatDate = (date: 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}`;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment