Good ideas come from interesting problems
I should not be spending that many of my late evenings on browsing salesforce.stackexchange.com, scavenging it for new, unanswered questions on Salesforce Marketing Cloud. We all have our addictions, this one is one of many for me. Never mind. What triggered my curiosity few days ago, was a question asking about the possibility of progressing a contact through a journey, to the next activity, immediately upon an email open…
Pushing a contact through a journey
Well, I though to myself, you would surely need an Wait Until Event step. It will hold your contact “captive” until it receives an API call. Something I have been using previously, with great success, together with a Cloud Page. A form submission, coming from a link in an email, would not only submit the data, but also send a call to this API event, “releasing” the contact, allowing it to progress into the next step.
But again, one thing is to progress the contact in real time through the journey with an API call – this is not only feasible, but fully supported with standard functionality. Another thing is to track email opens – in real time. Doing it on an batch basis, using Query Activity in Automation Studio, applying some SQL on _Open Data view is possible. But it will come with a latency. Unless you want to run your automation hourly, which on the other hand will quickly count towards your annual automation runs. Automation Runs is one of the key cost drivers in SFMC licenses, and even the “biggest” edition has a limit of 100.000 annual automation runs. Introducing one automation with hourly runs will result in 8760 annual runs. Having just 12 of these automations, will exceed your limit. But that’s a sidetrack (an important one, nevertheless).
Still, even with a frequently running automation, you will not see anything real time – and you will also still need to build a script activity, which will need to pick up these “opens” and send an API call to the right wait step.
Enter: Code Ressources
Those of you who know me well, might already be familiar with my passion for Cloud Page Code Resources, which is my weapon of choice, my Swiss knife, my duct tape, my WD-40 when I need to be HIGHLY creative.
I have used one previously in my solution for showing Marketing Cloud data in Sales Cloud, and I should probably write an article on Code Resources in general. Until then, here is a fantastic article on the same by my big SFMC idol, Salesforce Marketing Champion and MVP: Mateusz DÄ…browski. I particularly enjoy his emphasis on “Learning how to use and abuse SFMC Code Resources”. Because you are about to witness the same – violent abuse of a Code Resource. Do you see where we are going with this…? Are you brave enough to read the rest of my article?
Emails opens are a thing of a past
Before I get all carried away, I will admit something and warn you. Open tracking is not what it used to be. With the increased focus on privacy, open tracking is becoming less and less reliable. Some email opens are not tracked, as the tracking pixels are blocked by the email clients. Other cases show nearly 100% open rates (looking angrily at you, Apple) since the pixels are triggered upon email delivery. Litmus has a quite nice article about this topic, going more in depth.
Open tracking using a text file
So. When are we going to talk code resources? Well, now is the right time. As you might now, these come in few flavours. My favourite type (or Layout (?!?!) as Salesforce claims it is) for integrations is JSON. Allowing you to return the correct content type for integration clients calling it. We might be using that, or XML, or… but we’ll settle on the Text type for now:
What you want to include in your Code Resource is the logic you want to execute, once an open is registered. This can be triggering a journey, calling a Wait Until Event API, creating a Task in Sales Cloud, or simply adding a record to a data extension. As this is purely a proof of concept, I will not spend too much time on implementing any complex logic. This is well documented in multiple documents, help articles and blog posts out there. I will just create a record in a data extension, showcasing that it can indeed be created the very second an email is read. Let’s have a quick look at the simple code:
%%[
set @subscriberkey = requestParameter("subscriberKey")
set @jobid = requestParameter("jobId")
SET @servertime = NOW()
SET @localtime = SystemDateToLocalDate(@servertime)
set @insertCount = insertData("openTracking","subscriberKey", @subscriberKey,"openDate", @localtime, "jobId", @jobId)
]%%
Simple, isn’t it? So far so good. The next part is to actually register the opens. So. What similarities do we have between a tracking pixel and a text code resource? They are both files, served over a publicly accessible url. Let’s have a look how an open tracker is embedded in an email:
<img src="https://click.ex.example.com/open.aspx?defef-fe9910712345017d77-fe98762746c037aa00077-fe30000004046a61d70-ff296206c-fe2d167971736833e761170-ffce15&d=10183&bmt=0" width="1" height="1" alt="">
So, what we are seeing here, is a simple image tag, with a dynamic query string, which is specific to this exact recipient and this email. The fact that the email client requests this image, in the same way it requests the logo in your header, or the hero image, passes this information from the query string to SFMC, registering the “opening” of this email.
This query string gets created at send time, while the actual “placeholder” for open tracking looks like this in your emails:
<custom name="opencounter" type="tracking"/>
Can we possibly create something similar? Yes we can! (And here it becomes slightly crazy). Enter our text file disguised as an image:
<img src="%%=CloudPagesUrl(1234,"subscriberKey",_subscriberkey, "jobid",jobid)=%%" width="0" height="0">
So, what you are seeing here, is an image tag calling a cloudPagesUrl (which points to our code resource) and tries to display an image. Needless to say, this image will never be shown. But, the only thing we are interested in is whether the URL gets requested. And it does so upon opening of the email. Once the email is sent, you will see the actual URL being transformed from a CloudPagesUrl function and into a proper link with a query string:
<img src="https://cloud.ex.example.com/openTracker?qs=927bc947484bad7188b921a35916025672564752def8ff6c2c3078f85c037c7fc8ab9589c8364fd283460230bfa8780055efd3bff3c67367deafbbe610bf4f56b08b1937ca736c826bb36a187b54eff194c71" width="0" height="0">
So indeed, something not far from the open tracker. And so far, it seems to be working in web clients across Yahoo, Hotmail and Gmail:
Be aware, that the way above code is built, the “event” will be triggered on every load of the same email. You would want to create logic which will prevent multiple opens of the same email trigger multiple times. You can’t prevent the tracker from being opened, but your Ampscript should preferably store the fact that a certain event already occurred – and stop further processing.
So, what’s the conclusion?
- Can you execute code (Ampscript/SSJS) in real time, upon email opens? Yes
- Does this solution work every time? Probably not
- Why not? Privacy measures make open tracking unreliable. You could create logic preventing code from being executed if opens are registered immediately after send, or come from specific domains like me.com and icloud.com (which I know are showing open rates of close to 100%)
- Is this safe to use for large sends (we are talking millions of recipients) I doubt. So far, code resources are not subject to super message consumption, and there are to my knowledge no limits on how many views you can do. But this is more a POC rather than a production ready solution. Use it at your own risk.
Do you have any questions, comments or suggestions? Let me know!