Skip to content

Instantly share code, notes, and snippets.

@andyleejordan
Last active July 15, 2021 10:31
Show Gist options
  • Save andyleejordan/327ab637f182f8f19a77572b3f9e71a5 to your computer and use it in GitHub Desktop.
Save andyleejordan/327ab637f182f8f19a77572b3f9e71a5 to your computer and use it in GitHub Desktop.
Google Scripts web app to receive MailGun webhooks
// Hook location: https://app.mailgun.com/app/webhooks
var API_KEY = "key-abcd1234...";
var EMAIL = "[email protected]";
// Naive function to email a posted event from a MailGun web hook.
// `doPost(e)` signature: https://developers.google.com/apps-script/guides/web#deploying
// `parameter` schema: https://mailgun-documentation.readthedocs.io/en/latest/user_manual.html#webhooks
// Events API: https://documentation.mailgun.com/en/latest/api-events.html#api-events
function doPost(e) {
// The output is not consumed, but required by the Google Scripts API.
var output = ContentService.createTextOutput("Post received, thanks!");
var params = e["parameter"];
var params_str = JSON.stringify(params, null, 2);
Logger.log("Processing: " + params_str);
if (!verify(API_KEY, params["token"], params["timestamp"], params["signature"])) {
MailApp.sendEmail(EMAIL, "MailGun verification failed!", params_str);
// There is no way to return a specific exit code; so this still returns 200.
return output;
};
var subject = "MailGun " + params["event"] + " a message!";
var body = params;
try {
var headers = JSON.stringify(JSON.parse(params["message-headers"]), null, 2);
body =
"Domain: " + params["domain"] + "\n" +
"Description: " + params["description"] + "\n" +
"Recipient: " + params["recipient"] + "\n" +
"Message ID: " + params["Message-Id"] + "\n" +
"Headers: " + headers + "\n";
} catch (e) {
body += "\n" + e.message;
}
Logger.log("Body: " + body);
MailApp.sendEmail(EMAIL, subject, body);
return output;
}
// Needs API_KEY replaced with this to test.
var test_key = "key-4847b6d360af1cc52d7261693b609789"; // Yes, this key has been revoked.
var test_token = "4379a5189aab3b606083a4e4fba0b99064e88ce76ea8c2a6df";
var test_timestamp = 1499125194;
var test_signature = "5ad3b5393dd4c0795f8b381e2616646b25d12847b5c1b870b2f45a4f1e010d29";
function testDoPost() {
var parameters = {
"token": test_token,
"timestamp": test_timestamp,
"signature": test_signature,
"event": "dropped",
"recipient": "[email protected]",
"domain": "name.com",
"description": "This is a test POST.",
"message-headers": "[[\"Sender\", \"[email protected]\"], [\"X-Mailgun-Sending-Ip\", \"198.61.254.26\"]]"
};
var e = {
"parameter": parameters,
"parameters": parameters
};
doPost(e);
}
// Returns true if the message was verified.
function verify(key, token, timestamp, signature) {
var computed = toHexString(Utilities.computeHmacSha256Signature(timestamp + token, key));
Logger.log("Computed: " + computed);
Logger.log("Signature: " + signature);
return computed == signature;
}
// Converts a byte[] to a hex string.
// https://stackoverflow.com/a/34310051
// https://en.wikipedia.org/wiki/Hexadecimal
function toHexString(bytes) {
return bytes.map(function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
// Used in conjunction with logs to test `verify()`.
function testVerify() {
return verify(test_key, test_token, test_timestamp, test_signature);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment