Last active
July 3, 2019 15:14
-
-
Save iseebi/a86f56696b9905009c2e0138e8f65224 to your computer and use it in GitHub Desktop.
SES domain/account mapping forward / based: https://github.com/arithmetric/aws-lambda-ses-forwarder/blob/master/index.js
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
"use strict"; | |
console.log("AWS Lambda SES Forwarder - domain forward // modified by @iseebi, Rev.2 // Original: @arithmetric, Version 2.3.0 "); | |
// Configure the S3 bucket and key prefix for stored raw emails, and the | |
// mapping of email addresses to forward from and to. | |
// | |
// Expected keys/values: | |
// - fromEmail: Forwarded emails will come from this verified address | |
// - emailBucket: S3 bucket name where SES stores emails. | |
// - emailKeyPrefix: S3 key name prefix where SES stores email. Include the | |
// trailing slash. | |
// - forwardSuffix: forward domain suffix | |
// - forwardToDomain: forward domain (maps: any@[account][forwardSuffix] -> [account]@[forwardToDomain]) | |
var defaultConfig = { | |
fromEmail: "noreply@example.com", | |
emailBucket: "s3-bucket-name", | |
emailKeyPrefix: "emailsPrefix/", | |
forwardSuffix: ".mail.example.com", | |
forwardToDomain: "example.com" | |
}; | |
var parseMailHeader = function(input) { | |
var returnValues = []; | |
for (var line of input.split(/\r?\n/)) { | |
var m = line.match(/^([^\s]+):(.*)$/); | |
if (m) { | |
returnValues.push({name: m[1], lines: [m[2]]}); | |
} | |
else if ((returnValues.length > 0) && (line.length > 0)) { | |
returnValues[returnValues.length - 1].lines.push(line); | |
} | |
} | |
return returnValues; | |
}; | |
var rebuildMailHeader = function(input) { | |
var returnValues = ""; | |
for (var item of input) { | |
if (item.lines.length > 0) { | |
if (returnValues.length > 0) { | |
returnValues += "\r\n"; | |
} | |
var name = item.name.replace(/\r\n/, ''); | |
var value = item.lines.join("\r\n").replace(/(\r?\n){2,}/, "\r\n"); | |
returnValues += name + ":" + value; | |
} | |
} | |
return returnValues; | |
} | |
/** | |
* Parses the SES event record provided for the `mail` and `receipients` data. | |
* | |
* @param {object} data - Data bundle with context, email, etc. | |
* @param {function} next - Callback function invoked as (error, data). | |
*/ | |
exports.parseEvent = function(data, next) { | |
// Validate characteristics of a SES event record. | |
if (!data.event || | |
!data.event.hasOwnProperty('Records') || | |
data.event.Records.length !== 1 || | |
!data.event.Records[0].hasOwnProperty('eventSource') || | |
data.event.Records[0].eventSource !== 'aws:ses' || | |
data.event.Records[0].eventVersion !== '1.0') { | |
data.log({message: "parseEvent() received invalid SES message:", | |
level: "error", event: JSON.stringify(data.event)}); | |
data.context.fail('Error: Received invalid SES message.'); | |
return; | |
} | |
data.email = data.event.Records[0].ses.mail; | |
data.recipients = data.event.Records[0].ses.receipt.recipients; | |
next(null, data); | |
}; | |
/** | |
* Transforms the original recipients to the desired forwarded destinations. | |
* | |
* @param {object} data - Data bundle with context, email, etc. | |
* @param {function} next - Callback function invoked as (error, data). | |
*/ | |
exports.transformRecipients = function(data, next) { | |
var newRecipients = []; | |
data.originalRecipients = data.recipients; | |
var re = new RegExp("@(.+)"+data.config.forwardSuffix.replace(".", "\\\.", "g")+"$"); | |
data.recipients.forEach(function(origEmail) { | |
var matchResult = origEmail.match(re); | |
if (matchResult !== null) { | |
newRecipients = newRecipients.concat( | |
matchResult[1] + "@" + data.config.forwardToDomain); | |
data.originalRecipient = origEmail; | |
} | |
}); | |
if (!newRecipients.length) { | |
data.log({message: "Finishing process. No new recipients found for " + | |
"original destinations: " + data.originalRecipients.join(", "), | |
level: "info"}); | |
data.context.succeed(); | |
return; | |
} | |
data.recipients = newRecipients; | |
next(null, data); | |
}; | |
/** | |
* Fetches the message data from S3. | |
* | |
* @param {object} data - Data bundle with context, email, etc. | |
* @param {function} next - Callback function invoked as (error, data). | |
*/ | |
exports.fetchMessage = function(data, next) { | |
// Copying email object to ensure read permission | |
data.log({level: "info", message: "Fetching email at s3://" + | |
data.config.emailBucket + '/' + data.config.emailKeyPrefix + | |
data.email.messageId}); | |
data.s3.copyObject({ | |
Bucket: data.config.emailBucket, | |
CopySource: data.config.emailBucket + '/' + data.config.emailKeyPrefix + | |
data.email.messageId, | |
Key: data.config.emailKeyPrefix + data.email.messageId, | |
ACL: 'private', | |
ContentType: 'text/plain', | |
StorageClass: 'STANDARD' | |
}, function(err) { | |
if (err) { | |
data.log({level: "error", message: "copyObject() returned error:", | |
error: err, stack: err.stack}); | |
return data.context.fail("Error: Could not make readable copy of email."); | |
} | |
// Load the raw email from S3 | |
data.s3.getObject({ | |
Bucket: data.config.emailBucket, | |
Key: data.config.emailKeyPrefix + data.email.messageId | |
}, function(err, result) { | |
if (err) { | |
data.log({level: "error", message: "getObject() returned error:", | |
error: err, stack: err.stack}); | |
return data.context.fail("Error: Failed to load message body from S3."); | |
} | |
data.emailData = result.Body.toString(); | |
next(null, data); | |
}); | |
}); | |
}; | |
/** | |
* Processes the message data, making updates to recipients and other headers | |
* before forwarding message. | |
* | |
* @param {object} data - Data bundle with context, email, etc. | |
* @param {function} next - Callback function invoked as (error, data). | |
*/ | |
exports.processMessage = function(data, next) { | |
var sp = data.emailData.split(/\r?\n\r?\n/m, 1); | |
var header = sp[0]; | |
var body = data.emailData.substr(header.length); | |
var headerItems = parseMailHeader(header); | |
for (var i in headerItems) { | |
var name = headerItems[i].name.toLowerCase(); | |
// data.log({level: "info", message: "Process Header ->" + name}); | |
if (name == 'from') { | |
headerItems[i].lines = [' ' + data.config.fromEmail]; | |
} | |
else if (name == 'reply-to') { | |
headerItems[i].lines = []; | |
} | |
else if (name == 'return-path') { | |
headerItems[i].lines = []; | |
} | |
else if (name == 'sender') { | |
headerItems[i].lines = []; | |
} | |
else if (name == 'errors-to') { | |
headerItems[i].lines = []; | |
} | |
else if (name == 'dkim-signature') { | |
headerItems[i].lines = []; | |
} | |
} | |
header = rebuildMailHeader(headerItems); | |
// data.log({level: "info", message: "Output Header ->" + header}); | |
data.emailData = header + body; | |
next(null, data); | |
}; | |
/** | |
* Send email using the SES sendRawEmail command. | |
* | |
* @param {object} data - Data bundle with context, email, etc. | |
* @param {function} next - Callback function invoked as (error, data). | |
*/ | |
exports.sendMessage = function(data, next) { | |
var params = { | |
Destinations: data.recipients, | |
Source: data.originalRecipient, | |
RawMessage: { | |
Data: data.emailData | |
} | |
}; | |
data.log({level: "info", message: "sendMessage: Sending email via SES. " + | |
"Original recipients: " + data.originalRecipients.join(", ") + | |
". Transformed recipients: " + data.recipients.join(", ") + "."}); | |
data.ses.sendRawEmail(params, function(err, result) { | |
if (err) { | |
data.log({level: "error", message: "sendRawEmail() returned error.", | |
error: err, stack: err.stack}); | |
data.context.fail('Error: Email sending failed.'); | |
} else { | |
data.log({level: "info", message: "sendRawEmail() successful.", | |
result: result}); | |
next(null, data); | |
} | |
}); | |
}; | |
/** | |
* Report success after all steps are complete. | |
* | |
* @param {object} data - Data bundle with context. | |
*/ | |
exports.finish = function(data) { | |
data.log({level: "info", message: "Process finished successfully."}); | |
data.context.succeed(); | |
}; | |
/** | |
* Handler function to be invoked by AWS Lambda with an inbound SES email as | |
* the event. | |
* | |
* @param {object} event - Lambda event from inbound email received by AWS SES. | |
* @param {object} context - Lambda context object. | |
* @param {object} overrides - Overrides for the default data, including the | |
* configuration, SES object, and S3 object. | |
*/ | |
exports.handler = function(event, context, overrides) { | |
var steps = overrides && overrides.steps ? overrides.steps : | |
[ | |
exports.parseEvent, | |
exports.transformRecipients, | |
exports.fetchMessage, | |
exports.processMessage, | |
exports.sendMessage | |
]; | |
var step; | |
var currentStep = 0; | |
var AWS = require('aws-sdk'); | |
var data = { | |
event: event, | |
context: context, | |
config: overrides && overrides.config ? overrides.config : defaultConfig, | |
log: overrides && overrides.log ? overrides.log : console.log, | |
ses: overrides && overrides.ses ? overrides.ses : new AWS.SES(), | |
s3: overrides && overrides.s3 ? overrides.s3 : new AWS.S3() | |
}; | |
var nextStep = function(err, data) { | |
if (err) { | |
data.log({level: "error", message: "Step (index " + (currentStep - 1) + | |
") returned error:", error: err, stack: err.stack}); | |
context.fail("Error: Step returned error."); | |
} else if (steps[currentStep]) { | |
if (typeof steps[currentStep] === "function") { | |
step = steps[currentStep]; | |
} else { | |
return context.fail("Error: Invalid step encountered."); | |
} | |
currentStep++; | |
step(data, nextStep); | |
} else { | |
// No more steps exist, so invoke the finish function. | |
exports.finish(data); | |
} | |
}; | |
nextStep(null, data); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hi,
i tested on lambda and got that error. thanks.
{
"errorMessage": "Error: Received invalid SES message."
}