pxl
Access counting for any Express-served url - e.g. for a tracking pixel in emails
What for?
Assume you have a newsletter or webpage – but let's focus on an html email – likes this one:
- Logo image
- Link to a post on your own blog
- Link to a post on an external blog
You want analytics about your recipients, right?!
Question 1: Did the recipient open the email?
When the recipient opens the email, the email client will download the logo image. If you do the following you can easily answer the question:
- Serve the logo image with your Express app, e.g. using
serve-static
. If you don't have a suitable image in your html email, use a 1x1 transparent pixel image instead. - Unless you shorten the image url as described for question 3, make sure caching is disabled for this image. You may use
nocache
to achieve this. - Extend the image url by the query
?pxl=<created pxl>
. Your html markup then contains:<img src="http://mysite.com/img/logo.png?pxl=<created pxl>" alt="logo">
- This library provides:
- A
createPxl(...)
function to generate the new code that you use for<created pxl>
and - A
trackPxl
middleware to bump up a counter each time the image url is accessed – that is the email is opened and the logo is downloaded.
- A
- Since you are really clever you generate a new pxl code for each email you send out so you can track if the email was opened by each recipient individually.
Question 2: Did the recipient click on the link to the post on your own blog?
Of course with Google Analytics and the clever use of utm_source
you can track how many of your email recipients visited the post on your own blog. But you cannot see WHO it was in particular.
Assuming you host your own blog with your Express app you can do the following to easily answer the question:
- Extend the link to the post on your own blog by the query
?pxl=<created pxl>
. Your html markup then contains:<a href="http://mysite.com/blog/10-things-you-didnt-know?pxl=<created pxl>">10 things you didn't know</a>
- Unless you shorten the link as described for question 3, make sure caching is disabled for this link. You may use
nocache
to achieve this. - This library provides:
- A
createPxl(...)
function to generate the new code that you use for<created pxl>
and - A
trackPxl
middleware to bump up a counter each time the blog post url is accessed – that is the recipient clicks on the link to the post on your own blog.
- A
- Since you are really clever you generate a new pxl code for each email you send out so you can track if the link was clicked by each recipient individually.
Question 3: Did the recipient click on the link to the post on an external blog?
Of course with a link shortener service like bit.ly you can track how many of your email recipients visited the post on an external blog. But you cannot see WHO it was in particular.
Using pxl
's own link shortener you can do the following to easily answer the question:
- Shorten the link to the post on an external blog by using
pxl.shorten('http://externalblog.com/5-things-your-todo-list-is-missing')
. - Replace the link to the post with the shortened version. Your html markup then contains:
<a href="http://mysite.com/shortly/<linkId>">5 things your Todo list is missing</a>
- This library provides:
- A
shorten(...)
function to generate the new code that you use for<linkId>
and - A
redirect
middleware to redirect the shortened links to the original location, i.e. the post on the external blog.
- A
- Extend the shortened link by the query
?pxl=<created pxl>
. Your html markup then contains:<a href="http://mysite.com/shortly/<linkId>?pxl=<created pxl>">5 things your Todo list is missing</a>
- This library provides:
- A
createPxl(...)
function to generate the new code that you use for<created pxl>
and - A
trackPxl
middleware to bump up a counter each time the blog post url is accessed – that is the recipient clicks on the link to the post on the external blog.
- A
- Since you are really clever you generate a new pxl code for each email you send out so you can track if the link was clicked by each recipient individually.
Installation
This is a module for node.js and is installed via npm:
npm install pxl --save
Since pxl
does not include a persistence layer you either need to implement your own or you install an extended package instead. The following packages currently exist that include a ready-to-use persistence layer:
pxl-mongodb
pxl-dynamodb
- If you implement a package for another database I will gladly list it here!
Usage
Initialization
Initialize pxl
like this:
let Pxl = require('pxl')
let pxl = new Pxl({
persistenceLayer: yourPersistenceLayer,
queryParam: 'pxl',
logPxlFailed(err, pxlCode, url) {}
})
persistenceLayer
must be an instance of a class extendingPxl.PersistenceLayerBase
. Usually, you don't want to implement it yourself and use a package that provides one for your database instead. But if such package doesn't exist please have a look atpxl-mongodb
as a reference implementation.queryParam
with'pxl'
as the default is the query key to be used with thetrackPxl
middleware.logPxlFailed
is a callback that gets called if thetrackPxl
middleware was unable to bump up the counter for the given pxl. This makes sure that the tracking doesn't interfere with serving images etc. and allows you to e.g. monitor any tracking issues like invalid pxl codes etc.
Depending on the persistence layer you use, additional initialization steps may be required. If you e.g. use pxl-mongodb
check its README for details.
Pxl Tracking
// The database stores a counter for each pxl plus metadata you provide.
// The metadata is important for the user behavior analysis you aim to do!
let metadata = {
emailRecepient: 'user@gmail.com'
}
pxl.createPxl(metadata)
.then((createdPxl) => {
// createdPxl.pxl is the code to use in the query - `?pxl=${ createdPxl.pxl }`
})
.catch((err) => {
// Handle any error here
})
To bump up the counter for a pxl you can do it programmatically:
pxl.logPxl(pxlCode)
.catch((err) => {
// Handle any error here
})
However, the usual way to do it is to use the provided middleware:
let app = express()
app.use(pxl.trackPxl)
The middleware will check all requests that pass through for the pxl code in req.query
and bump up its counter accordingly. If any error occurs durings its operation the logPxlFailed
callback will be called without interfering with serving the request.
Pxl References
pxl.createPxl({ for: 'open tracking' })
.then((openTrackingPxl) => {
pxl.createPxl({
for: 'click tracking',
ref: openTrackingPxl.pxl // <-- The click tracking pxl references the open tracking pxl
})
.then((clickTrackingPxl) => {
pxl.logPxl(clickTrackingPxl.pxl) // --> BOTH pxls' counters get bumped up
})
})
The example follows a common use case relevant for email tracking: Usually, the url of an image of the html email is used to track if the recipient opens the email. However, the recipient's email client may be configured to not load any images. In that case the open tracking won't work. But once the recipient clicks on a link in the email we know that the email was opened. With the ref
property the clickTrackingPxl
will automatically log the openTrackingPxl
as well.
Chaining pxls with the ref
property is not possible. Only the direct reference will be resolved.
Link Shortening
pxl.shorten('http://externalblog.com/5-things-your-todo-list-is-missing')
.then((createdLink) => {
// createdLink.linkId is the code to use with the endpoint mounted with the redirect middleware
})
.catch((err) => {
// Handle any error here
})
You can unshorten programmatically:
pxl.unshorten(linkId)
.then((retrievedLink) => {
// retrievedLink.link is the orginal link you shortened
})
.catch((err) => {
// Handle any error here
})
However, unshortening is mainly used for redirecting the user who clicked on the shortened link. For that mount a middleware:
let app = express()
app.get('/shortly/:linkId', pxl.redirect)
Assume you shortened a link and got 'abcdefgh'
as the linkId
. Then the url 'http://mysite.com/shortly/abcdefgh
will be forwarded to the original url.
If anything goes wrong – like using an invalid linkId or a technical error – this error will be forwarded through the middleware chain and can be handled using an error-handling middleware. E.g.:
app.get('/shortly/*', (err, req, res, next) => {
res.redirect('/404.html')
})
Contributing
To set up your development environment for pxl
:
- Clone this repo to your desktop,
- in the shell
cd
to the main folder, - hit
npm install
, - hit
npm install gulp -g
if you haven't installed gulp globally yet, and - run
gulp dev
. (Or runnode ./node_modules/.bin/gulp dev
if you don't want to install gulp globally.)
gulp dev
watches all source files and if you save some changes it will lint the code and execute all tests. The test coverage report can be viewed from ./coverage/lcov-report/index.html
.
If you want to debug a test you should use gulp test-without-coverage
to run all tests without obscuring the code by the test coverage instrumentation.
Change History
- v0.0.4 (2018-04-12)
- Debouncing
.logPxl(...)
calls to filter double clicks by the user. This can happen when a logging call is tied to a button the user can click. - Breaking Change: If you call
.logPxl(...)
directly instead of using thetrackPxl
middleware then.logPxl(...)
calls which get debounced resolve toundefined
.
- Debouncing
- v0.0.3 (2016-10-08)
- Introduced the
ref
property to reference another pxl redirect
middleware disables caching for reliable tracking of requests to shortened urls- Extended
logPxlFailed
signature
- Introduced the
- v0.0.2 (2016-10-06)
- Extended
logPxlFailed
signature
- Extended
- v0.0.1 (2016-10-05)
- Initial version
License (ISC)
In case you never heard about the ISC license it is functionally equivalent to the MIT license.
See the LICENSE file for details.