Author's Posts

The following article assumes you have read/completed the following:

How the GA360 Connector works

Put simply, the GA360 Connector creates a link between your Google Analytics 360 and Salesforce Marketing Cloud accounts that enables Google to send non-personally identifiable information to SFMC for the purpose of re-marketing.

This is achieved with the following user/data flow:

  1. An email is sent from SFMC to a Subscriber with personalized links/tracking. The Subscriber clicks on the email and is sent through to the company website with tracking parameters; including “sfmc_id=”.
  2. Google Analytics detects these parameters and stores the “sfmc_id” value against it’s own Google ID.
  3. As users interact with your website, Google Analytics checks the Audience Definitions in your account to see if they qualify with any of the definitions you’ve built.
  4. When a web user has qualified for an audience that has been connected with Salesforce Marketing Cloud, Google Analytics checks to see if they have a “sfmc_id” value stored. If the user has a sfmc_id, then Google Analytics sends the value through to Salesforce Marketing Cloud as a Synchronized Data Source.

How tracking changes everything

Not so long ago, the only data most email marketers had access to was Open/Click data from their email platform, and any uploaded 1st party data such as customer information or purchases. Now with the GA360 connector, email marketers can access website traffic generated by their subscribers, closing the loop and uncovering the interactions that take place in-between the email engagement and purchase.

Best of all, the website tracking data isn’t limited to the session that started with an email click! Google Analytics stores the sfmc_id value against their own customer identifier; so your subscribers are tracked even if they return to the website days later and from different channels!

The advanced conditions & sequences in Google’s Audience Builder allows marketers to create an incredible range of re-targeting opportunities. Google has created a sample list of activities you can create using Audience Builder, however the sky is the limit!

Activating your audiences in Salesforce Marketing Cloud

After you’ve created your Google Audiences and selected Salesforce Marketing Cloud as the Audience Destination, it will take up to 24 hours for them to appear in SFMC. For Australian users, Google Analytics publishes audience data at 4am EST each day.

The standard way to activate this data is via a GA360 Entry Source in Journey Builder. This will be sufficient for most use cases, however it can become impractical when you have multiple audiences/activities that a subscriber could qualify for at the same time.
There is a way to access GA360 Audience data in a Data Extension:

  1. Create a new Journey using the “Google Analytics 360” entry source.
  2. Add some flow controls to the journey – such as a Random Split or Wait Activity – and press save.
  3. Close and reopen the Journey to refresh the interface.

If you look at the details in the Entry Event tile you will see the “Data Extension Name” has a funny value starting with “EA_”. This is the name of the synchronized Data Extension used by Google Analytics. Since this is a Data Extension, it can be accessed just like every other DE in SFMC.
The following SQL can be used to access a GA360 Audience Data Extension:

SELECT
SubscriberKey
FROM ent.[EA_xxxxxxx_X_xxxxxx]

More ways to activate web traffic

Accessing the data directly allows marketers to use GA Audience data to create opportunities outside of the standard Journey Builder activation. Rather than thinking of GA Audiences as a tool to create re-targeting segments to activate on directly – we can expand the use cases to include data sets based on customer behaviors that may not be individually actionable, but can instead support other activities.

For a B2C Retail/eCommerce example, we could make an audience that captures web users who have visited over 10 generic pages, but have not made it further down the conversion funnel into the shopping cart. We could use this audience data to infer that the customer is just browsing and not in a purchase state of mind, and then suppress them from more sales-heavy sends.

Alternatively, we could make an audience for customers that have visited the “shipping prices” page in the same session as an “Add to Cart” event, but has not clicked purchase. Normally this would trigger an “Abandoned Cart” activity, however we could infer that this customer is conscious of the shipping cost, and perhaps we could add them to a DE of Subscribers in SFMC that will have a “Free Shipping” promotion banner added in the body of their next EDM.

Closing thoughts

Listening to activities after the initial outbound click of an email gives marketers access to each customers full digital story, and these insights can help to personalized targeted messages and make you customers feel like you truly understand their experience with your brand.

Unassuming actions such as a visit to the Contact Us or Shipping Information page are not reason enough to trigger an activity or communication, but are a great modifiers to existing activities like Card Abandonment, Welcome Emails or general Sales and Newsletters sends.

Read more

Re-Engagement (re-activation, win-back, lapsed subscribers, dormant, etc) Campaigns are a core feature of every good customer marketing strategy, and while the immediate goal of these campaigns is well understood – to retain keep existing customers engaged – the true benefits of these targeted and automated communications is often lost on non-CRM marketers.

It’s time to talk in detail about one of the highest performing campaigns in an email marketers toolkit.

The benefits of a Re-Engagement Journey

Re-Engagement is cheaper than Re-Acquisition

Most companies know their allowable cost per acquisition – the cost they are willing to spend to acquire a new customer based on their predicted lifetime value. Most of the time the lions share of these costs come from above the line campaign spend in display, social, search, and other paid media channels.
Reinvesting this cost as a voucher to a lapsing customer via a re-engagement journey can significantly reduce media spend to re-acquire them later down the track. Re-Engagement vouchers have the added benefit of being a soft/internal cost to your business; meaning they only cost if they are being used, which also means they are working!

Reduce Subscriber Churn

Your churn rate isn’t simply the number of subscribers who click unsubscribe – it’s also all the subscribers who stop opening your emails! While these 2 subscriber states are functionally the same for your business – your emails are not being seen by customers – they are materially different for your database health and operating costs. Sending emails to customers who are never going to open them is a waste of your time, money and reputation (more on that shortly).
You can reduce churn by detecting lessening engagement and reaching out with a non-sales message.

Improve Subscriber Database Health

Sometimes your subscribers will become disengaged and there’s nothing you can do to prevent it – they no longer need to be affiliated with your brand – however they won’t say it to your face (your unsubscribe page). The best thing you can do is to let them go.
If a subscriber fails to re-engage during a win-back campaign, unsubscribe them. Tell them why they are being unsubscribed and how they can subscriber again in the future. Removing these records from your active database will decrease your send volumes/costs, and artificially improve your email open rates.

Improve Domain Health & Reputation

One of the lesser known benefits of a Re-Engagement Campaign is the impact it has to your sending IP address. Inbox providers are watching your sends; they review how many of your emails bounce, don’t get opened or are flagged as spam! Inbox providers are known for requisitioning abandoned inboxes and converting them into “spam traps”; inboxes that silently connect the addresses of senders who don’t adhere to good email database principals.
Your email domain reputation is affected negatively by disengaged subscribers and spam traps, and positively by subscribers who regularly engage with your emails. Identifying and removing disengaged subscribers can help keep your emails away from the junk folder.

What a good Re-Engagement Journey should have

A clear objective & measures of success

Although your Re-Engagement Journey is different to your standard marketing sends – it’s performance should not be measured or treated differently. Define what you consider engagement (open, click, add to cart, purchase, etc) and report on it’s performance to ensure ongoing success. Don’t be afraid to identify “removing disengaged subscribers” as an objective, as we covered above, this will benefit your IP Reputation.

Point of difference from your standard sends

Subscribers should know your Re-Engagement emails are different from the moment they see them in their inbox. The subject line should be short and clear, the email content should be clean and to the point.
In some cases this can be enough to get your email placed in the inbox rather than the promotion/spam folder for some subscribers!

Clear engagement message and actions

Re-Engagement emails should have a clear objective – don’t muddy the message with sales or product information – keep the focus on their continued engagement with your brand. Remove detracting messages banners and use simple Call to Action devices like buttons to identify what you want them to do with the email.

Give lapsing subscribers a choice

Black and white messaging will work on some lapsing subscribers; however sometimes “now is just not the right time”, and an in-between option could have retained a valuable future customer. Enter the Snooze option.
Giving subscribers the option to “snooze” emails for a duration of time is the easiest way to respect their changing communication preferences. Customers who use this option will be in a very particular mind-set; they want to deal with you again in the future, just not right now.
Use this information to your advantage. Design an “awaken” campaign at the completion of their snooze duration; welcome them back to the brand or invite them to snooze a little longer – we’ve all been there…

Keep testing and improving the Journey

Ensure the messaging in your re-engagement emails is performing by splitting all eligible subscribers into 3 groups and conducting an “ABC” test:

  • 70% receives the primary “A” version of the email.
  • 20% receives the testing “B” version of the email.
  • 10% is held as a control group “C” to measure email performance.

Building an ABC testing methodology into your journeys gives you the flexibility to create an ongoing testing process to validate hypothesis and business requests. The 70|20|10 split is relatively safe as the majority of your disengaging subscribers will receive the primary version, however there is enough statistical significance in the remaining groups to prove any test case you try.

Log and Record everything

Depending on your Re-Engagement Journey entry conditions, you may have subscribers who enter the activity multiple times per year! Keep a record of every subscriber who goes through your re-engagement activities and use it to report on long-term subscriber loyalty. The insights gained from viewing long-term subscriber engagement may help to identify problems in your on-boarding or always-on marketing strategy.

Read more

Loops are one of my favorite processes in programming – they allow you to step through a variable amount of data/inputs to produce a customized output! Recently while discussing an API solution with a colleague I realized just how difficult it can be to explain how to use a loop function to solve a problem, and the differences between the various loop statements.

In an attempt to help other users to navigate this topic, lets cover the key recursive functions that are supported in Salesforce SSJS.

Introduction to the FOR Loop

In my opinion the FOR Loop is best SSJS loop statement to start with – it’s the most common, practical and understandable – and shares a very similar syntax with the AMPScript FOR statement, among other programming languages. Consider the following statement and the output below:

<script runat="server">
Platform.Load("core","1");

for (i = 0; i < 10; i++) {
  Write('The Count is: '+i+'<br>');
}

</script>

//Output:
The Count is: 0
The Count is: 1
The Count is: 2
The Count is: 3
The Count is: 4
The Count is: 5
The Count is: 6
The Count is: 7
The Count is: 8
The Count is: 9

The FOR statement contains 3 parts: The Initialization, Condition, and Ending Expression.

The Initialization runs a the start of the code and sets the scene. In the example above, it’s declaring that the variable “i” shall be equal to be 0.

After Initialization, the statement evaluates the expression in the Condition section. In the example above the Condition expression is “i < 10”. Is the value of “i” less than 10?
As the function just initialized and we just declared that i = 0, the expression evaluates as TRUE, and the area of code between the curly brackets “{}” is run.

After the code between the curly brackets has run, the third and last part of the statement is run, the Ending Expression. In the example above, the expression is “i++”, which is short hand for “i = i + 1” or “Add 1 to the value of i”. You can read more about JavaScript Operators here.
The value of “i” was previously 0, however this expression has incremented it by 1. Now that the Ending Expression is complete, the statement returns (loops back) to the Condition expression to be evaluated again.
“i < 10” is re-evaluated as TRUE – since “i” is now equal to 1, and “1 is less then 10” – and the code between the brackets is run again. Once complete, the statement processes the Ending Expression and increments the value of “i” by 1.

The process continues to “loop” over itself until the Condition expression evaluates as FALSE, at which time the statement concludes. In the example above, the last output was “The Count is 9” because as soon as the value of “i” reached 10, the Condition of “i < 10” was no longer true.

Practical uses for the FOR Loop

The beautiful simplicity of the FOR Loop is that it will simply run as many times as the statement requires it to. The example above used “(i = 0; i < 10; i++)” which resulted in the statement running 10 times. Having control over these values means we can make small adjustment to achieve other goals – such as counting down to 0, or counting in numbers other than 1.
Now that we understand the basics, lets use this functionality to solve a business problem.

Lets say we needed to send an email at the start of each day with a scoreboard that displays a list of employees who scored at least 100 points the previous day.
For simplicity lets also assume that the results from the previous day are saved directly into a Data Extension along with the employee names, so there is no need for any other lookups or date manipulation.
The data we have access to is as follows:

IDNamePoints
14Ameen245
6Coby45
42Nolan25
38Trey77
62Lee112
79Duke145
24Conrad37

Based on the above information, we can use the following SSJS code to print a table of the employees that met the condition:

<table border=1>
<tr><th>ID</th><th>Name</th><th>Points</th></tr>
  
<script runat="server">
Platform.Load("core","1");

var EMP_Points = DataExtension.Init("EmployeePointsCampaign");
var filter = {Property:"EMP_Points",SimpleOperator:"greaterThanOrEqual",Value:100};
var data = EMP_Points.Rows.Retrieve(filter);

for (i = 0; i < data.length; i++) {
  Write('<tr><td>'+data[i].EMP_ID+'</td><td>'+data[i].EMP_Name+'</td><td>'+data[i].EMP_Points+'</td></tr>');
}
</script>

</table>
Result:
IDNamePoints
14Ameen245
62Lee112
79Duke145

There’s a lot going on in the code above – so lets break it down!
Notice that the TABLE tags are outside of the script – this is because the LOOP statement is looping over the rows (tr) inside the table for each eligible employee.
You can read more about DataExtension lookups in SSJS here, however the key things to know is that the variable of “data” now holds a JSON object which contains the payload of data returned by the retrieve function that we need to output. You can brush up on your JSON understanding with this JSON intro at W3Schools. The payload returned by the retrieve function is below:

[{"EMP_ID":14, "EMP_Name":"Ameen", "EMP_Points":245},
{"EMP_ID":62, "EMP_Name":"Lee", "EMP_Points":112},
{"EMP_ID":79, "EMP_Name":"Duke", "EMP_Points":145}]

The object contains an array of objects equal to the number of eligible employees (3); where each object has within it an array of Name/Value pairs which hold the data (EMP_ID, EMP_Name, EMP_Points).
To access the name “Ameen”, we need to reference its address in the JSON structure. In this example, its “data[0]. EMP_Name”. The first address reference is data[0] because it’s in the first object in the JSON payload and arrays start at Ordinal “0”. Next we reference the Name that the value is stored in, which is “EMP_Name”.
To reference “Duke’s” point value of 145, we would write: “data[2]. EMP_Points” .

Now that we’ve covered how to address the data, lets explore how the Loop works in this example.
Notice how the Condition statement in this Loop is “i < data.length”. The “.length” property detects how many elements are in the JSON object – which in this example is 3 – therefore the Condition statement actually resolves as “i < 3”.
As we learnt in the earlier example, the for loop will continue until the “i” is no longer less than 3; meaning “i” will step through the values starting at 0 and ending at 2: [0,1,2].

Within the Loop statement, the output line is using the Write() function to print the table elements along with the 3 values that we need; EMP_ID, EMP_Name, EMP_Points. Each of these values is prefixed with “data[i]”, meaning that as the Loop statement steps through the values [0,1,2] it will correctly address each of the 3 objects in the data payload!

The FOR IN Loop

FOR IN is another form of JavaScript Loop that uses a slightly different condition format to determine how many times to run the statement. An example of the FOR IN Loop is as follows:

<script runat="server">
Platform.Load("core","1");
var EMP_data = {"EMP_ID":14, "EMP_Name":"Ameen", "EMP_Points":245};
for (i in EMP_data) {
  Write('The Value of ' +i+ ' is: ' +EMP_data[i]+ '<br>');
}
</script>

//Output:
The Value of EMP_ID is: 14
The Value of EMP_Name is: Ameen
The Value of EMP_Points is: 245

Remember above how we covered the Name/Value pairs in JSON objects? The FOR IN Loop allows you to select every “name” within an array and store it as a value; in the example above it’s stored as “i”.
With the Name attribute known, we can correctly address each Name/Value pair in the payload.

This Loop is very useful when you don’t know the name of every Name/Value pair and just need to cycle through everything that is returned. An example of this is where the Name attributes could be optional or change based on inputs. Rather than hard-coding them like we did in the first example, they can be dynamically referenced with FOR IN.

The DO WHILE Loop

The DO WHILE Loop is a slightly more complex version of the standard WHILE Loop. Since the WHILE Loop functions in much the same way as the FOR Loop , I’ll only cover the DO WHILE version.

This is the most complex Loop to learn, however the utility benefit of this loop can not be understated, it is a truly powerful function for any automation marketing team.
The DO WHILE statement comes in 2 parts – DO{} and WHILE().

<script runat="server">
Platform.Load("core","1");
var i = 0;
do {
  Write("Count to " + i + "<br>");
  i++;
}
while (i < 10);
</script>

//Output:
Count to 0
Count to 1
Count to 2
Count to 3
Count to 4
Count to 5
Count to 6
Count to 7
Count to 8
Count to 9

The above example is a replica of the FOR Loop we used at the top of this article, and it follows a very similar process of running a statement, the DO{} section, “while” a condition is TRUE, the WHILE() section.

This means we have direct control over when the statement ends based on values that can be evaluated as part of the statement. This can be used to interesting effect, such as running a random number generator that will output numbers until it hits the correct number:

<script runat="server">
Platform.Load("core","1");
var random = Math.floor(Math.random() * 10);
do {
  Write("random is " + random + "<br>");
  var random = Math.floor(Math.random() * 10);
}
while (random != 7);
</script>

//Output:
random is 4
random is 9
random is 8
random is 1
random is 3
random is 2
random is 8
random is 2
random is 9
random is 5
random is 9
random is 9
random is 8
random is 1
random is 4
random is 5
random is 0

In the above, the code will continue to run while the value of “random” is not equal to 7. In my test run it took 17 iterations before the number 7 was selected by the random number generator.

Now lets have some fun and make a function that needs to count up to a target number by adding randomly generated numbers together. It will keep generating random numbers (between 1-10) until it reaches its goal; disregarding numbers that will put it over the specified target of 100.

<script runat="server">
Platform.Load("core","1");
var target = 100;
var keepgoing = true;
var total = 0
var steps = 0
do {
  var random = Math.floor(Math.random() * 10)+1;
  steps++;
  if ((total + random) == target) {
    Write("The final Number was " + random + ", bringing to total to "+target+" on step number "+steps+"<br>");
    var keepgoing = false;
  } else {
   if ((total + random) > target) {
    Write("Number " + random + " disregarded - it would push the total to "+(total+random)+"<br>");
     var keepgoing = true;
   } else {
    Write("Number " + random + " added - total is "+(total+random)+"<br>");
    var total = (total + random);
     var keepgoing = true;
   }
  }
}
while (keepgoing);
</script>

//Output:
Number 2 added - total is 2
Number 4 added - total is 6
Number 8 added - total is 14
Number 8 added - total is 22
Number 6 added - total is 28
Number 6 added - total is 34
Number 3 added - total is 37
Number 6 added - total is 43
Number 8 added - total is 51
Number 1 added - total is 52
Number 2 added - total is 54
Number 2 added - total is 56
Number 8 added - total is 64
Number 7 added - total is 71
Number 7 added - total is 78
Number 7 added - total is 85
Number 5 added - total is 90
Number 6 added - total is 96
Number 9 disregarded - it would push the total to 105
Number 3 added - total is 99
Number 5 disregarded - it would push the total to 104
Number 2 disregarded - it would push the total to 101
Number 7 disregarded - it would push the total to 106
Number 9 disregarded - it would push the total to 108
Number 4 disregarded - it would push the total to 103
Number 7 disregarded - it would push the total to 106
Number 2 disregarded - it would push the total to 101
Number 8 disregarded - it would push the total to 107
Number 8 disregarded - it would push the total to 107
Number 2 disregarded - it would push the total to 101
Number 2 disregarded - it would push the total to 101
Number 2 disregarded - it would push the total to 101
Number 2 disregarded - it would push the total to 101
Number 7 disregarded - it would push the total to 106
Number 9 disregarded - it would push the total to 108
Number 7 disregarded - it would push the total to 106
Number 6 disregarded - it would push the total to 105
Number 4 disregarded - it would push the total to 103
Number 6 disregarded - it would push the total to 105
The final Number was 1, bringing to total to 100 on step number 40

Practical Examples for the DO WHILE Loop

The function above was creative, but the real world applications for this kind of loop are endless!
We could write an automated email that selects items from a customer’s cart in price acceding order until it reaches the minimum spend needed for Free Shipping. Imagine getting an email from your favorite fashion retailer with the subject line “Buy these 7 items in your wishlist today and get FREE SHIPPING!”
Retailers with annual spend milestones required to maintain a loyalty status – such as Sephora, Witchery and Mimco – could use the same logic to create a personalized Call to Action for the customer to maintain membership for another year. “Hey %%fName%%, restock on these %%count%% items and maintain %%tier%% status for another year!”

The versatility of SSJS Loops is that they can be run on Cloud Pages, in Emails, and as activities in Automation Studio, so there are plenty of ways to ingest and activate your data! Be sure to ask for help on the Salesforce StackExchange if you need assistance building your SSJS Loops.

Read more

I recently read the Marketing Cloud Security module on Trailhead and found the Secure Your Web and Landing Pages unit to be extremely interesting, however it glossed over the reason and function of the Example Headers that are recommended.

As the defined role gap between IT and Marketing is closing quickly, I thought this content needs to be given the attention it deserves from a non-IT perspective.

Website Headers and why you should care

Response headers are used to give a more detailed context of the response provided by a server. To critically over simplify this: When you hit SEARCH on Google.com your request is sent to Google to process – the response is then sent back and interpreted by your browser where is it rendered accordingly. If you are using Chrome, you can open the DevTools to see this in action: Instructions here.

With the example above, the response from Google contains a number of headers that instruct your browser what to do, for example:

content-encoding: br
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=31536000
  • content-encoding: The response is compressed, and your browser needs to know which encodings were used by the server so it can decode the message.
  • content-type: The type of content contained in the response so that your browser knows how to render it.
  • strict-transport-security: This forces the browser to use the more secure HTTPS for communicating with the server, and sets a duration (in seconds) for this to be upheld.

So Headers are important. Why does Salesforce recommend we use these 6 on our pages?

The 6 headers outlined by Salesforce in their Landing Pages Security Best Practices provide an additional layer of security to prevent malicious actors from accessing your Cloud Page in ways that may hurt your business. Each of these 6 headers provide explicit instructions about how your Cloud Page can be accessed. Lets cover these in detail:

<script runat=server>
   Platform.Response.SetResponseHeader("Strict-Transport-Security","max-age=200");
   Platform.Response.SetResponseHeader("X-XSS-Protection","1; mode=block");
   Platform.Response.SetResponseHeader("X-Frame-Options","Deny");
   Platform.Response.SetResponseHeader("X-Content-Type-Options","nosniff");
   Platform.Response.SetResponseHeader("Referrer-Policy","strict-origin-when-cross-origin");
   Platform.Response.SetResponseHeader("Content-Security-Policy","default-src 'self'");
</script>

Strict-Transport-Security:
As above, this Header ensure all communication from a browser is sent over HTTPS. Without going into detail, HTTPS will protect your user’s data as it is sent to and from your Cloud Page. You really want to be using HTTPS if you are asking your subscribers to enter any form of personal information on your Cloud Page.
Note: SSL certificates are required to be installed by Salesforce Support and at the time of writing have an annual fee of ~$450 USD each. Adding this header without have an SSL installed on the appropriate Marketing Cloud Subdomain will result in the page request being blocked.

X-XSS-Protection:
XSS stands for Cross Site Scripting, and is a form of attach where a malicious actor injects scripts onto your page to make it perform differently.
This is a common header and most browsers support this by default. The “1; mode=block” value indicates that the header is active, and to block the page from rendering if a XSS attack is detected.

X-Frame-Options:
This header prevents your page from being loaded into a frame/iframe on another website.
The “Deny” value prevents any site from referencing you page in an iframe, however you can also use the “SameOrigin” or “Allow-From” values to specify trusted exceptions. You should always use this header unless you have an explicit plan for the page to be used in an iframe.

X-Content-Type-Options:
Content-Type sniffing is where an attacker can exploit a security vulnerability to interpret metadata from your site and possibly execute functions that the site was not built to expect. Forcing such errors can expose sensitive code or customer data.
Using the “nosniff” parameter tells the browser that the content-type is defined and not to be queried further.

Referrer-Policy:
The Referrer Policy controls how much referrer (origin site) information is sent in the request (destination site).
The “strict-origin-when-cross-origin” parameter will enforce HTTPS policy and only send the Full Referrer URL when the request comes from the same Origin. Cross-Origin requests will only see the base URL.

Content-Security-Policy:
This header acts as an additional layer of protection against Cross Site Scripting and other attacks.
The value recommended by Salesforce for this header is “default-src ‘self'”. This will force the browser to only load content that is served from the same place as the page with this header. This may cause problems with externally references images and CDN hosted JS libraries such as jQuery, so check the assets used on your page before enabling this one.

More ways to keep you Pages & Data safe

Cloud Pages are extremely versatile and powerful feature of Salesforce Marketing Cloud – however if used incorrectly can they can open up numerous serious security risks to your business and customers. Check with your company’s WebAdmin before enabling these headers and ask the Marketing Cloud community on Stack Exchange for help if you run into problems.

Read more

Regular Expression (RegEx) is an instruction set for matching and returning a part of a larger string, and is recognised by most programming languages.

AMPScript is unable to nativity parse JSON objects, meaning that string manipulation is the only way to extract the value from the larger object. As discussed in my OAuth 2.0 Access Token Quick Start guide, the SubString function is a quick way to return text if it’s location in the payload is reliable.

Since the order of objects in JSON payloads are not designed to be preserved, RegEx is a more reliable way of searching and returning values from a string. The following RegExMatch function is that I use to return the access_code from the V2 Token API:

SET @access_token = REGEXMATCH(@apistatusCode,'^(?:.*"access_token":")(.*?)(?:".*)$',1)

If you need more detail on how the RegExMatch() function works, I suggest checking out the examples on The AMPScript Guide.

RegEx can take some time to get your head around as it’s a different syntax to most languages. To understand what the Regular Expression string above is achieving, I suggest using an online visual RegEx guide such as regexr.com or regex101.com to build and test your expressions. Copying the example JSON V2 Token response into either of these services and inspecting the rule set will give a far more detailed explanation to how this expression works; however in short, the expression above is returning everything that comes after "access_token":" and before the very next " character.

Due to the simplicity of this Regular Expression, you can amend it relitivly quickly for any value in any JSON endpoint. For example, the icanhazdadjoke.com API will return a random Dad Joke every time it’s requested:

{
  "id": "R7UfaahVfFd",
  "joke": "My dog used to chase people on a bike a lot. It got so bad I had to take his bike away.",
  "status": 200
}

We can alter the RegEx code from earlier to return the value of the “joke” object in the JSON response above:

^(?:.*"joke":.*")(.*?)(?:".*)$

Note that the Dad Joke API returns a space after the : character, so I used the .* wildcard to indicate that anything could optionally appear there.

Regular Expressions are a very powerful and can be used to great effect if you understand how they work. This is a language syntax worth spending some time learning!

Read more

Working in Marketing Automation, I spend a lot of my time in Salesforce Marketing Cloud creating API integrations to push and pull data from various sources.

Writing these calls from scratch or copying bespoke code from previous solutions can be messy, so to save time I created a boilerplate of the basic OAuth 2.0 GetToken request process in AMPScript and SSJS that was easily editable for my purposes.

TL;DR. Here’s the codes!

AMPScript OAuth 2.0 using the V2 Token Endpoint:

%%[
VAR @httppost,@authurl,@apiid,@apistatusCode,@apiresponse,@apitoken
SET @apiid = '{"client_id": "zzzzzzzzzzzzzzzzzz","client_secret": "xxxxxxxxxxxxxxxxxxxxxxx", "grant_type": "client_credentials"}'
SET @authurl = "https://ccccccccccccccccccccc.auth.marketingcloudapis.com/v2/token";
SET @httppost = HTTPPost2(@authurl,"application/json",@apiid,false,@apistatusCode,@apiresponse)
SET @access_token = REGEXMATCH(@apistatusCode,'^(?:.*"access_token":")(.*?)(?:".*)$',1)
SET @rest_instance_url = REGEXMATCH(@apistatusCode,'^(?:.*"rest_instance_url":")(.*?)(?:".*)$',1)
]%%

access_token: %%=v(@access_token)=%% <br>
rest_instance_url: %%=v(@rest_instance_url)=%% <br>
apistatusCode: %%=v(@apistatusCode)=%% <br>

The Access Token can be retrieved using @access_token. Since the API response is in JSON – a data structure not supported by AMPScript – we need to use other string functions to extract the value we need, but more on that later.

SSJS OAuth 2.0 using the V2 Token Endpoint:

<script runat="server">
    Platform.Load("Core","1");
    var payload = {
    "grant_type": "client_credentials",
    "client_id": "yyyyyyyyyyyyyyyyyyy",
    "client_secret": "xxxxxxxxxxxxxxxxxx"
};
var authurl = "https://zzzzzzzzzzzzzzzzzz.auth.marketingcloudapis.com/v2/token";
var result = HTTP.Post(authurl, 'application/json', Stringify(payload));
  
if (result.StatusCode == 200) {
    var responseJson = Platform.Function.ParseJSON(result.Response[0]);
    var accessToken = responseJson.access_token;
    var restUrl = responseJson.rest_instance_url;
} else {
    throw new Error("Error fetching access token");
}
Write(result.Response[0]); //payload response
Write(accessToken); //token
</script>

Luckily Server Side JavaScript is able to parse JavaScript Object Notation (JSON), so the “access_token” value can be referenced and stored in the variable “accessToken” for later use.

Parsing JSON Values in Salesforce Marketing Cloud

If you work with API endpoints you’ll be familiar with the XML and JSON content types. JSON took the most-popular content-type throne from XML in around 2013 and has been the dominant format ever since. As of February 2020 there is still no AMPScript-only solution for parsing JSON values in Salesforce Marketing Cloud, so we need to employ other tools to extract the values we need.

Below is an Example JSON Response of the V2 Token Endpoint from earlier:

{
"access_token":"eyJhbLciOiJIPzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIifQ.eyJhY2Nlc3NfdG9rZW4iOiJhYmJUQTlpSHZqRjkyd3Jkb0xWZEFCaloiLCJjbGllbnRfaWQiOiI3ZTRmYW1xaWUzcWtzdzlhNDRrcmxvZDgiLCJlaWQiOjEwNzU3Njc2LCJzdGFja19rZXkiOiJRQTFTMSJ9.wSFfEdeNrkoiU_tnmJ2ihm8iUqnJKlZoI3GlavTGBhs.hU4EsiC1e9txh_TCt90YlI2l7xZZ5E6_oa0xku3Jj9CCk1B72M4bhO3kUIyhwfVuB0MFbL0y9KD_RRFzg-nuqPgjPyONnby-iWopdZPBHd-3woupxCMST5-vfJO9qAED9qiUfYLS4WmHRuJTCX4NPScyu8BdROTVEe-D3iAoAeFoJX_rLZ9d5eEhIn1AvkYgoj9siuxAprHEvmySTgNIXkQA6uT_IQ-H1dbfOyJmlFKpYzvhvHb0KH7NJ24zy5bd2MQ5",
"expires_in":1200,
"token_type":"Bearer",
"rest_instance_url":"mc563885gzs27c5t9-63k636tzgm.rest.marketingcloudapis.com",
"soap_instance_url":"mc563885gzs27c5t9-63k636tzgm.soap.marketingcloudapis.com",
"scope": "email_read email_write email_send"
}

In the example above, we could use a SubString function to extract the specific character index range that we need for the token. In this example, it’s character 17 through 529; or from character 17 for a length of 512 characters, written as: %%=Substring(@response,17,512)=%% in AMPScript.

SubString works well so long as the response payload doesn’t change in any way, however by default JSON is an unordered set of name/value pairs, so even though Salesforce does preserve object orders, we shouldn’t rely on it.

RegEx is the most reliable way to extract a string value from a JSON payload in AMPScript. I cover this topic in more detail here: RegEx to get JSON values in AMPScript

Read more

Server Side JavaScript is a powerful language in Salesforce Marketing Cloud with numerous practical applications for digital marketers. It can lookup Data Extensions, it has a simple WSProxy model for accessing SOAP objects, and best of all – it can handle JSON objects!

However developing these kinds of activities in SFMC can sometimes be tedious due to the lack of error detection and handling in Emails and Cloud Pages. Before using this function I could easily waste hours hunting down my SSJS code errors!

So here is a SSJS code snippet that I use to detect and handle any errors in my Cloud Pages:

<script runat="server">
Platform.Load("Core","1");
try{
//Do your SSJS Functions Here
} 
catch(error) {
Write('Message: '+ error);
}
</script>

The try statement identifies a block of code to watch for errors while it’s being run.
The catch(error){} statement works like an if statement – it only runs if the condition “was there an error in the try section” is true. The variable “error” is where the the exception message – why the error occurred – will be stored.
In the code above, I’ve used the “error” variable inside of a Write() function so that my Cloud Page outputs the cause of any errors that occur.

Why should I use Try-Throw-Catch

Everyone makes mistakes when writing code, and the worst kind of mistakes are the ones you can’t find! Normally I like to us the Output/Print/Write functions when developing to see where the error occurred in my code – however this isn’t possible in SFMC, as the Cloud Page just fails to load or throws an ambiguous 500 error.

Using the Try-Throw-Catch function above will ensure you get a response from the page, and if an error has occurred, you’ll have some clues about what went wrong.

What about the “Throw” part?

By default, the try statement only listens for invalid operations – like syntax errors. However you can create your own “errors” by using the throw statement. For example:

<script runat="server">
Platform.Load("Core","1");
try{
//Do your SSJS Functions Here
var subKey = Attribute.GetValue("_subscriberkey");
if (!subKey) {throw "Empty SubscriberKey";}
} 
catch(error) {
Write('Message: '+ error);
}
</script>

In the code above, I’m trying to set the Cloud Page attribute “_subscriberkey” into the variable “subKey”.

If this page was accessed via a %%=RedirectTo(CloudPagesURL(x))=%% link in an email, the recipient’s SubscriberKey attribute would be populated as part of the Query String, and page would load.
However if the page was access directly and without a QS parameter, then the _subscriberkey attribute will be empty, and the page will throw the custom error I created.

Further Reading & Resources

I highly recommend reading through some of the JS Documentation on error handling; it’s a great way to speed up your development and put controlled exceptions around unstable 3rd party API calls.
Here’s a few resources I used to learn how the try statement works:

Read more