Last active
December 6, 2019 09:02
-
-
Save metanomial/23624b3fc2a879d3da80ab9f31f7c604 to your computer and use it in GitHub Desktop.
AWS Lambda Mail Forwarder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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