Skip to content

Instantly share code, notes, and snippets.

@metanomial
Last active December 6, 2019 09:02
Show Gist options
  • Save metanomial/23624b3fc2a879d3da80ab9f31f7c604 to your computer and use it in GitHub Desktop.
Save metanomial/23624b3fc2a879d3da80ab9f31f7c604 to your computer and use it in GitHub Desktop.
AWS Lambda Mail Forwarder
const AWS = require('aws-sdk');
/**
S3 service interface
@type {S3}
@see {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html}
**/
const s3 = new AWS.S3({
signatureVersion: 'v4'
});
/**
SES service interface
@type {SES}
@see {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SES.html}
**/
const ses = new AWS.SES();
/** Email wrapper **/
class Email {
/**
Validate an SES inbound email event
@param {object} event - Possible SES inbound email event
@throws {Error} if `event` is not a valid SES inbound email event
**/
static validate(event) {
switch(true) {
case event == null:
case event.Records == null:
case !Array.isArray(event.Records):
case event.Records.length !== 1:
case event.Records[0].eventSource !== 'aws:ses':
case event.Records[0].eventVersion !== '1.0':
throw new Error('Invalid SES inbound email event');
}
}
/**
Email destinations
@type {Array<string>}
**/
recipients;
/**
Source recipient
@type {string}
**/
source;
/**
Email identifier
@type {string}
**/
id;
/**
Create email wrapper from SES inbound email event
@param {object} event - SES inbound email event
@param {bucket}
**/
constructor(event) {
this.recipients = event.Records[0].ses.receipt.recipients;
this.source = this.recipients.length > 0 ? this.recipients[0] : null;
this.id = event.Records[0].ses.mail.messageId;
}
/**
Forward all recipients
@param {Map<string, string>} forwards - Forwards mapping
@param {string} fallback - Fallback forward address
**/
forward(forwards, fallback) {
this.recipients = this.recipients
.map(item => forwards.has(item) ? forwards.get(item) : fallback)
.filter((item, index, array) => array.indexOf(item) === index);
}
/**
Email contents
@type {Promise<string, Error>}
**/
contents = Promise.resolve(null);
/**
Fetch the email contents from S3
@param {string} bucket - S3 bucket name
@param {string} prefix - Key prefix
**/
fetch(bucket, prefix = '') {
const params = {
Bucket: bucket,
Key: prefix + this.id
};
this.contents = new Promise((resolve, reject) => {
s3.getObject(params, (error, data) => {
if(error != null) reject(error);
else resolve(data);
});
});
}
/**
Send email to recipients
@return {Promise<object, Error>}
**/
async send() {
const params = {
Destinations: this.recipients,
Source: this.source,
RawMessage: {
Data: (await this.contents).Body.toString()
}
};
return new Promise((resolve, reject) => {
ses.sendRawEmail(params, (error, data) => {
if(error != null) reject(error);
else resolve(data);
});
});
}
}
module.exports = Email;
const Email = require('./Email');
/**
Forwarding addresses mapping
@type {Map<string, string>}
**/
const forwards = new Map([
['user@originalDomain.com', 'user@forwardDomain.com'] // Example
]);
/**
Fallback forwarding address
@type {string}
**/
const fallback = 'user@forwardDomain.com' // Example
/**
Storage bucket
@type {string}
**/
const bucket = 'myBucket' // Example
/**
Storage key prefix
@type {string}
**/
const prefix = 'emails/' // Example
/**
SES inbound email event runtime handler
@param {object} event - SES inbound email event
@param {object} context - Lambda context object
**/
exports.handler = async function(event, context) {
Email.validate(event);
const email = new Email(event);
email.forward(forwards, fallback);
await email.fetch(bucket, prefix);
await email.send();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment