One of the primary reasons I am supporting Ghost(Pro) is because of the promise of integrating content across a web CMS and an email newsletter without requiring an external platform. I was happy to see the progress wrapping up 2020 in v3.40.0 that allowed monitoring email opens.
Now that we are in 2022, any idea when will we be able to monitor clicks from within an email?
I would really prefer clicks analyzed by post rather than by user, since I would like to track interactions anonymously. I care more about what is getting attention than who as I find the latter to be too intrusive. I am trying to avoid Google Analytics for the same reason by using Plausible instead. Thanks.
The outcome is that subscribers will either look like they never open emails, or in many cases, the email provider returns “read” to everything so it looks like they open 100% of emails, when they don’t. More reading here.
One thing you can do in Ghost to get a clearer picture of engagement is to filter these false positives/negatives out:
One thing I’ve been doing is using a link shortener for all my newsletter links. It’s a bit of a hassle but it will give you some click data! I’ve been using bit.ly out of habit but there are tons out there.
@katyenka That is a great solution when you have a couple of links. I considered that for my needs, but my messages have about 20 unique external links each which would quickly become unsustainable.
Good news though… I was able to figure out a way to track these in Plausible. There may be more elegant solutions but mine has worked thus far. Would anyone be interested if I were to document how to do it?
How to track clicks through web analytics within an email sent via Ghost(Pro)
I still would like to have this native functionality in Ghost but I hope this helps others for the time being who had the same question I did. I am basing these instructions off of the “edition” theme since that is what I am using. Big hat tip to SitePoint for the URLSearchParams code. As I said, I am sure there are more elegant ways to achieve this but it gets the job done. Please feel free to chime in and simplify or make this more widely applicable.
Optional Turn off the Portal button under Settings > Membership > Portal Settings. This is not required but leaving it on shows the button on a temporary, otherwise blank page which looks weird. Hopefully this setting will be able to be overridden on a page-by-page or template basis in the future.
Ensure your web analytics code is added in Ghost to Settings > Site Injection.
Download your current theme from Ghost and back it up locally.
Copy these theme files into a new folder where you will do your work. In the new folder, you should duplicate the default.hbs file and give the clone a new memorable name that starts with custom such as custom-blank.hbs. Open that file in your text editor of choice and edit it down to the absolute minimum template required to present a blank page while still allowing room for code injection with {{ghost_head}} and {{ghost_foot}}. Would love the ability to add a blank template as a default feature in the future
Zip up the folder and give it a new name so that you do not overwrite your old template.
Upload the new theme zip file to Ghost.
Create a new page in Ghost and choose the new custom template you created above.
In the new page, add the following snippet to your Page Header via Code Injection.
<meta name="robots" content="noindex">
<script type = "text/javascript">
function getAllUrlParams(url) {
// get query string from url (optional) or window
var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
// we'll store the parameters here
var obj = {};
// if query string exists
if (queryString) {
// stuff after # is not part of query string, so get rid of it
queryString = queryString.split('#')[0];
// split our query string into its component parts
var arr = queryString.split('&');
for (var i = 0; i < arr.length; i++) {
// separate the keys and the values
var a = arr[i].split('=');
// set parameter name and value (use 'true' if empty)
var paramName = a[0];
var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];
// (optional) keep case consistent
paramName = paramName.toLowerCase();
if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();
// if the paramName ends with square brackets, e.g. colors[] or colors[2]
if (paramName.match(/\[(\d+)?\]$/)) {
// create key if it doesn't exist
var key = paramName.replace(/\[(\d+)?\]/, '');
if (!obj[key]) obj[key] = [];
// if it's an indexed array e.g. colors[2]
if (paramName.match(/\[\d+\]$/)) {
// get the index value and add the entry at the appropriate position
var index = /\[(\d+)\]/.exec(paramName)[1];
obj[key][index] = paramValue;
} else {
// otherwise add the value to the end of the array
obj[key].push(paramValue);
}
} else {
// we're dealing with a string
if (!obj[paramName]) {
// if it doesn't exist, create property
obj[paramName] = paramValue;
} else if (obj[paramName] && typeof obj[paramName] === 'string'){
// if property does exist and it's a string, convert it to an array
obj[paramName] = [obj[paramName]];
obj[paramName].push(paramValue);
} else {
// otherwise add the property
obj[paramName].push(paramValue);
}
}
}
}
return obj;
}
</script>
<script>
var destination = getAllUrlParams().utm_content;
document.write('<meta http-equiv="refresh" content="1; url=' + destination + '"/>');
</script>
Then add utm_content parameters to the links you want to track in your email using your preferred destination. Of course you can add utm_medium and utm_source or others as needed. For instance, if you want to track a link in your email to the Ghost forum, then you could use the following link:
This solution was designed to show your click data within Plausible but should work with Google Analytics as well. I suppose you could incorporate all of the code into the template itself rather than the code injection snippet if you prefer. You also do not have to use utm_content if you have set that up for other data; you could switch the parameter to use utm_term or others. Remember that you will need to encode spaces with %20 if you choose to use them in any of your parameters.
@kym The URLSearchParams code allows you to pull a parameter from the link for use in the meta refresh, in this case utm_content. The meta refresh is what directs the user to the ultimate destination after the blank page loads and increments the visit. Plausible does indeed normally track UTM parameters on its own but cannot do so here without this intermediary step, since the whole point of this process is to measure clicks to external sites.
As I said, there are definitely cleaner ways to do this but it works.
For background, I run https://tldrsec.com/ (~12,000 subscriber profitable cyber security newsletter) using Jekyll and MailChimp. I’d love to be able to use Ghost for the blog and newsletter. However, each newsletter I send is composed of many links (>20), and I really need to be able to know the total and unique clicks per link (ideally subscriber-specific attribution).
I appreciate that this is a lot of work, but I’m not sure if I’ll be able to switch to Ghost without it. If it was supported, I would probably try to switch ASAP.
@Kym - is this sort of functionality on the Ghost team’s roadmap?
For what it’s worth, I would also be willing to personally contribute funds to have this built out, so that everyone can benefit from it.