-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements simple microservice for moving data from SendGrid events t…
…o Mixpanel
- Loading branch information
Showing
8 changed files
with
511 additions
and
272 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.serverless | ||
serverless.env.yml | ||
node_modules |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# SendGrid Event Webhook to Mixpanel Lambda Function | ||
|
||
A simple lambda function that can be used to transfer SendGrid event data to Mixpanel pings. | ||
|
||
## Getting Started | ||
|
||
1. To get started, you'll need the [Serverless Framework](https://serverless.com/framework/docs/providers/aws/guide/quick-start/) installed. You'll also need your environment configured with [AWS credentials](https://serverless.com/framework/docs/providers/aws/guide/credentials/). | ||
|
||
2. Recommended: Map relevant Mixpanel characteristics to [SendGrid SMTP API unique_args](https://sendgrid.com/docs/API_Reference/SMTP_API/unique_arguments.html) for the emails you are sending through SendGrid. | ||
|
||
3. Add the MIXPANEL_TOKEN env var for each environment to `serverless.env.yml`. | ||
|
||
``` yml | ||
dev: | ||
MIXPANEL_TOKEN: "DEV TOKEN" | ||
staging: | ||
MIXPANEL_TOKEN: "STAGING TOKEN" | ||
prod: | ||
MIXPANEL_TOKEN: "PROD TOKEN" | ||
``` | ||
4. Deploy | ||
``` sh | ||
sls deploy --stage prod | ||
``` | ||
|
||
5. [Setup the SendGrid Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) with the lambda endpoint. | ||
|
||
``` | ||
HTTP POST URL | ||
https://EXAMPLE.execute-api.us-east-1.amazonaws.com/dev/mixpanel_sendgrid_events | ||
``` | ||
|
||
6. 🎉 SendGrid event data is in Mixpanel and I don't have to run the webhook through our app! |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const serverless = require('serverless-http'); | ||
|
||
const Mixpanel = require('mixpanel'); | ||
const mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN); | ||
|
||
function handler(event, context, callback) { | ||
let body = JSON.parse(event.body); | ||
let eventsTracked = 0; | ||
|
||
body.forEach((sendGridEvent) => { | ||
mixpanel.track('SendGrid: ' + sendGridEvent.event, sendGridEvent); | ||
eventsTracked += 1; | ||
}); | ||
|
||
const response = { | ||
statusCode: 200, | ||
eventsTracked: eventsTracked | ||
}; | ||
|
||
callback(null, response); | ||
} | ||
|
||
module.exports.mixpanel = mixpanel; | ||
module.exports.handler = handler; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,11 +6,14 @@ | |
"author": "Nick Weiland <[email protected]>", | ||
"license": "MIT", | ||
"dependencies": { | ||
"express": "^4.16.2", | ||
"mixpanel": "^0.7.0", | ||
"serverless-http": "^1.5.2" | ||
}, | ||
"devDependencies": { | ||
"serverless-offline": "^3.16.0" | ||
"chai": "^4.1.2", | ||
"lambda-local": "^1.4.4", | ||
"mocha": "^4.0.1", | ||
"serverless-offline": "^3.16.0", | ||
"sinon": "^4.1.2" | ||
} | ||
} |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
process.env.MIXPANEL_TOKEN = "test_token"; | ||
const expect = require('chai').expect; | ||
const sinon = require('sinon'); | ||
|
||
const lambdaLocal = require('lambda-local'); | ||
const mpTrackSendGrid = require('../mpTrackSendGrid'); | ||
|
||
let sendGridPayload = { | ||
body: JSON.stringify([ | ||
{ | ||
"distinct_id": 123, | ||
"organization_id": 32, | ||
"organization_size": "enterprise", | ||
"user_email": "[email protected]", | ||
"timestamp": 1508542936, | ||
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", | ||
"event": "processed", | ||
"category": "cat facts", | ||
"sg_event_id": "M0KaRo92wGpS1clK5ox6gg==", | ||
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0" | ||
}, | ||
{ | ||
"distinct_id": 456, | ||
"organization_id": 122, | ||
"organization_size": "smb", | ||
"user_email": "[email protected]", | ||
"timestamp": 1508542936, | ||
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", | ||
"event": "deferred", | ||
"category": "cat facts", | ||
"sg_event_id": "W5aNQjYjn5JzWnrfQ6tGuw==", | ||
"sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0", | ||
"response": "400 try again later", | ||
"attempt": "5" | ||
} | ||
]) | ||
}; | ||
|
||
let res; | ||
let err; | ||
let mixpanelSpy; | ||
|
||
before(function(cb) { | ||
mixpanelSpy = sinon.spy(mpTrackSendGrid.mixpanel, 'track'); | ||
lambdaLocal.execute({ | ||
event: sendGridPayload, | ||
lambdaFunc: mpTrackSendGrid | ||
}).then(function(_res) { | ||
res = _res; | ||
}).catch(function(_err) { | ||
err = _err; | ||
}); | ||
|
||
cb(); | ||
}); | ||
|
||
describe('mpTrackSendGrid', function() { | ||
it('returns a 200 status', function() { | ||
expect(res.statusCode).to.equal(200); | ||
}); | ||
|
||
it('counts tracked events', function() { | ||
expect(res.eventsTracked).to.equal(2); | ||
}); | ||
|
||
it('asks mixpanel to track the right number of events', function() { | ||
expect(mixpanelSpy.callCount).to.equal(res.eventsTracked); | ||
}); | ||
|
||
it('asks mixpanel to track the right data', function() { | ||
let event_1 = JSON.parse(sendGridPayload.body)[0]; | ||
let args_1 = mixpanelSpy.firstCall.args; | ||
let props_1 = args_1[1]; | ||
|
||
expect(args_1[0]).to.equal("SendGrid: " + event_1.event); | ||
expect(props_1.category).to.equal(event_1.category); | ||
expect(props_1.sg_event_id).to.equal(event_1.sg_event_id); | ||
expect(props_1.sg_message_id).to.equal(event_1.sg_message_id); | ||
|
||
let event_2 = JSON.parse(sendGridPayload.body)[1]; | ||
let args_2 = mixpanelSpy.secondCall.args; | ||
let props_2 = args_2[1]; | ||
|
||
expect(args_2[0]).to.equal("SendGrid: " + event_2.event); | ||
expect(props_2.category).to.equal(event_2.category); | ||
expect(props_2.sg_event_id).to.equal(event_2.sg_event_id); | ||
expect(props_2.sg_message_id).to.equal(event_2.sg_message_id); | ||
}); | ||
}); |
Oops, something went wrong.