Search Content
Hero Image

All HowToSFMC Articles

Published 02/15/2024
Spring '24 Release Review

After what felt like a longer than usual January, Salesforce finally shared the release notes for the Spring 2024 release. After such a long wait for new features and with the extended delay and overall lateness to the party with some stacks only having 10 days to get prepared for the changes, surely this release is setting up to be a big one!

Alas, it seems not.

In fact, much of this release seems to be about less to do with Marketing Cloud Engagement and is padded out with references to Data Cloud, Cross Cloud capabilities and repeated content - which for many customers may not be entirely useful. However, let’s get into the details of what you can expect in the Spring 2024 release.

WhatsApp as a channel in SFMC gets a little bit of a face lift and brought up to speed with some of the other channels available in the platform. New transactional messaging API capabilities allow users to send non-promotional messages from outside of Journey Builder. You’ll also start to see WhatsApp engagement reporting made available in Intelligence Reports for Engagement (The reporting that replaced Discover reports) but not in Intelligence Reports for Engagement Advanced (The additional premium Datorama SKU?). But it’s something!

More of a Google change than an SFMC change, but users who use MobilePush and leverage the Firebase FCM APIs will need to update to HTTP v1. The Firebase FCM APIs have been deprecated for almost 8 months at this point, so it’s good that Salesforce have finally started to allow users to update. Check out the Firebase documentation here to find out more.

Back in the Winter 2024 release, Salesforce moved the journey optimisation dashboard from open beta to general release and the relentless approach to making users optimise the way they work seems to be continuing. Now, SFMC will make recommendations to you if you have items such as back to back decision splits which could impact journey performance. It currently won’t prevent you from activating journeys with less than optimal performance, but I wouldn’t be surprised if that changes in future releases.

The Journey History Dashboard gets some overdue enhancements where you may have similar activities being indistinguishable in the user interface. Now activities will have Activity IDs to enable you to distinguish between them. On the subject of dashboards, from the journey dashboard you’ll be able to pause and resume multiple journeys at once from the journey dashboard rather than having to go into each individually. This is the natural progression from the bulk stop journey capability from Winter 2024. Keen to see how Salesforce tackles the configuration elements of pausing journeys as not every reason to pause is created equal!

Ever wished your emails would send faster out of Journey Builder? Approximately 5 weeks after the release lands for some accounts - Salesforce has stated a new High-Throughput Sending for Journey Builder setting will be available in the journey settings panel. No mention of cost implication, or whether this essentially applies high priority sending to each of the email activities in a journey. There’s probably not many scenarios where you would elect to not use High-Throughput Sending for Journey Builder unless there’s an additional burden on Super Message consumption? It’s a little light on the detail in the approximately 20 words in the release so keep your eyes peeled on your super message consumption!

What could be the most significant part of this release is the introduction of being able to Track any URL interaction in Engagement Split Activities in Journey Builder. Again, no details have been shared about the specific implementation of this new capability, will this be done via a list in the user interface or will users be required to paste requisite URLs into a free text field? We’ll soon find out.

Einstein Probabilistic Opens is being renamed to Einstein Metrics Guard. Nothing new to see here, just padding out the release with a rename.On the subject of padding out the release. WhatsApp is also getting referenced here. Just with a bit more detail in that you’ll get dashboards and dimensions to build your pivot tables against.

A couple of things to call out here, some documentation for what has previously been undocumented REST API endpoints. Great to see the documentation for these, many of which have been previously shared on StackExchange and through various blogs and communities. However, getting this documented means any of the solutions that have been built under the caveat of “This is undocumented and may stop working with no recourse” now have some extra support.

A neat new feature is the ability to create one use imports via the REST API. Previously you could have leveraged an existing Import Definition via the SOAP API to deliver similar type capabilities. Plenty of use cases for this kind of functionality and you can leverage S3, Azure Blob, Google Cloud Storage or any of your existing File Transfer Locations.

A couple of items here, both hygiene and optimisation related. First one is around Data Retention and Data Extensions that would be deleted via the retention configuration. These can no longer be linked to the Contact model. If you’ve got any of these and they’re used in your Journeys, make sure to check as this could end up creating some unexpected outcomes if links are getting removed.

What seems like a new enforcement of a known limit within SFMC is the cap on field length for the field used for sendable data extension data relationships. Where SubscriberKey has a cap of 254 characters, this will now be enforced within Data Extensions in the sending relationship. This shouldn’t impact any other text fields that you may use for content. Again, check the sendable fields in your Data Extensions to make sure this doesn’t cause issues in your SFMC org as this will be enforced on existing as well as new activities for email sends or Journeys.

Process Builder is being retired (and has been since 2022ish) but this time for Marketing Cloud Connect. The retirement will happen beginning after the Spring 2024 release and is expected to run until May 2024. Migrations to record-triggered flows will happen automatically when a Journey using the object is published. All journeys that depend on that same object will also be migrated.

Distributed Marketing gets a couple of enhancements.

  • A new campaign performance dashboard
  • The option to create a single use template with Phrases content blocks
  • Additional personalisation from 5 new objects including Opportunity, Account and Case

Having been lagging behind other elements of SFMC, CloudPages will move from collections to Folders and get a new recycle bin similar to Content Builder. This should make things easier and remove the need for hacky workarounds to move Cloud Pages between folders. You’ll also be able to nest folders which will allow a more efficient storage taxonomy for your Cloud Pages.

The Marketing Cloud Engagement App is being retired. If you have it installed already you can continue to use it until retirement on May 5th 2024. Recommendation from Salesforce is to use your mobile browser instead. If you’re an active SFMC user on the go, it would be good to hear some community comments of your experience of interacting with SFMC on your mobile device.

IP addresses for Event Notification System are changing. If you have an IP Allowlist within your business to listen to events, you’ll need to get these updated to match your stack.

Automation Studio gets some general enhancements. Salesforce is claiming that scheduled automations are now 62% more on time. Salesforce have also previously claimed that Importing Data from one Data Extensions to another would be up to 10x faster, so it would be interesting to see if there is any evidence to back up this claim.The Data Extension Storage report available in the Setup tab is getting the customer key added to it, this should help you find any of those Data Extensions that don’t appear in the user interface but still have storage usage attached to them!If you’ve ever had a situation where an automation has been paused and you’re not sure why or who did it. Salesforce has decided that you should know who most recently paused the journey so you can make sure the right person is aware that they’ve not reactivated a journey that has been paused!

This is the shortest set of release notes and the review is the shortest written, even when compared to the 5 release cycle that was replaced a few years ago. Progress in the platform is slow, there is padding in the documentation and repetition between releases. The level of detail in the release notes is getting less and less. With the release notes published as late as they are to the release going live this is becoming unacceptable. Salesforce Marketing Cloud Engagement is an enterprise platform and the quality of the information provided in these notes is a fraction of what is published for other platforms in the Salesforce ecosystem.

That’s not to say that there aren’t good things in this release, there are. Most of them are quality of life releases rather than game changing capabilities. Cloud Pages getting folders is great, the new REST API documentation is also welcomed (albeit not new capability just not admission of the capability) and the single use imports without needing an Import Definition through the REST API is definitely welcomed.

Fingers crossed we get a bit more in the next release!

Read more
HowToSFMC
Published 12/14/2023
SOAP
12 Days of Scriptmas - 2023

As we reach the end of 2023, we’re rapidly approaching that time of year again. It’s that time where all of the HowToSFMC Elves and helpers get together to share their Scriptmas joy with the world. For the fourth year in a row, HowToSFMC is proud to announce Scriptmas is back!

Whether you’ve got a script you’d like to submit for the HowToSFMC Helpers to celebrate Scriptmas cheer in 2023 or you’re looking to see what the community has got to offer, that may be a little gift to yourself. Make sure to check back in the run up to the big day.

If you’re not familiar with Scriptmas, head up to the search bar and check out the submissions and contributions we’ve had over the last few years. From December 13th up to Christmas Eve itself we’ll be revealing one piece of Scriptmas magic for the world to see.

Check out the scripts revealed so far below:

On the first day of Scriptmas, <ins>Ruchika</ins> gave to us - a complete example to robustly personalise email content to subscribers, including graceful fallback content! Take a look and see how you can use this in your day to day.

<details> <summary>Click here to see the Day One Script</summary>


/* Define variables */
SET @recipientName = "Friend"
SET @magicalReunionEvent = "Christmas Gathering"

/* Check if the recipient's name is available */
IF NOT EMPTY(@recipientName) THEN

  /* Personalized greeting for the recipient */
  SET @greeting = CONCAT("Dear ", @recipientName, ",")

  /* Content for the email */
  SET @emailContent = CONCAT(
    "<h2 style='color: 
    "<p>As we prepare for our ", @magicalReunionEvent, ", we can't help but feel the warmth of togetherness in the air.</p>",
    "<p>We hope this Christmas event will bring back fond memories and create new magical moments!</p>",
    "<p>Looking forward to seeing you at the gathering!</p>"
  )

  /* Output the personalized greeting and content */
  OUTPUTLINE(@greeting)
  OUTPUTLINE(@emailContent)

ELSE

  /* Default content if recipient's name is unavailable */
  SET @defaultContent = "<p>Dear friend, join us at our Magical Reunion for a heartwarming Christmas event!</p>"

  /* Output default content */
  OUTPUTLINE(@defaultContent)

ENDIF

]%%

</details>

<br /> Thank you again Ruchika! Make sure to check back tomorrow for our second piece of Scriptmas joy!


On the second day of Scriptmas, <ins>Nicolò</ins> gave to us… A script to update and run a single script activity in one fell swoop. No more writing, saving and manually running once. Perfect for those times where your query needs to change automatically! Customise this and stick it in an automation and you’ll be laughing your way to the automation bank!

<details> <summary>Click here to see the Day Two Script</summary>

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

    // setting the required parameters

    var deName = "DeName";                  // Name of the DE target of the query
    var deCustomerKey = "deCustomerKey";    // Customer Key of the DE target of the query
    var queryParameter = "queryParameter";  // Parameter to be changed in the query
    var queryCustomerKey = "queryKey";      // Customer Key of the query
    var queryObjectId = "queryObjectId";    // Object Id of the query

    // running function to edit the query

    var qd = QueryDefinition.Init(qkey);

    var sql = "SELECT\r\n      sub.Subscriberkey\r\n    , sub.EmailAddress\r\nFROM _Subscribers AS sub\r\nINNER JOIN _Sent AS sent\r\n    ON sub.SubscriberKey = sent.SubscriberKey\r\nINNER JOIN _Job AS job\r\n    ON sent.JobId = job.JobId\r\nWHERE job.EmailName = \"" + queryParameter + "\"";
    
    var prox = new Script.Util.WSProxy();
    var options = {
      SaveOptions: [
        {
          "PropertyName": "*",
          SaveAction: "UpdateAdd"
          }
        ]
      };

    var data = {
      CustomerKey: queryCustomerKey,
      ObjectID: queryObjectId,
      QueryText: sql,
      TargetType: "DE",
      DataExtensionTarget: {
        Name: dename,
        CustomerKey : deCustomerKey
      }
    };

    var desc = prox.updateItem("QueryDefinition", data, options);

    // run the query 
    
    qd.Perform();
  
</script>

</details>

<br />

Thank you again Nicolò! I wonder what Scriptmas will bring us tomorrow? You’ll have to come back and see!


On the third day of Scriptmas, <ins>Ralph</ins> gave to us… A neat little trick to leverage AMPscript within an SSJS Activity to create or update a case in Salesforce CRM.

<details> <summary>Click here to see the Day Three Script</summary>

var userInput = Platform.Function.ParseJSON(Platform.Request.GetPostData());
if (userInput.casedata) {
var caseId = createCaseInSalesforce(userInput);
response.caseId = caseId; 
}

/**
 * @function createCaseInSalesforce
 * @description Creates a case in Salesforce by executing an AMPscript block.
 * @param {Object} userInput - The user input containing case data.
  * @returns {String} The ID of the created case.
 * @throws Will throw an error if case creation fails.
 */
function createCaseInSalesforce(userInput) {
    try {
    var caseVariables = userInput.casedata.split(',');
    var caseFieldValues = [];

    // Loop through the dynamic AMPscript variables and populate the caseFieldValues array
    for (var i = 0; i < caseVariables.length; i++) {
        var key = caseVariables[i].replace(/^\s+|\s+$/g, ''); // Trim whitespace
        var value = userInput[key]; // Get the value from the userInput object

        // Only count fields if a value exists
        if (value != undefined) {
            numCaseFields++;
            caseFieldValues.push("'" + key + "', '" + value + "'"); // Push the field name and value directly
        }
    }
        // Build the AMPscript string to create the Case object in Salesforce
        var createCaseSalesforceObjectAmpscript = 'SET @Case = CreateSalesforceObject("Case", ' + caseFieldValues.length + ', ' + caseFieldValues.join(', ') + ')';
        
        // Execute the AMPscript block and retrieve the result
        var caseId = caseAmpScript(createCaseSalesforceObjectAmpscript);
        return caseId;
    } catch (error) {
        throw { message: "Error creating case in Salesforce: " + error.message };
    }
}


/*----------------------------------------------------*/
/*------------ EXECUTE CASE AMPSCRIPT FUNCTION ------------*/
/*----------------------------------------------------*/
/**
 * @function caseAmpScript
 * @description Executes a block of AMPscript code within SSJS. This function
 *              takes a string of AMPscript code, wraps it in delimiters to
 *              form a valid AMPscript block, then uses the TreatAsContent 
 *              function to evaluate the block. After execution, the function 
 *              retrieves the value of a predetermined variable set within the
 *              AMPscript code.
 * @param {String} code - The AMPscript code to execute. This should be a 
 *                        string containing valid AMPscript syntax.
 * @returns {String} The value of the '@Case' variable as set by the executed 
 *                   AMPscript. It's assumed that the AMPscript code sets a 
 *                   variable named '@Case'. If '@Case' is not set, the 
 *                   function returns undefined.
 * @example
 * // Example of using the ampScript function:
 * var caseIdAmpScript = "SET @Case = CreateSalesforceObject('Case', 3, 'Subject', 'Inquiry', 'Description', 'Details')";
 * var caseId = ampScript(caseIdAmpScript);
 * // caseId now contains the Salesforce ID of the created case object.
 */
function caseAmpScript(code) {
    // Wrap the provided code in an AMPscript block
    var ampBlock = '%%[' + code + ']%%';

    // Treat the AMPscript block as content and execute it
    Platform.Function.TreatAsContent(ampBlock);

    // Retrieve the value of the '@Case' variable set by the AMPscript
    return Variable.GetValue('@Case');
}  

</details>

<br />

Thank you for your Scriptmas treat, Ralph! Check back tomorrow to see what’s behind Script Door Number Four…


On the fourth day of Scriptmas, Salesforce Marketing Champion <ins>Pato</ins> gave to us… An AMPscript driven method to retrieve a dynamically populated countdown timer for use in email or Cloud Pages!

<details> <summary>Click here to see the Day Four Script</summary>

%%[
/*

This script lets you display a countdown timer in your emails using countdownmail.com.

Step 1. Create a dynamic timer with countdownmail.com
Step 2. Get the id of your timer from the embed code window
Step 3. Set the enddate variable with the end date using ISO format
Step 4. Display the timer!

*/

set @enddate = "2024-01-01T00:00:00.0000000-00:00"
set @enddate_f = formatdate(@enddate,"YYYY-MM-DDThh:mm:sszzz")
]%%
<br>

<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<img src="https://pbs.twimg.com/tweet_video_thumb/C0iX_9RWQAAV7qr.jpg" width="500"/>
<h1>Countdown until the new year!</h1>
<img src="
https://i.countdownmail.com/2zigqr.gif?end_date_time=%%=v(@enddate_f)=%%"
style="display:inline-block!important;" border="0" alt="countdownmail.com"></td></tr></table>

</details>

<br />

Thank you for your submission, Pato! If you’re looking to see this Scriptmas treat in a bit more context, head over to <ins>MCSnippets</ins> and take a look.


On the fifth day of Scriptmas, <ins>Erlend</ins> gave to us… An all in one Data View query to summarise each subscribers email engagement behaviour for the last 60 days! A great way to see across 5 golden data views!

<details> <summary>Click here to see the Day Five Script</summary>

SELECT 
    sub.SubscriberKey
 ,  COUNT(DISTINCT o.JobID) AS TotalOpens
 ,  COUNT(DISTINCT c.JobID) AS TotalClicks
 ,  COUNT(DISTINCT s.JobId) AS TotalSent
 ,  COUNT(DISTINCT b.JobId) AS TotalBounces
 ,  COUNT(DISTINCT CASE WHEN c.IsUnique = 'true' THEN o.JobID END) AS TotalUniqueOpens
 ,  COUNT(DISTINCT CASE WHEN c.IsUnique = 'true' THEN c.JobID END) AS TotalUniqueClicks
 ,  COUNT(DISTINCT CASE WHEN c.IsUnique = 'true' THEN b.JobId END) AS TotalUniqueBounces
 ,  COUNT(DISTINCT CASE WHEN o.EventDate > DateAdd(Day, -7, GetDate()) THEN o.JobID END) AS OpensLast7Days
 ,  COUNT(DISTINCT CASE WHEN c.EventDate > DateAdd(Day, -7, GetDate()) THEN c.JobID END) AS ClicksLast7Days
 ,  COUNT(DISTINCT CASE WHEN s.EventDate > DateAdd(Day, -7, GetDate()) THEN s.JobID END) AS SentLast7Days
 ,  COUNT(DISTINCT CASE WHEN b.EventDate > DateAdd(Day, -7, GetDate()) THEN b.JobID END) AS BouncesLast7Days
 ,  COUNT(DISTINCT CASE WHEN o.EventDate > DateAdd(Day, -30, GetDate()) THEN o.JobID END) AS OpensLast30Days
 ,  COUNT(DISTINCT CASE WHEN c.EventDate > DateAdd(Day, -30, GetDate()) THEN c.JobID END) AS ClicksLast30Days
 ,  COUNT(DISTINCT CASE WHEN s.EventDate > DateAdd(Day, -30, GetDate()) THEN s.JobID END) AS SentLast30Days
 ,  COUNT(DISTINCT CASE WHEN b.EventDate > DateAdd(Day, -30, GetDate()) THEN b.JobID END) AS BouncesLast30Days
 ,  COUNT(DISTINCT CASE WHEN o.EventDate > DateAdd(Day, -60, GetDate()) THEN o.JobID END) AS OpensLast60Days
 ,  COUNT(DISTINCT CASE WHEN c.EventDate > DateAdd(Day, -60, GetDate()) THEN c.JobID END) AS ClicksLast60Days
 ,  COUNT(DISTINCT CASE WHEN s.EventDate > DateAdd(Day, -60, GetDate()) THEN s.JobID END) AS SentLast60Days
 ,  COUNT(DISTINCT CASE WHEN b.EventDate > DateAdd(Day, -60, GetDate()) THEN b.JobID END) AS BouncesLast60Days
FROM _Subscribers AS sub
/*Join with all Opens a subscriber have done*/
/*NOTE: Opens may not be correct as some email provider like Apple is auto opening emails. Making this statistick flaed. Salesforce recomend to always use engagement data, like clicks instead.*/
    LEFT JOIN _Open AS o
    ON o.SubscriberKey = sub.SubscriberKey
/*Join with all Clicks a subscriber have done*/
    LEFT JOIN _Click AS c
    ON c.SubscriberKey = sub.SubscriberKey
/*Join with all Sent emails a subscriber recieved*/
    LEFT JOIN _Sent AS s
    ON s.SubscriberKey = sub.SubscriberKey
/*Join with all Bounc event a subscriber is linked to*/
    LEFT JOIN _Bounce AS b
    ON b.SubscriberKey = sub.SubscriberKey
GROUP BY sub.SubscriberKey

</details>

<br />

Thank you again Erlend for your Scriptmas treat! This is something many SFMC users will add to their toolbox I’m sure. Check back tomorrow to see what’s next this Scriptmas!


On the sixth day of Scriptmas, Salesforce Marketing Champion <ins>Lesley</ins> gave to us… The back end code to create a SFMC hosted and built rendition of hit internet game, Wordle - Using AMPscript! All you need to do is add your own front end & a Data Extension with your Wordle Words in to start your own version, with your own words.

<details> <summary>Click here to see the Day Six Script</summary>

%%[
var @rows, @row, @today, @word, @attempt, @j, @letterFromWord, @letterFromAttempt, @letterStatus, @status
var @jsonOutput, @letter1, @letter2, @letter3, @letter4, @letter5

set @today = Format(Now(), "MMMM dd, yyyy")
set @rows = LookupRows("Wordle Words","Date", @today)

/* Retrieve the 'attempt' form field value */
set @attempt = RequestParameter("attempt")

if rowcount(@rows) == 1 then
    set @row = row(@rows, 1) /* Get the first (and only) row */
    set @word = field(@row,"Word") /* Get word from row */

    /* Compare the word and the attempt */
    for @j = 1 to length(@word) do
        set @letterFromWord = substring(@word,@j,1)
        set @letterFromAttempt = substring(@attempt,@j,1)

        if @letterFromWord == @letterFromAttempt then
            set @status = 2
        elseif IndexOf(@word, @letterFromAttempt) > 0 then
            set @status = 1
        else
            set @status = 0
        endif

        /* Concatenate the status to the @letterStatus string */
        set @letterStatus = Concat(@letterStatus, @status)
    next @j

    /* Split the @letterStatus string into individual values */
    set @letter1 = substring(@letterStatus, 1, 1)
    set @letter2 = substring(@letterStatus, 2, 1)
    set @letter3 = substring(@letterStatus, 3, 1)
    set @letter4 = substring(@letterStatus, 4, 1)
    set @letter5 = substring(@letterStatus, 5, 1)

    /* Construct the JSON-like output */
    set @jsonOutput = Concat('{"letter1": ', @letter1, ', "letter2": ', @letter2, ', "letter3": ', @letter3, ', "letter4": ', @letter4, ', "letter5": ', @letter5, '}')
endif
]%%
%%=v(@jsonOutput)=%%

</details>

<br />

Thank you Lesley for this fun little script! Now to start thinking of as many 5 character Scriptmas themed words we can all think of… Check back tomorrow for more Scriptmas joy!


On the seventh day of Scriptmas, Marketing Champion <ins>Rodrigo</ins> gave to us… A neat little bit of HTML/CSS to encourage your Cloud Pages to get found by web crawlers and for better experiences when sharing the content on social media!

<details> <summary>Click here to see the Day Seven Script</summary>

<!-- SERP metadata -->
<title>AddYourTitle</title>
<meta name="description" content="AddYourDescription">

<!-- Facebook metadata -->
<link rel="image_src" href="YourImageLink.png" /> <!-- always use an absolute link -->
<meta property="og:type" content="website" />
<meta property="og:title" content="AddYourTitle" />
<meta property="og:description" content="AddYourDescription" />
<meta property="og:image" content="YourImageLink.png" /> <!-- always use an absolute link -->
<meta property="og:image:width" content="1200" /> <!-- By using this image size, you define both facebook and twitter large image card types -->
<meta property="og:image:height" content="630" />

<!-- Twitter Cards -->
<meta property="twitter:creator" content="@YourName" /> <!-- If you don't have a Twitter/X fanpage, you can delete this line of code -->
<meta property="twitter:url" content="https://www.website.com/" />
<meta property="twitter:card" content="summary_large_image" /> <!-- This is the large card version for Twitter Cards -->
<meta property="twitter:site" content="@YourCompanyName" /> <!-- If you don't have a Twitter/X fanpage, you can delete this line of code -->

<!-- Add alt txt for accesibility -->
<meta property="og:image:alt" content="Add your alternative text here" />
<meta name="twitter:image:alt" content="Add your alternative text here">

</details>

<br />

Thanks again Rodrigo for this helpful boilerplate to help manage user experience when interacting with your Cloud Pages! Make sure you check back tomorrow to see what our next Scriptmas helper has in store…


On the eighth day of Scriptmas, Salesforce Marketing Champion <ins>Corrina</ins> gave to us… A SQL query to help you find what emails a subscriber was really sent (if for some reason they don’t believe it). Subscribers like to get themselves on the Scriptmas naughty list!

<details> <summary>Click here to see the Day Eight Script</summary>

SELECT J.EmailName as 'EmailName', 
se.JobId, 
s.EmailAddress, 
se.SubscriberKey, 
se.EventDate 
from  _Sent se 
INNER JOIN ENT._Subscribers s 
ON se.SubscriberID = s.SubscriberID
INNER JOIN _Job J
ON se.JobID = J.JobID
WHERE se.EventDate >= dateadd(day, -20, getdate())
AND s.EmailAddress = 'address@email.com'

</details>

<br />

Thanks again for your Scriptmas treat, Corrina! Check back tomorrow to see what our Scriptmas friends have in store for us…


On the ninth day of Scriptmas, Erick gave to us… A joyful WSProxy Script to trigger an email send on a CloudPage using some nifty query string parameters. Just update the ExternalKey variable, pass an Email Address and SubscriberKey in query parameters – and you’re off to the races!

Want to make it a bit more magical? Update the script to grab a dynamic Triggered Send External Key so this can be used for multiple triggered sends.

<details> <summary>Click here to see the Day Nine Script</summary>

<script runat="server">
var prox = new Script.Util.WSProxy();

//Set TriggeredSend External Key
var tsExKey = 'ExternalKey'; 
//pass Email Address as query string parameter
var EmailAddress = Platform.Request.GetQueryStringParameter('email');
//pass Subscriber Key as query string parameter
var SubscriberKey = Platform.Request.GetQueryStringParameter('subscriberkey');

var tsDef = {
    TriggeredSendDefinition: {
        CustomerKey:  tsExKey
    },
    Subscribers: [{
        EmailAddress: EmailAddress,
        SubscriberKey: SubscriberKey
    }]
};

var res = prox.createItem('TriggeredSend', tsDef);  
</script>

</details>

<br />

Thank you again Erick for sharing this nifty little trick! We’re almost in the double digits of Scriptmas, make sure you check back tomorrow what’s in store for Day 10 of Scriptmas 2024.


On the tenth day of Scriptmas, Marketing Champion <ins>Rafal</ins> gave to us… A Server Side Javascript Function to calculate the time between two dates and easy to read for all of our Scriptmas helpers!

<details> <summary>Click here to see the Day Ten Script</summary>

function timeDifferenceForHumans(minuend, subtrahend) {
    // Make sure the minuend and subtrahend are numeric timestamps that can be parts of subtraction
    try {
        if (typeof minuend !== "number") {
            minuend = new Date(minuend).getTime();
        }
        if (typeof subtrahend !== "number") {
            subtrahend = new Date(subtrahend).getTime();
        }
    } catch (e) {
        return "timeDifference function: Unable to parse the provided dates";
    }

    // Calculate the time difference in milliseconds
    var timeDifference = Math.abs(minuend - subtrahend);

    // Calculate days, hours, minutes, and seconds
    var oneDay = 24 * 60 * 60 * 1000;
    var oneHour = 60 * 60 * 1000;
    var oneMinute = 60 * 1000;

    var days = Math.floor(timeDifference / oneDay);
    var hours = Math.floor((timeDifference % oneDay) / oneHour);
    var minutes = Math.floor((timeDifference % oneHour) / oneMinute);
    var seconds = Math.floor((timeDifference % oneMinute) / 1000);

    // Build strings in a format readable for humans
    var shortString = days + "d " + timePadding(hours) + "h " + timePadding(minutes) + "min " + timePadding(seconds) + "s";
    var variableString = '';

    // Days
    if (days == 1) {
        variableString += "1 day ";
    } else if (days > 1) {
        variableString += days + " days ";
    }

    // Hours
    if (hours == 1) {
        variableString += "1 hour ";
    } else if (hours > 1) {
        variableString += hours + " hours ";
    }

    // Minutes
    if (minutes == 1) {
        variableString += "1 minute ";
    } else if (minutes > 1) {
        variableString += minutes + " minutes ";
    }

    // Seconds
    if (seconds == 1) {
        variableString += "1 second";
    } else if (seconds > 1) {
        variableString += seconds + " seconds";
    }

    var object = {
        "days": days,
        "hours": hours,
        "minutes": minutes,
        "seconds": seconds,
        "variableString": variableString,
        "shortString": shortString
    };

    // Returned object
    return object;
}

// Helper function to pad single-digit numbers with a leading zero
function timePadding(num) {
    return (num < 10 ? "0" : "") + num;
}

</details>

<br />

Who’s going to be the first to use the function to count down to Christmas? Check back tomorrow for our Day 11 Scriptmas treat!


On the eleventh day of Scriptmas, <ins>Jake</ins> gave to us… A SQL & SSJS based solution to get everything you’d need to create an Open & Click Heatmap. This is a bit of a big one, so make sure you check out the full details from Jake below!

<details> <summary>Click here to see the Day Eleven Script</summary>

/* 
*
* Fill with Weekday and Hours DE SSJS Script 
*
*/
<script runat="server" language="JavaScript">
  Platform.Load("core", "1");
 
   var addToDEArray = [];


   var HeatMap_ClicksDE = DataExtension.Init('HeatMap_Clicks');
   var HeatMap_OpensDE = DataExtension.Init('HeatMap_Opens');
  
   for (var i = 0; i < 24; i++) {
       for (var x = 0; x < 7; x++) {
           if (x == 0) {
               var weekday = "Monday"
           } else if (x == 1) {
               var weekday = "Tuesday"
           } else if (x == 2) {
               var weekday = "Wednesday"
           } else if (x == 3) {
               var weekday = "Thursday"
           } else if (x == 4) {
               var weekday = "Friday"
           } else if (x == 5) {
               var weekday = "Saturday"
           } else if (x == 6) {
               var weekday = "Sunday"
           }
          
           var addObject = {
               Weekday: weekday,
               Hour: i
           }
          
           addToDEArray.push(addObject);
       }
   }
  
    try {
       HeatMap_ClicksDE.Rows.Add(addToDEArray);
       HeatMap_OpensDE.Rows.Add(addToDEArray);
   } catch (err) {
       Write(err);
   }


</script>

/*
*
* Open Rate SQL
*
*/

/* This query will provide you with the data necessary to make an Open/Click heat map for day of the week and time of day based on send time.   */


SELECT y.SendCount, y.Weekday, y.Hour,
   CASE
       WHEN x.OpenCount IS NOT NULL
           /* Get the open count based on 'total opens in a Weekday & Hour of Day bucket' divided by
              'total sends in a Weekday & Hour of Day bucket' */
           THEN (CONVERT(DECIMAL(9,4),x.OpenCount) / CONVERT(DECIMAL(9,4),y.SendCount))
       WHEN y.SendCount IS NOT NULL AND x.OpenCount IS NULL
           /* instead of returning null when the resulting OpenCount is 0, we want to set the rate to 0 */
           THEN 0.0000
       ELSE null
   END AS OpenRateByTime,
   CASE
       WHEN y.SendCount IS NOT NULL AND x.OpenCount IS NULL
           /* instead of returning null when the resulting OpenCount is 0, we want to set the open count to 0 */
           THEN 0
       WHEN y.SendCount IS NOT NULL AND x.OpenCount IS NOT NULL
           THEN x.OpenCount
       ELSE null
   END AS OpenCount
FROM (
   SELECT Count(*) AS SendCount,
   /* the DATENAME function allows us to group by things like 'Monday' and '4' aka 4am */
   DATENAME(WEEKDAY, EventDate) AS Weekday,
   DATEPART(HOUR, EventDate) AS Hour
   FROM _Sent
   GROUP BY DATENAME(WEEKDAY, EventDate), DATEPART(HOUR, EventDate)
) y
LEFT JOIN (
   /* this subquery is getting the accumulative Open Count for the Weekday and Hour of Day buckets */
   SELECT Count(*) AS OpenCount,
       a.Weekday,
       a.Hour
   FROM
       (
           /* this subquery gets all unique open data. you can always add a WHERE clause to limit
              the results to a shorter time period than the full past 6 months of data that Data Views retain */
           SELECT s.JobID, s.BatchID, o.IsUnique, s.EventDate,
           DATENAME(WEEKDAY, s.EventDate) AS Weekday,
           DATEPART(HOUR, s.EventDate) AS Hour
           FROM _Sent s
           INNER JOIN _Open o
               ON s.JobID = o.JobID AND s.BatchID = o.BatchID AND s.SubscriberKey = o.SubscriberKey
           WHERE o.IsUnique = 1
       ) a
   /* groups by weekday and hour so we can now grab the open counts just like the send counts before */
   GROUP BY a.Weekday, a.Hour
) x
ON x.Weekday = y.Weekday AND x.Hour = y.Hour

/*
*
* Click Rate SQL
*
*/

SELECT y.SendCount, y.Weekday, y.Hour,
   CASE
       WHEN x.ClickCount IS NOT NULL
           THEN (CONVERT(DECIMAL(9,4),x.ClickCount) / CONVERT(DECIMAL(9,4),y.SendCount))
       WHEN y.SendCount IS NOT NULL AND x.ClickCount IS NULL
           THEN 0.0000
       ELSE null
   END AS ClickRateByTime,
   CASE
       WHEN y.SendCount IS NOT NULL AND x.ClickCount IS NULL
           THEN 0
       WHEN y.SendCount IS NOT NULL AND x.ClickCount IS NOT NULL
           THEN x.ClickCount
       ELSE null
   END AS ClickCount
FROM (
   SELECT Count(*) AS SendCount,
   DATENAME(WEEKDAY, EventDate) AS Weekday,
   DATEPART(HOUR, EventDate) AS Hour
   FROM _Sent
   GROUP BY DATENAME(WEEKDAY, EventDate), DATEPART(HOUR, EventDate)
) y
LEFT JOIN (
   SELECT Count(*) AS ClickCount,
       a.Weekday,
       a.Hour
   FROM
       (
           SELECT s.JobID, s.BatchID, c.IsUnique, s.EventDate,
           DATENAME(WEEKDAY, s.EventDate) AS Weekday,
           DATEPART(HOUR, s.EventDate) AS Hour
           FROM _Sent s
           INNER JOIN _Click c
               ON s.JobID = c.JobID AND s.BatchID = c.BatchID AND s.SubscriberKey = c.SubscriberKey
           WHERE c.IsUnique = 1
       ) a
   GROUP BY a.Weekday, a.Hour
) x
ON x.Weekday = y.Weekday AND x.Hour = y.Hour

</details> <details> <summary>Full details here</summary> This query will provide you with the data necessary to make an Open or Click heat map by day of the week and time of day based on send time.

The open and click rates are calculated by grouping all individual unique open and click events by the email job that those open and clicks are attributed to. So, the data does not necessarily represent when users are opening or clicking, but rather the amount of users that are opening or clicking from emails sent in a given hour and time of the week.

There’s a good chunk of subqueries going on, so I’ll break down what’s going on at a high level and leave some comments in the query for more context.

In the FROM clause we reference the “_Sent” Data View and group by Weekday and Hour of the day. This breaks up our sends into buckets of Time of Day and Day of Week. We have to do this mainly because we have to get the total send counts for each of those buckets.

We reference “_Sent” Data View again in our LEFT JOIN subquery’s FROM clause. This is so we can get the total number of opens/clicks that are attributed to a send time hour & weekday bucket.

Now that we have our Send totals and Open/Click totals attributed to hour and weekday buckets, we can do some math in our SELECT statement to get our “OpenRateByTime” metric.

[PREREQUISITE]: you will want to fill your Data Extension with values for Weekday and Hour of Day. Here are the Data Extension asset requirements you’ll need as well as an SSJS script you can run to fill this Data Extension will all 24 Hours and all 7 Days. The script assumes you made 2 Data Extensions with the external keys of “HeatMap_Opens” and “HeatMap_Clicks”.

Field Name - Field Type - PK/Nullable

Weekday - Text - Primary Key Hour - Text - Primary Key SendCount - Number - Nullable OpenCount - Number - Nullable OpenRateByTime - Date - Nullable

Run the SSJS script and then run your SQL query and you will have heat map data in your Data Extension!

You can then create a UI of some sort that can display the results of this query or even export the results in your Data Extension and create a Pivot Table in Excel or Google Sheets.

Disclaimer: I have only run this on relatively small SFMC accounts. If scaling is an issue, note that you may have to break some of the subqueries up into separate staging queries/Data Extensions.

Merry Scriptmas! Reach out on LinkedIn for any questions and I’m happy to help!

</details> <br />

Thanks Jake for this Scriptmas treat! Check back tomorrow to see what our final Scriptmas gift may be…


On the twelfth day of Scriptmas, Marketing Champion <ins>Duc</ins> gave to us… A perfect ASCII art Christmas Tree, built using SSJS and HTML.

<details> <summary>Check back to see the Day Twelve Script</summary>

<html lang="en">
   <meta charset="utf-8" />
</html>
<body style="font-family: monospace">
<script runat="server">
Platform.Load("Core", "1.1.1");
var debugging = false;
var heightOfTree = 20; // Change the height here
var leaf = '*'; // Change the leaf symbol here
var christmasTree = createChristmasTree(heightOfTree, leaf, debugging);

Write(christmasTree);

function createChristmasTree(height, leaf, debugging) {
  /* USAGES 
    para 1: height is the height of tree
    para 2: leaf symbol
    para 3: debugging
  
  */
  var tree = '';
  for (var i= 0; i < height; i++) {
      var stars = '';
      var spaces = '';

      var quantityOfStars = i*2 + 1;

      if(debugging){Write(quantityOfStars + '<br>');} // DEBUGGING
      for (var j=0; j < quantityOfStars; j++){
        stars += leaf;
      }

      var quantityOfSpaces = height -i -1;

      if(debugging){Write(quantityOfSpaces + '<br><br>');} // DEBUGGING
      for (var e=0; e < quantityOfSpaces; e++){
        spaces += '&nbsp;';
      }
      
      tree += spaces + stars +"<br>"
  }

  // Add the tree trunk
  var maxElement = height*2 + 1;
  var trunkSpaces = '';
  for (i = 0; i < (maxElement-3)/2; i++) {
      trunkSpaces += '&nbsp;';
  }
  trunkSpaces += '| |<br>';
  for (i = 0; i < height / 4; i++) { // Adjust trunk height based on the tree height
      tree += trunkSpaces;
  }


    return tree;
}

// Add Merry Xmas line
var line;
var line1 = " __  __                       __  __                    _ ";
var line2 = "|  \\/  | ___ _ __ _ __ _   _  \\ \\/ /_ __ ___   __ _ ___| |";
var line3 = "| |\\/| |/ _ \\ '__| '__| | | |  \\  /| '_ ` _ \\ / _` / __| |";
var line4 = "| |  | |  __/ |  | |  | |_| |  /  \\| | | | | | (_| \\__ \\_|";
var line5 = "|_|  |_|\\___|_|  |_|   \\__, | /_/\\_\\_| |_| |_|\\__,_|___(_)";
var line6 = "                       |___/                              ";
  line = "<br>" + line1 +"<br>" + line2 +"<br>" + line3 +"<br>" + line4 +"<br>" + line5 +"<br>" + line6 +"<br>";
  line = line.replace(/ /g, '&nbsp;');
Write(line);
</script>
</div>
</body>

</details> <br />

Thank you Duc for the Scriptmas tree! Everyone, make sure to send us screenshots of your very own custom Scriptmas tree, courtesy of Duc!


Thank you once again for all of our Scriptmas contributors for Scriptmas 2023!

Read more
HowToSFMC
Published 10/30/2023
Community
HowToScream - Community SFMC Horror Stories

As the Winter ‘24 release finishes casting its influence on the world of Salesforce Marketing Cloud, the witching hour is rapidly moving towards us. Now, the only shadows that decorate our Marketing Automation platform are those cast from the jack-o-lanterns carved into the shapes of everyone’s favourite mascots. Whether you’ve got a Wicked Astro, an Evil Einstein or Creepy Codey in your pumpkin this year, we’ve got tales from our community to share. Brace yourself, get your comforting Trailblazer hoodie at the ready and scroll through the cursed texts of their deepest, darkest and most terrifying SFMC stories.

There’s little that births fear in the heart of an SFMC user than the thought of a quick fix. A simple concoction of actions to remedy the ills of an org. Whether it’s a data extension that needs some new fields or a journey that needs a new version… Here are some examples where that simple, quick fix led to a little more than a ghostly pause. It led to despair and dismay for users and customers alike.

Sometimes when you hear a knocking in your org, it’s better for you to sit tight and wait for it to just go away on its own. When you go looking for ghosts, sometimes ghosts find you as Lesley found out…

It was a dark and stormy afternoon when I paused a crucial automation for a “quick fix” before darting off to my attic to see what was making a suspicious noise. It was not until a week later when asked “why are these essential notification not going out?” that I realized I must retreat to that attic for the rest of my life to live in the shadows in shame.

But, just because you didn’t go looking for ghosts, it doesn’t mean the ghosts aren’t looking for you. The ghost may have been in the platform all along.

I’ll always remember the moment early in our SFMC usage when I learned that if a Triggered Send hit any error, it just stopped - for EVERYONE, not just the problem record - without any sort of notification of failure. I can still taste the bile that rose as I realized that critical emails had been paused for weeks.

Even if the ghosts don’t find you, time ticks for us all and whether we like it or not, time will always get us. Sometimes, times and date maths are the scariest parts of all.

I created coupon codes with 23:59 UTC-8 expiration dates; the SFMC import converted the code expirations to UTC-6. The email’s AMPscript had FormatDate(@dateString,“M”) with no conversion back to UTC-8, which meant the email showed the expiration as the next day.

The biggest challenge with ghosts is they can be incredibly tricky to see, you may think you’ve done the right thing but they can still appear later on or even straight away and leave you none the wiser. Duc has expressed their fears of not always knowing whether the job that was done was the only job that was done or whether the ghosts in the platform were having a little fun at their expense.

Refresh triggered sends (pause, publish, start). Did it include all the inactive ones…

As with everything you can’t see, you can never know what is happening in the background. Is there a queue of 1 thing, 20 things or 100 things in front of your request. Is the platform just cackling away in the background? Is that one of the things causing you a delay?

“Run Once" to perform Automation activities with clients in the test session. The automation was put in queue…

When traversing the hallowed fields of Marketing Cloud, one must always watch their step. One wrong move can turn a normal day into a weeks, months or even years long nightmare. Remember folks, the spirits behind Salesforce Marketing Cloud are old, ECMAScript 3 old in fact, and these spirits can be vindictive. They want nothing more than to see you slip.

Like a responsible developer, Aman maintains documentation of the history of their SFMC org, but, some words should be shared only through ephemeral means or locked away securely and away from prying eyes.

I just found our client ID, and Secret is available openly on Google. Good, they don’t have our login credentials

This anonymous community member summoned upon themselves the woe of misplaced contacts, sending them through the rabbit hole, never to be found again.

A user loaded a big chunk of our opted-in Contacts dataset into a new Data Extension, with smartly designed segmentation and personalisation flags to run a fancy new journey. Then they selected the wrong Subscriber Key for the Contact relationship, and hit Send…

Never trust phantom data. Especially when it comes to sending test emails as Stephan found. Nobody knows what lurks in the shadows and unless you’ve been and explored, there may be ghouls and zombies waiting for you.

We sent a test email to random generated supposedly non-existing e-mail addresses. We had clicks and opens!

Sometimes you don’t have to make a misstep to be caught out by the shifting tides within SFMC, as the tool moves, evolves and creates new capabilities it can be all too easy to be caught out by some form of new hex.

The moment you realised missing an enhancement to lookup lead records in CRM before creating new ones and your SFMC landing pages pumping duplicates into salesforce CRM over an year!

The shifting tides aren’t always on the surface, the undercurrent can look very different from the surface. What was a good idea in years past may no longer be that same good idea. What worked before may not work now. Exercise caution, especially when others have elected to freeze and stay still.

I accidentally changed the wrong FTP user password in the enterprise business unit and it wouldn’t let me change it back because it didn’t meet the latest password security baseline baked into SFMC. It was the week between Christmas and New Year’s Eve during a tech freeze and no one was around to help me update passwords in the systems dropping off data so most of file drops needed to power customer journeys failed for 8-10 days

Be aware of mimics. What may look harmless and safe to play with, doesn’t necessarily mean it is

Delete an original data extension instead of a test DE in an accident…

You never know what’s lurking around the corner…

We all need to know the best tools for the job we need to do and we all need to use them wisely. Whether you’re looking to hunt vampires or crush bugs, getting the right tool can be the difference between success and failure. But, sometimes we have to make do with the only tools we have.

Billy found this out first hand when trying to overcome the creatures of SSJS in SFMC.

Not feeling as comfortable with SSJS, so I build out the original solution in a cloudpage just so I can get a feeling of any errors as opposed to putting it all in the SSJS and then running it and hoping that it won’t error. There as to be a better way!

Sometimes the right tool for the job doesn’t exist in our own tool kits. We should look at all of the tools available to us and perhaps the right tool is in the software next door. But if you use the tool badly, the issue may persist or even get worse as Cam alludes to…

Once upon a time there was a business who self implemented Marketing Cloud and Marketing Cloud Connect. Their Salesforce Org was full of duplicate records due to bad merge rules, and rather than cleaning up the source data, they built a SQL solution in Marketing Cloud to dedupe records and assign them a new subscriber key - which was a random number generated in a SQL query

Whilst some tools may not do the job and some tools may be supported a little further away from home than we’d typically be used to, sometimes the right tool is in front of our eyes within SFMC. These zombie links could have remained alive rather than undead in Content Builder.

We used external social media icon URLs for every MC email template. The URLs are broken after 3 years…

Even if we’re using the right tool, things can still go wrong in the shadows. Things can still not go according to plan as highlighted in this warning for everyone who activates journeys. Not all that appears fine is always fine.

Journey ran perfectly when I checked on the UI. Recipients didn’t receive the EDM…

The wisened veteran Pato was seemingly using the right tool at one point, but in an effort to enhance things may have instead opened a crypt of configuration requiring 120 re-integrations!

One hundred and twenty BUs, I decided to turn on multi-org. One by one all users had to be re-integrated again.

At times in our lives in SFMC, we need to communicate with the other side, whether that is customers, clients or contacts. These communications can sometimes be fraught with danger, with or without a ouija board it’s easy for requests or requirements to go astray.

Unexpected voices bring about a fear and sense of Halloween dread unlike any other. Seeing an unfamiliar name, sensing an unfamiliar presence can bring about a swift change of priorities, especially when it’s throwing the bile of a bad customer experience into the world as Pep once found out.

Yes, it’s me… WHAT???.. call center says customers asking us to stop sending duplicate emails?.. MORE THAN 200 TO EACH CUSTOMER???

Lesley has seen a fair share of horror stories, but nothing could have prepared for the sheer pain of logging in to find someone else lurking in the shadows and releasing wickedness into the world!

It was a seemingly normal day when my client allowed another contractor into their org. They created an all subscriber data extension and made it testable.

Sometimes the communications are all hidden away where mortal eyes cannot see, the beasts of the back end can play havoc with your finest technology work and just rear their ugly heads without warning as Duc found out.

Script activity, SQL query activity worked perfectly with test records. Runtime error occurred when running with real data volume…

But the worst can sometimes be what is not said. Rafał shared this tale of something going wrong, something being acknowledged as wrong and everyone being left in the darkness knowing not what happened, nor what to do. The anticipation of finding out whether something was getting better, getting fixed or even getting worse playing on the mind is a fear that will keep even the most stalwart of us all on edge.

In the shadowed realm of the server stack, a ghastly silence stretched for 12 hours, ensnaring a team in a maddening abyss of worklessness and desolation. Like characters in a cursed tale, they languished, their souls tormented by the absence of updates on the incident report and halted communication

At times communication can lead to a sacrifice that you may not be ready to make. It may not be avoidable and it

I cc’d my boss on the SFMC email blast today. However, for some reason, I can’t log in now.

After seeing all of these horror stories, we should remember that sometimes the biggest reason to scream in fear is a little closer to home. It’s not the ghouls, ghosts or gremlins that live in the SFMC platform that can strike fear into our hearts. Sometimes it’s in the friends we meet and the people we collaborate with where the fear can come. We know everyone involved means the best, but sometimes it cannot be helped that these scary challenges will come across us. Here are a few horror stories that have led to some of our community having to wrap up extra safe in their Salesforce swag.

The client asked to update an EDM. Not aware where it is but only the EDM screenshot is given…

The client asked for keeping their contact counts not overage, but… They ingest and consume new data every day

The client was not happy with the cost. Now they are starting to compare other competitors with SFMC.

What are your scariest SFMC tales? What has spooked you out or made you scared to turn out the lights for bed? Come and join us on Slack and share some of the things that taught you HowToScream.

Read more
HowToSFMC Community
Published 10/15/2023
Leadership
To Lead or Not To Lead

Introduction:

Many of us came into the Salesforce Marketing Cloud ecosystem the same way others came into the general Salesforce space, by chance.

My career began as a web developer in 2010 when many companies were making the transition from traditional print marketing to digital engagement (primarily through email and web). My manager at the time attended Dreamforce and purchased Salesforce Marketing Cloud. The onboarding process required collaboration with other teams within the company to add DNS configurations, internal email routing, SSL certificate generation, and other topics that were outside of the skillsets of a normal marketing team. Since I was already managing our existing ESPs and developing and deploying websites for the team, my skills were best aligned with the onboarding process and configuration and implementation needs of the platform beyond onboarding.

Emergence of the Technical Marketer:

Today’s MarTech landscape requires more than just an ESP and a handful of customer data points. Companies need the ability to respond to customers in real-time, at the moment it matters most, and with relevant content. Mix in the latest innovations in AI and it can seem like an impossible task for small and often under-funded marketing teams.

Modern marketing requires a deeply integrated marketing stack that not only ties customer data to your marketing automation platform of choice but surfaces it in a way that is meaningful for automated segmentation, offer targeting, proactive customer service and support.

Here’s an example of what a Modern Marketing Architecture might look like today:

With new products like Data Cloud and the latest Salesforce Einstein innovations, marketing teams will be able to take on more initiatives. Once they work with internal IT and Big Data teams to integrate the right data and lay the foundation for success by establishing their Customer 360 data models for segmentation and targeting, they’ll be able to pivot quickly as the needs of their organizations and the AI-driven marketing landscape evolves.

Transition to Management:

Salesforce Marketing Cloud has historically had a steeper learning curve, but it also creates opportunities for enterprise marketing teams to build just about anything they can imagine. The flexibility of the platform and ability to configure it, integrate with it, and develop in it has led to a lot of highly sophisticated marketing solutions.

Those who advanced quickly in their Marketing Cloud careers understood the platform’s potential. They developed solutions that had a noticeable impact on revenue streams and influenced customer behaviors for large brands. Their roles often existed on the business side, not within IT, but they had a direct connection with IT teams (network/infrastructure, security, integration, and mobile app teams to name a few). Highly technical marketers collaborated with these teams regularly, often raising the question of where this marketing role should live within the organization.

As the value of Salesforce Marketing Cloud gains visibility within enterprise companies, it begins to attract attention from other departments who want to utilize marketing automation to support their initiatives.

What typically begins as a marketing automation team of one or two people, grows over time, and requires someone to take the lead and help scale the use of the platform across the enterprise. In addition, they must also manage daily marketing operations to maintain, monitor, and troubleshoot live automations and integrations.

I’ve had the privilege of working with some amazing people in my career and have worked as both an individual contributor and manager. Leading a team is a big responsibility, but having the opportunity to teach and help others grow their career is really an honor. It’s an interesting position to be in when you train others up and encourage their growth. You must balance the feelings of pride from watching them reach their potential with the realization that their success means you must start over again by hiring, training, and leading new team member(s) on their career path. I still love hearing from many of them and having an opportunity to catch up on their latest achievements (personal and professional) and provide guidance when they seek it.

Building a Team:

As we all know, technology changes at a rapid pace. It’s important to learn how to balance the ability to lead and manage your team while deciding how much of an individual contributor role you can play (if any).

Knowing what roles your team will need to support enterprise-scale marketing automations is also key. There can (and often will) be team members that share multiple roles, but as the team grows this can be a guideline for how individual roles might become more focused to grow with the organization:

!Marketing Automation Team

Management and Servant Leadership:

When transitioning from an individual contributor to a supervisor or management role, are some new skills you will need to learn and implement. These are a few things I’ve learned over time that will hopefully help others seeking a management role:

  • Don’t be afraid to surround yourself with people who either know more than you about a specific topic or can challenge you in ways you might not challenge yourself.
  • Learn to delegate! This was so hard to do early in my management career. But once I did, it was amazing how much we could accomplish as a team.
  • Be a leader not a boss. To be an effective leader, you must have a genuine desire to grow others and champion their success.
  • Actively seek opportunities that allow your team to grow. This could be through experiences like on-site retail visits to immerse them in the experience of your customers or it might be an opportunity to lead a project that gives them more exposure to different teams across an organization.
  • Allow your team to present their own work (weekly standups, monthly product demos, quarterly presentations).
  • Recognize wins and learn from failures as a team (be willing to accept responsibility as their leader when things go wrong, but align that with retrospectives and constructive feedback given privately).
  • Reserve 1-1 time for team members each week to clear obstacles. This also gives them a space to communicate other challenges and accomplishments.
  • Allow the team to work through problems without running to their rescue. They can’t learn, if they aren’t allowed to work through a journey decision flow, troubleshoot errors, weigh pros and cons of implementing campaigns in different ways, or anticipate blockers, etc.

There are also some things that might be unexpected. A few things you may want to consider about the transition from an individual contributor to a management role:

  • It’s difficult to lead a team when you don’t know how to steer the ship. Experience comes from exposure to a variety of scenarios that may seem unpredictable in the moment. Over time, those are the experiences that form your ability to think ahead and plan for points of friction, scalability considerations, etc. that will help lead your team in the right direction.
  • For many, imposter syndrome creeps back in at a swift pace when you’re no longer “hands on” and actively developing on the platform. Plan for this in advance, so you know how to address it head on if it happens.
  • Keep an eye on what’s trending. This requires even more attention with all the rapid changes with AI.
  • Multiple meetings, reports, and other activities will compete for your time in management. You will need to pro-actively schedule time on your calendar to allow for that pulse-check and research on the latest innovations that could impact your team and organization.

Return to Individual Contributor:

Because managing teams leaves less time for implementation, many leaders find themselves wondering if they’re on the right path. This is a trend I’ve seen a lot of technical marketers and developers choosing over the last couple years. I have also seen many alternate from an IC to leadership, back to IC and then return to leadership again with a renewed appreciation and deeper understanding of well architected solutions.

!Manager Flow

While my career change was primarily motivated by the needs of my family, there was also a desire to return to a role where I could avoid losing the skills I had worked so long to build. This is a concern for many who lead technical teams because of the rapidly changing landscape and the fear of becoming irrelevant.

Many companies don’t have a clear path for developers and other technical roles to advance beyond a senior role, so management is the only path forward. Solution and Technical Architecture roles are a natural progression for these roles and are common in the consultancy and implementation partner space but are not seen as frequently in enterprise organizations.

While the best career choices are unique to the individual and circumstances, it’s important to weigh the pros and cons to determine the best career path for you.

Here are a few additional resources for those considering management or transitioning back to an individual contributor role from a leadership position:

<br />

About Natasha:

Title: Salesforce Marketing Cloud Architect

Company: Rosetree Solutions (https://rosetreesolutions.com/)

Bio: Natasha has managed and trained teams on Marketing Cloud and led the technical implementation of numerous customer-facing campaigns that span loyalty, sales, retail, mobile app, and more. She began working as a web developer in 2011 with a dual client/project management role. In 2013, she transitioned to a technical marketer role that included web development, marketing automation, integration, and project management. She has architected automated campaign solutions that deliver messaging to customers in real-time utilizing MuleSoft APIs, point-of-sale integrations, Salesforce Marketing Cloud, Salesforce Data Cloud, and more.

Read more
Natasha Martin
Published 10/01/2023
Winter '24 Release Review

As we brace ourselves for the festive season, Salesforce has dropped its latest set of release notes just a few short weeks prior to them going live in some instances. So, whether you’re participating in HowToScream or wondering if another year of Scriptmas (Check out 2020, 2021 and 2022) is coming, there’s some reading you may need to do beforehand to make sure you don’t get caught out!

With the broad range of AI features being added to SFMC in this release, it’s worth bringing them together at the beginning. After all, Dreamforce this year was all about Data and AI so it’s clearly high on the Salesforce agenda. It should also save you the hassle of trying to work out which AI features are related to which general topic as it appears that the same feature is being announced multiple times…

!alt_text Same feature, 2 different categories (and the second one is in the Winter 24 release, just labelled incorrectly) so if you find yourself wondering why the navigation is strange in the release notes, this is possibly why. But, I digress…

If you head to Einstein Copy Insights, you’ll be able to configure your Einstein Generative AI Personality. It’ll come with 2 out of the box configured personalities or you can configure up to 10 of your own. Perhaps you’ll be able to switch between them and test the outcomes to see which is resonating best with your customers and continue to test and challenge to get to your robust Einstein Brand Personality.

Salesforce have split these up, but based on the descriptions they’re very similar features and capabilities. You can jump into Content Builder or Einstein Copy Insights to generate body copy and subject lines for your emails based on your Einstein Personality set up in the previous step. This is all covered by the Einstein Trust Layer so for users who are concerned about proprietary data feeding into a LLM, this should prevent that. It’ll be interesting to see how effective Einstein Generative AI is in regards to content creation en masse. Some key things I’d like to see include Einstein dynamic content selection or even feeding variations of a prompt programmatically in line within an email rather than manually copying and pasting into Dynamic Content blocks.

This feels a little out of place in terms of a Salesforce official release note whereby there is a new AppExchange App for Typeface “_The generative AI app to supercharge personalized content creation for work” _to add a new Content Builder Block. I’m not sure we’ve seen many releases call out an AppExchange extension, I certainly don’t recall any release notes calling out Sales Wings or DESelect. But, Typeface currently has a waitlist so if you’re not already using the platform, this is unlikely to change your day to day!

There’s a few updates in this release, much like the Typeface release for Content Builder that are definitely more aligned to the wider Marketing Cloud product umbrella rather than the core SFMC platform. Rather than awkwardly shoehorning them into vaguely similar topics, let’s group them here. If you’re a user of Marketing Cloud Personalisation/Salesforce Personalisation/Interaction Studio or whatever the name of the week is, there’s some new consumption reports. If you’re one of those who has bought Data Cloud for Marketing, new segment intelligence reports are available in the Segment Intelligence tab. This should help optimise segments based on common activation channels, including within SFMC, some paid media offerings and Commerce Cloud.

!alt_text

In what should be a surprise to absolutely zero people at this point, the retirement of Classic Content (which was officially completed in Spring ‘23) reaches the final stage: Destroying the evidence. Classic Content is no longer officially supported, the documentation is to be removed. That said, the release note does include some Classic Content links so perhaps the saga of the retired veteran not going down without a fight continues? Just make sure if you have anything that is still being maintained on classic content that you don’t try to change it without having a Content Builder asset available (Those automation Email Sends and Triggered sends with Classic Content need Content Builder assets!). Elsewhere in messaging, are you using Email Archiving? This release will help you to monitor and resolve archiving issues proactively rather than reactively. If you’re looking to further capitalise on the capabilities of WhatsApp messaging, you will be able to include your favourite SFMC Memes and other (probably more relevant to your business use case) media in WhatsApp messaging.

A couple of what some would say are long overdue updates in Content Builder & Cloud Pages this time around. The roll out of recycle bins in SFMC continues with Cloud Pages getting a recycle bin. It ticks a few boxes where content can get deleted by accident and being able to bring it back is definitely a welcome addition. The documentation for this release doesn’t specify whether it’s just the content behind the Cloud Page that gets retained or whether the whole “Page” including all of the configuration in terms of the URLs etc. are retained in a recycle bin. But, if someone accidentally deletes a preference centre it would be nice to be able to just click a restore button and have everything continue working without having some kind of additional workarounds needed. Emails built within Content Builder are getting an overdue accessibility update. The introduction of a new page tree format which makes it easier to move content blocks around when the user interface for dragging and dropping can be a little tricky at times. One of the key things is that it’s screen reader and keyboard accessible, game changing for those users who may be dependent upon these technologies. It’s definitely long overdue for there to be official mechanisms to interact with the platform for these purposes. Looking forward to this being rolled out to Cloud Pages in the future once any quirks are identified.

The recycle bin roll out continues with Data Extensions getting recycle bin access. It’s often far too easy to delete a Data Extension. With there being no automated dependency mapping when you hit the far too inviting button that says “Hey, this Data Extension is targeted by XYZ Query and is included in a LOOKUPROWS function in these emails” unintended outcomes may abound. It’s no wonder tools like Stashr are becoming an important tool for Marketing Cloud ownership. Data Extension Retention Management gets some helpful new features in this release. Starting off with a couple of fields being added to Contact Builder to highlight the current retention settings and the “Date data extensions were deleted”. I would presume this is the date the rows from a Data Extension were deleted, after all if the DE were deleted it wouldn’t be in the list? It’s unclear how this would work with items like Row Based Retention. A specific note for the Salesforce Product Team: Please fix the known issue around DataExtension SOAP API not respecting the content of RowBasedRetention on the SOAP API as highlighted here. If you make the experience of managing retention troublesome, you’ll not get people using it.

Process Builder is being retired, which will impact anyone using Distributed Marketing and Marketing Cloud Connect. There should be no impact to users beyond upgrading the Distributed Marketing package which will migrate everything to record-triggered flows or journeys that use an object get stopped/published will migrate that object. Should be relatively pain free, but keep an eye if you have anything using Process Builder. Distributed Marketing gets a couple of new features including adding attachments to emails from a set of files identified as well as being able to approve multiple emails at once rather than one at a time. Should help streamline some user flows and reduce user friction. Just make sure to be mindful of the Super Message impact if this is something you’re going to roll out to a wide audience! Custom Personalisation Interactions can now be managed at a template level **or **at an all template level, previously it was only available at an all template level. A little bit of granularity makes a big difference, especially when it comes to personalising experiences in email.

The Journey Builder system optimisation dashboard appears to be coming out of open beta in this release. Right now, you can see items that Salesforce have determined are inefficient for Journey Builder and you’re informed how to optimise those design decisions for the platform. It’s key to keep in mind that just because something is better for the Journey Builder platform, doesn’t mean it’s ideal for a business and their ways of working. As it stands right now, the dashboard will make recommendations but still allow journeys to continue to run. But, be cognisant that this may change. If Salesforce is able to tell you something is going to have a negative impact on back end systems, that’s only a few short steps from blocking it from being activated. That said, this does include the option to Adjust System Priority to mean that users are able to prioritise some Journeys relative to others. There may be some journeys that need to happen before a certain time of day each day and contention means that sometimes it overruns. Take a look and see what you find out about your journeys! These optimisation highlights will be present within journeys as well as within the dashboard and will feature links to relevant documentation. This is a clear drive towards making users more aware and more informed about the decisioning they make and its impact on the platform. But, these are still only recommendations, you will still be able to activate journeys if needed with these less than optimal platform configurations. You’ll be able to stop multiple journeys through the journeys dashboard. For times where you need to stop communications to customers for whatever reason, whether it’s an internal or external event. The last thing anyone needs is to need to spend hours going through each journey one by one to stop journeys. Hopefully this rolls out to Journey Pausing as well in the future as some of the key requirements for actioning a stop on multiple journeys often align with the need to temporarily pause communications. Journey Builder Event API gets some volume increases, now allowing developers to inject up to 100 contacts per call rather than the one by one method currently available. You’ll be able to easily leverage commercial and transactional messaging within a single journey which could help users reduce the number of journeys needing to be actively monitored. If you’re using Salesforce Data Entry events within your Journeys and there’s that request to keep the object owner included in the communications, there is now an option to introduce the Object Owner as a recipient when certain events occur on the Task, Case, CaseComment, Lead, and Opportunity objects.

With the shuffling of release notes in this compared to the source, the analytics releases are (is) pretty small (it’s padded out by Einstein and the Data Cloud items mentioned above). But, it’s not necessarily small in impact. With Google sunsetting GA360 Universal Analytics back in July this year and the required migration to GA4, it’s good to see this feature officially rolling out. So, if you’re using GA4 you can get back to creating audiences and feeding them into the Salesforce Marketing Cloud for journey builder activities.

Much of this centres around file transfers and data imports to SFMC from external systems with changes for FTP and External data sources. This release is expected to introduce a throttle on FTP File Uploads per Session. There’s no information around what the throttling is beyond that it has a very high threshold so most users won’t be impacted. But, specifically it’s missing details around whether this is a limit around file volume, file sizes and data volumes or whatever mechanism you may use to threshold against this. If you move a lot of data into SFMC through FTP File Uploads, this may be something to consider for you. The File Transfer API has had official documentation published. If you’re using that, you can now see how Salesforce officially expects it to be used. Key thing is that now that this is documented you should find it easier to get support from Salesforce for this if things go wrong. The documentation is available now, before the release goes live so head [here](https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/automation-studio-api.html Leverage Pre-signed URLs to perform imports without needing to configure file transfer locations. This should remove some of the administrative work involved in loading data to SFMC from external sources. You can provision Pre-signed URLs across most major cloud platforms, so if you’re going to make use of this feature check your cloud storage platform and see how. Race Condition Error Messaging, for those who import high volumes of Subscriber data concurrently, the current experience doesn’t necessarily give you the information you need to remedy the problem. By introducing these race condition errors and continuing to load the data from the import file you should get some extra insight to rectify any activities contributing to the condition.

Package Manager gets the majority of new items included in this element of the release, some new default packages and a few new features. If you’re in Health & Life Services or Financial Services, there’s some default packages included now to help you get off the ground. The release also includes some new objects, including Einstein STO, Attribute Groups and WhatsApp activities, so for those who are deploying between business units or orgs this just adds a little more capability. For those who regularly deploy packages, the Package Manager interface will also allow you to filter by deployment status. Some quality of life updates which are not to be sniffed at. If you’re an AWS S3 user, you’re able to make use of S3 Transfer Acceleration with SFMC as part of this release. Amazon claims between a 50%-500% speed increase for large files over long distances when using this, so definitely one to explore if you’re in the Amazon ecosystem for data storage. Data Extension Storage reports will be made available to child BUs as well as the parent BU. If you’re a user of the Data Extension Storage reporting capabilities within SFMC this may be useful to add to your arsenal of reporting and platform management. Finally, the Marketing Cloud Admin role by default will now include all of the permissions needed for Automation Studio.

With all of the messaging from Salesforce around Generative AI and Einstein it’s no wonder that the most “game changing” content of this release is in that arena. The release contains a lot of iterative changes and more roll outs of previously released capability than exciting new features. I’m not sure that documentation of an existing capability counts as a new release, albeit worthwhile pointing out that this documentation now exists is great. But, announcing documentation months after a feature has been made available makes the releases seem half complete. If, as a user when a release is made it’s not possible to read how it works, what the key impacts are then it’s difficult to effectively engage with new functionality and features. Users want to adopt new things. I truly hope the documentation for all of the Generative AI and Einstein capabilities in this release are up to scratch for everyone involved. Whether they’re a user, admin or developer if the documentation isn’t there, Einstein will get put in the pile of toys people don’t understand how to use and the effort from Salesforce ends up only being part realised.

Read more
Jason Cort
Published 09/15/2023
Leadership
Creating Culture in a decentralized world

Before the 2020 Global Pandemic the notion of having organizations be practically 100% remote seemed like an experiment, reserved for articles you would read in the New York Times or Harvard Business Review.

But the Pandemic forced millions into remote work and while for many companies this seemed like a really good idea on paper the reality is that it affected organizational culture more than you imagine.

In the video below I discussed strategies to create organizational culture in the modern world. But if you are more of a reader feel free to read the summary included with this article.

Intro: The world has changed, forever

In 2020, I came to the realization that the world had changed and we were never going to be living life like we used to.

Fast forward to today and almost half of the population at least in the US are working remotely some partially and others in a full time capacity.

Remote Work: A blessing or a curse?

But is remote work good for us? Or does it hurt us more than we think? Depends who you ask.

While you could say remote work gives you flexibility in time, others feel they are working all the time.

While some say remove work eliminates the need to commute to the office, others feel that home becomes too connected with work.

You may think that remote work forced you to learn to use other channels (such as Slack, Zoom, etc) to collaborate with people but others may feel that this produces a lack of interpersonal relationships.

I believe one of the main issues with an organization with remote employees is that as time goes by, people start working in their own island, creating their own idealization about what the organization is.

Additionally, as new people join the organization (specially entry level people) their onboarding experience becomes more fragmented and transactional.

To bring people together, for them to believe that they are all pushing towards the same direction, you need to build an Organizational Culture.

What is Organizational Culture?

Organizational culture is generally understood as all of a company’s beliefs, values and attitudes, and how these influence the behavior of its employees.

When you lack a sense of community, of culture, then your organization starts being siloed.

Creating Culture in a Decentralized World

To create organizational culture, you can implement a method that I like to call: Values In Practice which consists in 3 simple steps:

  • Step 1. Define your Values
  • Step 2. Communicate them
  • Step 3. Put them in practice

Step 1. Define your Values

Meet with your leadership team and think of employees you consider excel in your organization and represent what you stand for. Then write a list of such adjectives. Each person in the leadership team should do the same. Once you finalized with the list of words, discuss with your team which adjectives would you combine or remove or keep. You want to end with a list of 3 to 5 adjectives. These will become your values!

!alt_text

Step 2. Communicate them

For each value, add a short description of what each value represents and why they are important to you.

Share them out with your team in different ways such as team meetings, digital media, swag.

It is important that you don’t communicate your values once but you are making sure that people understand (and more importantly agree with) your values.

!alt_text

!alt_text

Step 3. Put them in practice

The final and most important step to bring people together and create organizational culture is to put these values into practice.

Form a Values In Practice (or VIP) committee with members of different teams and locations (if you have people in different countries, for instance).

I believe that there’s a misconception that the culture of an organization is driven by HR or the Managers but in reality everything works better when the culture is driven by different team members of your organization.

The committee should meet once a month to plan a calendar of activities surrounded each value, see the example below for reference:

!alt_text

Conclusion

Once you implement the VIP method you will notice dramatic change in your organizational culture.

You will begin noticing that you can use your values not only to bring people together but also in other processes of your organization such as performance reviews or hiring.

Organizational Culture begins with setting the standard for the values your team stands for. When people agree with these values, they all push towards the same direction as a unified force and good things happen.

Read more
Pato Sapir
Published 08/15/2023
Leadership
The Keys to unlocking your team's potential

As marketing technology leaders, we hold the keys to unlocking our team’s potential and driving exceptional results for both our clients and those on our team. While some of the aspects of leadership discussed here might seem obvious, it’s important to recognize the critical role they play in building a supportive environment in the workplace.

The important takeaway is that it is our responsibility to not only see our team members as colleagues and important organizational resources, but as human beings with a diverse set of challenges, needs, backgrounds, and motivations. Fostering leadership from a standpoint of individual growth and support creates an inviting atmosphere, a sanctuary where employees desire to contribute, be part of, and thrive in, all while driving exceptional work for the organization.

Let’s look at a few pillars of leadership that can be important for leading effective, and happy, teams.

To me, one of the most important aspects of being a successful leader is empathy and communication. So many of us in this industry have had a unique path to our current position and have challenges or circumstances that might not be readily apparent at first glance. Starting from a place of trust, and privacy, with your individual team members can help provide them with a channel for communicating issues they are having and how those might be impacting their experience on the team. This ensures that you’re aware of any issues that might impact the team and are able to provide the needed support to help in whatever way that you can.

Work is an important part of our lives, but it is not the only (or most important) part. Setting the expectation amongst your team that personal development and relationships are paramount helps to create a scenario where they feel more protected when discussing how their work may be impacted by factors both external and internal to the workplace. Taking the opportunity to approach each situation with empathy, privacy, and understanding, rather than rushing to conclusions, provides a safe space and helps create an open dialogue, leading to a motivated and united team.

While it might not hold true for all teams and situations, for those managing engineering resources, it is critical that you possess the technical knowledge necessary to understand and guide your team. In positions of marketing technology leadership, you will be called upon to be a subject matter expert across a wide variety of domains and disciplines and will be able to make important decisions that will impact the day-to-day work of those on your team. A failure to understand the opportunities, and challenges, on a technical level can set everyone up for failure, and lead to spiraling costs and burnout amongst your team.

To effectively guide engineering teams, we must delve into their challenges, struggles, and the evolving tech landscape. Staying informed about industry trends and developments allows us to make informed decisions and offer relevant support. This deep understanding earns our team’s respect and trust, fostering an environment of growth and achievement.

Speaking of growth, the commitment to empowering your team members to reach their goals and potential is one of the most critical aspects of being a leader in this space. For most of us in this field, a lot of career growth and development has been on-the-job and supplemented by encouraging managers, books, and online resources. Understanding that the day-to-day work environment is not simply a mechanism for providing benefit to the organization but is also an important training ground for everyone on your team is an important step in building a culture of growth.

One of the primary challenges you might encounter are team members who have the ability, and desire, to further their skill set and development but are unsure about the possible paths or means of taking the next step. Building out a clear development and learning path for your team is critical to ease concerns of those who might find it overwhelming and to help guide everyone towards the next step in their career path.

Assigning non-urgent work in a domain someone might be interested in learning, creating training sessions, providing FTO for external training and providing means to attend conferences are just a few additional ways that you can help encourage a mindset of growth and development on your team. The important thing to note is that all knowledge and experiences can benefit your team, even if they fall outside of the traditional areas that we work in, so you can never go wrong encouraging employees to pursue and grow in the areas that interest them.

Transparency is a cornerstone of effective leadership, and it should be exercised as often as possible where discretion allows. Obviously, some circumstances prohibit this, such as receiving a sudden client or initiative pivot that is out of your control, but even then, steps should be taken to ensure that those who are responsible for the effects of those updates are informed as early as possible so that they can mitigate its effects. Never underestimate the simple power that simply keeping people informed in an honest and constructive way can yield.

This applies even more to the expectations and relationship that you have with the members on your team directly. We must communicate clearly and openly with our team, setting clear expectations from the start. If a team member falls short of expectations, address it early and constructively. Timely feedback fosters growth and encourages accountability. A transparent culture builds trust, ensuring our team feels supported and valued in their roles.

Keeping your team informed on both individual and department initiatives helps everyone feel more accountable and part of an overall direction rather than a cog in a machine.

This seems like a no-brainer, but it is remarkable how often driven team members at organizations feel unrecognized or adequately compensated for the value that they provide. As a leader, it is your responsibility to understand where great work is being done and to reward it with whatever means are both available and appropriate.

An extra FTO day to aid a long weekend for someone who has worked tirelessly on a critical project, a gift card to enjoy a free lunch on the company or public recognition in calls internal and external to your team are only a few ways to reward accomplishments that deserve to be recognized. Lastly, there is likely a reason that you’ve got a high performer on the team, and they have their own goals and interests that are driving their great work.

Try to understand what motivates them, and you might find even more opportunities to create a satisfying work environment for them whilst also doing great work for your organization.

As marketing technology leaders, we hold the power to transform our teams into trailblazers in the dynamic marketing technology landscape. By embracing the pillars of Empathy, Understanding, Growth, Transparency, and Accomplishment, we create an environment where collaboration, innovation, and continuous improvement thrive. Let’s lead with a human-focused approach to management and create innovative, happy, work environments that we ourselves would thrive in.

Read more
Jason Henshaw
Published 08/15/2023
Leadership
The Keys to unlocking your team's potential

As marketing technology leaders, we hold the keys to unlocking our team’s potential and driving exceptional results for both our clients and those on our team. While some of the aspects of leadership discussed here might seem obvious, it’s important to recognize the critical role they play in building a supportive environment in the workplace.

The important takeaway is that it is our responsibility to not only see our team members as colleagues and important organizational resources, but as human beings with a diverse set of challenges, needs, backgrounds, and motivations. Fostering leadership from a standpoint of individual growth and support creates an inviting atmosphere, a sanctuary where employees desire to contribute, be part of, and thrive in, all while driving exceptional work for the organization.

Let’s look at a few pillars of leadership that can be important for leading effective, and happy, teams.

To me, one of the most important aspects of being a successful leader is empathy and communication. So many of us in this industry have had a unique path to our current position and have challenges or circumstances that might not be readily apparent at first glance. Starting from a place of trust, and privacy, with your individual team members can help provide them with a channel for communicating issues they are having and how those might be impacting their experience on the team. This ensures that you’re aware of any issues that might impact the team and are able to provide the needed support to help in whatever way that you can.

Work is an important part of our lives, but it is not the only (or most important) part. Setting the expectation amongst your team that personal development and relationships are paramount helps to create a scenario where they feel more protected when discussing how their work may be impacted by factors both external and internal to the workplace. Taking the opportunity to approach each situation with empathy, privacy, and understanding, rather than rushing to conclusions, provides a safe space and helps create an open dialogue, leading to a motivated and united team.

While it might not hold true for all teams and situations, for those managing engineering resources, it is critical that you possess the technical knowledge necessary to understand and guide your team. In positions of marketing technology leadership, you will be called upon to be a subject matter expert across a wide variety of domains and disciplines and will be able to make important decisions that will impact the day-to-day work of those on your team. A failure to understand the opportunities, and challenges, on a technical level can set everyone up for failure, and lead to spiraling costs and burnout amongst your team.

To effectively guide engineering teams, we must delve into their challenges, struggles, and the evolving tech landscape. Staying informed about industry trends and developments allows us to make informed decisions and offer relevant support. This deep understanding earns our team’s respect and trust, fostering an environment of growth and achievement.

Speaking of growth, the commitment to empowering your team members to reach their goals and potential is one of the most critical aspects of being a leader in this space. For most of us in this field, a lot of career growth and development has been on-the-job and supplemented by encouraging managers, books, and online resources. Understanding that the day-to-day work environment is not simply a mechanism for providing benefit to the organization but is also an important training ground for everyone on your team is an important step in building a culture of growth.

One of the primary challenges you might encounter are team members who have the ability, and desire, to further their skill set and development but are unsure about the possible paths or means of taking the next step. Building out a clear development and learning path for your team is critical to ease concerns of those who might find it overwhelming and to help guide everyone towards the next step in their career path.

Assigning non-urgent work in a domain someone might be interested in learning, creating training sessions, providing FTO for external training and providing means to attend conferences are just a few additional ways that you can help encourage a mindset of growth and development on your team. The important thing to note is that all knowledge and experiences can benefit your team, even if they fall outside of the traditional areas that we work in, so you can never go wrong encouraging employees to pursue and grow in the areas that interest them.

Transparency is a cornerstone of effective leadership, and it should be exercised as often as possible where discretion allows. Obviously, some circumstances prohibit this, such as receiving a sudden client or initiative pivot that is out of your control, but even then, steps should be taken to ensure that those who are responsible for the effects of those updates are informed as early as possible so that they can mitigate its effects. Never underestimate the simple power that simply keeping people informed in an honest and constructive way can yield.

This applies even more to the expectations and relationship that you have with the members on your team directly. We must communicate clearly and openly with our team, setting clear expectations from the start. If a team member falls short of expectations, address it early and constructively. Timely feedback fosters growth and encourages accountability. A transparent culture builds trust, ensuring our team feels supported and valued in their roles.

Keeping your team informed on both individual and department initiatives helps everyone feel more accountable and part of an overall direction rather than a cog in a machine.

This seems like a no-brainer, but it is remarkable how often driven team members at organizations feel unrecognized or adequately compensated for the value that they provide. As a leader, it is your responsibility to understand where great work is being done and to reward it with whatever means are both available and appropriate.

An extra FTO day to aid a long weekend for someone who has worked tirelessly on a critical project, a gift card to enjoy a free lunch on the company or public recognition in calls internal and external to your team are only a few ways to reward accomplishments that deserve to be recognized. Lastly, there is likely a reason that you’ve got a high performer on the team, and they have their own goals and interests that are driving their great work.

Try to understand what motivates them, and you might find even more opportunities to create a satisfying work environment for them whilst also doing great work for your organization.

As marketing technology leaders, we hold the power to transform our teams into trailblazers in the dynamic marketing technology landscape. By embracing the pillars of Empathy, Understanding, Growth, Transparency, and Accomplishment, we create an environment where collaboration, innovation, and continuous improvement thrive. Let’s lead with a human-focused approach to management and create innovative, happy, work environments that we ourselves would thrive in.

Read more
Jason Henshaw
Published 07/17/2023
Leadership
Acceptable Failure

Failure. It means different things to different people. It means different things at different stages of life.

I am no stranger to failure. But learning how to use it to my advantage is only a recent thing.

My earliest memories of failing go back to when I was 4 or 5 and learning how to ride a bicycle. We lived at the top of a big hill. I had mastered riding around my driveway and yard. But I wanted to prove I could venture further, down the hill, and come back safely. I had no idea what the speeds would be like, or how to stop at such speed. I crashed pretty hard into a friend’s yard. I don’t recall specifics, but I do recall being incredibly upset and in pain, and walking the bike back up the hill to my house. I do not recall a good response from my father, only adding to the sense of failure I felt. It was very powerful, so much so it is still one of my most vivid memories from 45+ years ago.

Professionally I have had my own string of failures as well. Find me at a conference or call me sometime, I’m happy to talk about them. They used to be an embarrassment to me, until the mother of all failures hit. I have shared the story before, so I will put it on you to go <span style=“text-decoration:underline;”>listen</span> if you are interested. It was some time after this epic failure, that occurred on a national stage, that I really started learning how to embrace my failures. I give credit to <span style=“text-decoration:underline;”>Alex Williams</span>, then at Trendline Interactive, for starting conversations with me around the idea of acceptable failures. It is a concept that bears some thought - both because total perfection is not realistic, and because people need to grow.

Before diving into what acceptable failure looks like, it’s important to understand why some failure needs to be expected and appreciated. Kentin Waits outlines what I think is the best list of things you can learn from failure in the article <span style=“text-decoration:underline;”>“7 Surprising Benefits of Failure”</span>.

The key ones to me are:

  • Failure teaches lessons. There are some things I have failed at that I learned a great deal from, and there are other mistakes I have had to repeat a few times before I learned the lesson I was meant to learn. But there is always a learning opportunity there, and as a manager, it is my hope that people see that. I also feel it is my job to help them see it if they can’t get there themselves.

  • Failure helps us overcome fear. In the email marketing industry, one of the biggest fears people have is of “pushing the button” - the deployment of a campaign to a live audience. I don’t think I have ever been in another situation where I second-guessed myself so many times before taking an action. But after you make a mistake in an email campaign, large or small, you learn that the sun still comes up the next day, and there is more work to do. This actually helps you keep things in perspective.

  • Failure Inspires Creative Solutions. As Thomas Edison famously said, “I have not failed 10,000 times—I’ve successfully found 10,000 ways that will not work.” Sometimes a solution requires out-of-the-box thinking and the only way to get there is through trial and error.

Having been in both the agency and brand-side worlds, I know that the daily expectation is total perfection. But as I previously mentioned, that is not realistic. So you need to reframe your expectations to allow for acceptable failures. The right mindset from a leadership perspective is critical. Here are my thoughts for how to deal with failures within your team:

Treat each incident as a learning opportunity. Not just for the person that made the mistake, but for yourself, the “process”, the whole organization. This is part of a mantra of continual improvement. Treat the person who made the mistake with grace and humility. At the end of the day, we are all human, with the emotions that go with that. Castigating someone harshly for a mistake doesn’t help the situation at all. Professionals will already feel bad enough and be really hard on themselves about whatever went wrong. They don’t need you piling on top of it. Don’t punish the person. This comes with a caveat outlined further down, but in general, punishing someone for making a mistake is going to be counter-productive to their well-being and that of the organization. You want people to feel safe about making minor errors, as that is part of the learning process. Be relatable. One of my most powerful tools when a team member makes a mistake is to share one of mine. I have made more than one, so I have a library to pull from. When you can relate to what they have done and be empathetic about it, they will likely be more diligent and confident in the future, seeing that you got to where you are by learning from your mistakes. Own your own mistakes. While this kind of goes with being relatable, it applies to you every day. Nothing magical has happened to make you immune to making a mistake, you will make more. Make sure you take responsibility in the same manner you would want your team to.

Now, you may be asking yourself “does he think any and all mistakes are acceptable?” Of course not. There is a line that has to be drawn. It should be pretty easy to see where that line is. Two key factors for this line are:

  • Has the same mistake been made multiple times? If someone doesn’t learn the first time and makes the same mistake again, then you need to work with them to understand why this happened, and deliver a warning. If it continues to happen, then other actions may be necessary. This is not acceptable failure in my mind, it is more like willful ignorance, which I have less tolerance for.

  • Weigh the impact. If there are legal or financial ramifications for a given mistake, then the person who made the mistake needs to be made aware of this. And in the future, identical mistakes cannot be made. Once is understandable, but twice can jeopardize the business and you need to treat it as such.

I have adopted this approach with the teams I have been leading for the last 10 years or so, and it has proven successful. With each team, I have felt connected to them, and feel that I have earned their trust simply by showing that I can relate and own up to my own mistakes. More importantly, the teams have felt empowered to experiment and build good solutions without the fear that a failure means it is the end of the world. Morale with my teams has been great, and in turn, I also feel that I am helping them to become better leaders as well. Sure, mistakes still happen, but our ability to recover from them and prevent future ones has been one of the biggest benefits from this approach.

Thank you.

Read more
Chester Bullock
Published 06/15/2023
Leadership
Agile Leadership and Change Management

Change is an inevitable part of our lives, and in today’s fast-paced world, it is more prevalent than ever. It’s the undercurrent of our evolution and is the process of becoming different. As leaders, it is crucial to not only adapt to change but also embrace it with an agile mindset. In this blog post, we will explore the concept of agile leadership, its relevance in a dynamic marketing world, and how it can empower individuals and organizations to thrive amidst constant change. So, let’s delve into the world of agile leadership and discover its transformative power.

Change creates a massive opportunity to have a clearer vision, become stronger, reinvent, grow, and thrive.

In today’s dynamic world, where technology advancements, market disruptions, and global events shape the business landscape, traditional leadership approaches often fall short. The global pandemic has compelled us to swiftly adapt to new business practices and redefine social interactions. Companies promptly transitioned to online operations, necessitating prompt communication regarding changes in business operations.

Businesses that are stuck in old ways will become part of the past.

Agile leadership offers a solution to navigate this complexity and uncertainty. It enables leaders to effectively lead teams through change, foster innovation, and seize emerging opportunities. By embracing agile leadership, individuals and organizations can stay ahead of the curve and thrive in the face of constant flux.

Change can occur at four different levels of intensity:

  1. Minor changes: Simple adjustments that don’t require process or behavior changes
  2. Moderate changes: Moderately complex changes confined to a specific group or process area.
  3. Significant changes: Highly complex changes that span multiple areas or functions, involving organizational transformations, new roles, responsibilities, reporting structures, and software. Successful execution demands preparation, planning, training, and time.
  4. Extreme changes: Highly complex, novel changes with broad impacts, impacting individuals, businesses, industries, and the global business landscape. They may be sudden and poorly understood, such as disruptive technologies.

<br />

Understanding the different levels of change intensity enables effective preparation and navigation, ensuring appropriate strategies are employed to manage change at each level.

Change and Transformation are often used interchangeably. Change may be planned or unplanned and has clear boundaries. Transformation, on the other hand, has no clear boundaries. It typically looks like a strategy, a vision for what the future would look like. It cuts across different departments and not just about the process as it also involves transforming the culture. While it’s difficult to manage change, it’s much more difficult to manage transformation. Transformation takes longer than change.

Agile transformation is a process of evolving from being change resistant, uncomfortable, fearful, and reactive, to thriving in the environment with high change saturation, and being proactive and embracing change. I have worked with multiple clients who have invested millions of dollars in tools and technologies but their lack of investment in change management hindered them from achieving the desired outcomes. An agile leader plays an important role in change management.

Key attributes of an agile leader include a flexible mindset, tolerance for risk, comfort with change, adaptability in problem-solving, embracing servant leadership, valuing team collaboration over individual contributions, active listening and asking questions, and empowering the team.

Agile leadership is a leadership approach that embraces flexibility, adaptability, and responsiveness in the face of change. It is rooted in the principles of the Agile methodology, originally developed for software development but now widely applied across industries. Agile leadership focuses on collaboration, empowerment, continuous learning, and iterative problem-solving. It encourages leaders to be facilitators and coaches rather than traditional hierarchical managers. It’s highly beneficial and helps in enabling organizations to adapt and thrive in the dynamic and competitive market.

  1. Rapid Response to Market Trends: Agile leaders in marketing can quickly identify emerging market trends and customer preferences. They can swiftly adjust marketing strategies, campaigns, and messaging to capitalize on new opportunities and stay ahead of the competition.
  2. Agile Campaign Execution: In a fast-paced marketing environment, agile leaders can effectively manage and execute marketing campaigns. They embrace iterative processes, allowing for continuous feedback, optimization, and adaptation throughout the campaign lifecycle.
  3. Customer-Centric Approach: Agile leaders prioritize customer needs and preferences. They foster a customer-centric culture, encouraging teams to gather customer insights, conduct experiments, and iterate marketing initiatives based on feedback. This approach ensures that marketing efforts align with customer expectations and drive better results.
  4. Cross-Functional Collaboration: Agile leadership promotes collaboration and communication across different marketing functions and teams. By breaking down silos and encouraging cross-functional cooperation, agile leaders enable the exchange of ideas, knowledge sharing, and efficient execution of integrated marketing strategies.
  5. Agile Data-Driven Decision Making: Agile leaders leverage data and analytics to drive marketing decisions. They encourage teams to collect, analyze, and interpret data, enabling informed decision-making and optimization of marketing campaigns based on real-time insights.
  6. Continuous Learning and Improvement: Agile leaders foster a culture of continuous learning and improvement. They encourage experimentation, celebrate both successes and failures, and provide opportunities for skill development. This approach empowers marketing teams to innovate, adapt, and continuously improve their performance.

<br />

By embracing agile leadership principles, marketers can navigate the evolving landscape and achieve sustainable growth.

Effectively guiding teams towards change-resilience is not as simple as knowing and understanding the psychology of individuals multiplied by the number of team members involved. It calls for a different approach.

Susanne, a Marketing Strategist at H2M, collaborated with John, a Solution Architect, to conduct an extensive 8-week evaluation of H2M’s marketing tools and strategies. Through numerous discovery meetings and individual sessions with key stakeholders, they dedicated significant time and effort to this endeavor. Subsequently, they presented a comprehensive transformational strategy plan, accompanied by various recommendations. While most teams expressed their support, there was one team whose consent was essential for project advancement. Unfortunately, despite the investment of resources and time, the lack of alignment from this team rendered the efforts futile. Upon further exploration, it became evident that this particular team had exhibited resistance to change for the past two years. This incident highlights the significance of implementing a robust change management process and the need for agile leadership to ensure alignment across all teams, foster collaboration and drive continuous improvement.

Belonging and Understanding are foundational in change management. If they are properly accounted for during the design and implementation of change initiatives, they can contribute significantly to their success

The following techniques can help create and reinforce Belonging and Understanding within teams:

  • Create a signature core message: Develop a consistent message that emphasizes belonging, high standards, and belief in individual growth. Share this message during one-on-one meetings and team interactions to foster a sense of safety and confidence.
  • Solve challenges together: Give your team autonomy to solve meaningful problems. Shared challenges create a sense of belonging and unite the team. Encourage a shared purpose that transforms individuals into a cohesive team.
  • Seek input and demonstrate shared understanding: Ask for team members’ opinions, ideas, and solutions. This shows that their input matters and fosters a sense of belonging and safety. Actively listen and use their words verbatim to demonstrate shared understanding.
  • Create opportunities for connections: Encourage non-work-related interactions and social bonds. Facilitate water-cooler conversations, virtual huddles, and non-work events to build a sense of belonging. Host team dinners or virtual discussions on thought-provoking topics to encourage collaboration and bonding.
  • Hold team non-work-related events: Host regular gatherings where team members can share a meal and engage in facilitated discussions on thought-provoking topics. This fosters personal connections and transcends work boundaries.
  • Facilitate team mission statement workshops: Conduct workshops to develop a team mission statement that aligns with organizational goals. Ensure agreement among team members and continually reinforce the mission to create a sense of contribution and understanding.

<br />

By implementing these techniques, leaders can cultivate belonging and shared understanding within their teams, enhancing collaboration, engagement, and overall team success.

A large real estate client has invested heavily in various marketing tools. They aim to optimize tool utilization by integrating their SMS efforts into Mobile Connect and eliminating two other redundant tools. However, existing team members resist the change due to comfort with the current tools. They fear the learning curve and the additional time that has to be invested. Agile leadership is crucial here to address concerns, provide support, and emphasize the benefits of consolidation, fostering shared understanding and a sense of belonging within the team.

Tools/processes that can help promote Understanding:

A team working agreement refers to a set of guidelines or rules that the team collectively agreed upon and follows to facilitate effective collaboration and achieve common goals. It serves as a foundation for how team members interact, communicate, and work together. It covers various aspects, including but not limited to:

  • Communication: How team members will communicate with each other, the preferred communication channels, and the frequency of communication.
  • Roles and Responsibilities: Clarifying the roles and responsibilities of each team member to ensure a clear understanding of who is accountable for specific tasks and deliverables.
  • Decision-Making: Establishing the decision-making process, whether it’s through consensus, voting, or another agreed-upon approach.
  • Work Processes: Defining the team’s approach to planning, prioritizing tasks, and managing workflow, such as using agile frameworks like Scrum or Kanban.
  • Collaboration and Respect: Promoting a culture of respect, active listening, and constructive feedback, encouraging collaboration and fostering a positive team environment.
  • Conflict Resolution: Outlining strategies and approaches to address conflicts and disagreements that may arise within the team, ensuring a swift and effective resolution.

<br />

The team working agreement is created and agreed upon by the team members themselves, often during the project kickoff or team formation stage. It is a dynamic document that can be revised or updated as needed, reflecting the team’s evolving needs and circumstances.This agreement promotes transparency, accountability, and a sense of ownership among team members, leading to improved productivity and a more cohesive and high-performing team.

  1. Empathy Maps: An empathy map is a tool that helps design a collaborative process aligned with team members’ feelings, motivations, and working styles. It allows team leaders to understand what drives each team member and the emotions they may experience during their work.

  2. Scrum framework: Scrum, a lightweight framework rooted in agile principles, is used to develop and deliver complex products. It focuses on people and enables teams to be creative, collaborative, productive, and adaptable to change. Scrum teams are cross-functional and adhere to five core values: openness, courage, commitment, respect, and focus.

  3. Servant leadership mindset: It’s a mindset and philosophy embraced by exceptional leaders. It involves ten important attributes: listening, empathy, healing, awareness, persuasion, conceptualization, foresight, stewardship, mentorship, and team building. These qualities enable leaders to support and empower their teams effectively.

  4. Facilitation is the art of unleashing the intelligence, insights, and creativity of individuals within a team. Teams comprise individuals with diverse knowledge and incredible potential for creativity. Facilitation is a skill of a servant leader who helps the team reach its full potential.

Knowing how to lead change in an organization is a critical skill for any leader to have.

Regardless of the scope, complexity, and size of a particular change, you must be able to create a clear path to get buy-in from others on your team and in your organization.

Two important models for understanding and managing organizational change: Lewin’s three stages model and the McKinsey 7S framework.

Lewin’s three stages model provides a simple yet effective approach to managing change. The three stages are unfreeze, change, and freeze. **Unfreezing **involves preparing the organization for change by creating awareness and a sense of urgency. This stage helps break down existing mindsets and resistance to change. The **change **stage is where the actual modifications and adjustments take place. It requires deliberate effort to manipulate the current state of the organization and implement the desired changes. Finally, the **freeze **stage involves solidifying and institutionalizing the new state, making it the norm.By following Lewin’s three stages model, organizations can expect to gain a structured approach to change, increased understanding and support from employees, and the successful implementation and institutionalization of desired changes.

The McKinsey 7S framework examines seven interconnected elements within an organization that collectively shape its effectiveness. These elements are shared values, structure, strategy, systems, style, staff, and skills. The framework highlights the interdependencies and relationships between these elements. It emphasizes that all components need to be aligned and mutually reinforcing for the organization to achieve its objectives. When planning a project or change initiative, the McKinsey 7S model can be used to assess the impact on each element and identify any misalignments that require corrective action.

Let’s take an example of a retail company developing a new campaign on a new channel to compete with established market leaders. In this scenario, the company’s **strategy **might involve adopting a lean startup approach. Instead of following a traditional development process, the company focuses on rapid experimentation and learning. The **structure **would involve cross-functional teams, self-organizing and empowered to make decisions. **Systems **would include marketing tools and frameworks. **Skills **would focus on training and developing team members. The leadership **style **would change to be more collaborative and servant-oriented, encouraging autonomy and trust among team members. **Staffing would involve hiring or reskilling individuals. Lastly, shared values **would emphasize the importance of customer satisfaction, continuous improvement, and embracing change.

By applying the McKinsey 7S framework, agile leadership can assess and align these elements to create a conducive environment for agile practices. It ensures that strategy, structure, systems, skills, style, staff, and shared values work cohesively to support agile principles, enabling the organization to be more responsive, adaptable, and successful in delivering customer value.

Kotter’s eight-step model, developed by John Kotter, is a popular change management model that helps organizations effectively implement change. The model consists of the following steps:

  • Create a sense of urgency: Communicate the need for change and create a compelling reason for individuals to support and embrace it.
  • Build a powerful coalition: Form a team of influential individuals who are committed to the change and can drive it forward.
  • Develop a strategic vision: Create a clear and inspiring vision that outlines the desired future state after the change has been implemented.
  • Enlist change advocates: Identify and empower change agents who can influence and motivate others to embrace the change.
  • Enable action by removing obstacles: Identify and eliminate barriers that may hinder the progress of the change initiative.
  • Generate short-term wins: Create and celebrate small victories or milestones along the way to maintain momentum and build confidence.
  • Sustain change acceleration: Embed the change into the organizational culture and ensure that it becomes the new norm.
  • Institute change: Make the change a part of the organization’s systems, processes, and practices to ensure its long-term sustainability.

<br />

By following these eight steps, organizations can navigate through the change process more effectively and increase the likelihood of successful implementation.

Another well known theory, ADKAR theory, developed by Jeff Hiatt, provides a road map for addressing the business and the people’s side of change. **ADKAR **is an acronym and stands for awareness, desire, knowledge, ability, and reinforcement. For change to be successful, each of the elements of the model must be addressed in order.


Let’s consider a use case and understand how Agile leadership is effective in overcoming challenges related to market dynamics, cross-functional collaboration, tight timelines, and customer-centricity

Scenario: A retail brand is planning a marketing campaign to promote its new product line across various channels, including social media, email marketing, and influencer partnerships.

  • Channel complexity: Managing multiple channels simultaneously can be challenging, requiring effective coordination and alignment of messaging and creative assets.

  • Real-time adaptation: The marketing landscape is dynamic, and campaign strategies may need to be adjusted based on market trends, competitor activities, or customer feedback.

  • Measuring campaign performance: Tracking and analyzing campaign results across different channels can be complex, requiring the ability to gather and interpret data effectively.

  • Cross-functional collaboration: Agile leaders foster collaboration between marketing, creative, and data analytics teams to ensure a cohesive campaign strategy. This involves regular communication, sharing insights, and aligning efforts across channels.

  • Agile project management: Adopting agile methodologies allows for iterative campaign development, testing, and optimization. Agile leaders facilitate shorter feedback loops and enable quick adjustments to maximize campaign effectiveness.

  • Data-driven decision-making: Agile leaders emphasize the importance of data in driving campaign decisions. They encourage the use of analytics tools and dashboards to monitor performance, identify trends, and make data-driven adjustments.

  • Continuous improvement: Agile leaders promote a culture of continuous improvement by conducting post-campaign analysis, capturing learnings, and applying them to future campaigns. They encourage experimentation and innovation to stay ahead of the competition.

With agile leadership, the retail brand successfully executes the marketing campaign across multiple channels. Agile methodologies allow for real-time adaptation based on performance data, resulting in optimized campaign strategies. Cross-functional collaboration and a data-driven approach contribute to improved campaign performance, customer engagement, and brand awareness.


In a dynamic world of constant change, agile leadership is crucial for individuals and organizations to thrive. By embracing adaptability, empowering teams, fostering collaboration, and promoting continuous learning, agile leaders can navigate uncertainty and seize opportunities. So, let us embrace the transformative power of agile leadership and embark on a journey of growth, innovation, and success in this dynamic world of change.

Read more
Jyothsna Bitra (JB)
Published 05/20/2023
Summer '23 Release Review

As we come crashing into the second release of 2023, Salesforce is pulling their fingers out and are bundling one of the single most exciting packages of updates in a long time. With changes across the board from CloudPages, Content Builder and Intelligence Reports, there will likely be something here that will be of interest. But, the key question is, are each of these changes a welcome value add?

One of the highlights of this release is that it’s one of the few releases in recent years that hasn’t featured a range of functionality being sunset or announced as end of life. It’s a new growth focused release which is a great state to be in. So, let’s get started.

In-App data is being added to Intelligence/Intelligence Reporting to provide parity for your In-App campaign activity. This will manifest with new attributes available within Intelligence Reporting for use in Pivot Tables and Reports covering:

  • In-App Content Name
  • In-App Button Label
  • In-App Unique Sends
  • And more…

<br /> <br />

Some workspace management capabilities are being added to Intelligence, find out more about the options available here. These should help keep your workspaces neat, efficient and simpler to navigate. If you’re using Intelligence within your Marketing Cloud activities beyond the Intelligence Reports available in the platform natively and have some tips, tricks or ideas that have been game changers to you, reach out and we’d love to provide you a platform!

Whilst we may have suggested there’s not much going away in this release, it’s time to mention a feature leaving your Intelligence platform (it will never stop being strange referring to Datorama as Intelligence). This isn’t a Salesforce led initiative, this is Google retiring Universal Analytics. If you’re not already on a GA4 Property already, make sure you get that change done before 1st July to prevent a break in service. Just make sure you have the Email to Web conversion tracking app installed from the marketplace.

Export your data to Google BigQuery from your Intelligence Workspace. Simply access the Database Tab, select Google BigQuery as the vendor of choice and follow the on screen prompts to get the data into your Google account for wider use across your org.

Enhancements to Pivot Tables are coming to Intelligence. Underneath Analyze & Act, just select Pivot Tables, select your Pivot Table and via Visualize view the Pivot Table Widget. Once you’re there, hover over the specific table select Drill-down or Filtering. Drilling down and Filtering will give a more accessible way to view your data at a more granular level.

Transactional Messaging NotSent Events are being added to the Tracking Extracts within Automation Studio. It’s one of those elements of SFMC that you don’t know you need until often it’s too late! If you’re not already leveraging the NotSent tracking extract, there’s even more reason now to get it into your regular workflows and monitoring with the inclusion of Transactional Messaging NotSent Events being included. This is the best non-code way to view unsent email messages across the whole platform, whilst NotSent doesn’t have a data view. That said, we’ve seen new data views get added in recent releases, perhaps the product team is more open to adding new data views than we may have historically thought. Would a NotSent Data View be of use to you? Especially with NotSent only retaining 2 months worth of data, an upgrade to 6 months could be helpful.

Potentially overshadowed by the announcement of Salesforce 360 Data Genie Cloud 360 Audiences CDP, Salesforce & Meta announced a partnership around the Meta Cloud API, introducing selling directly in chat. You’re now able to migrate your existing integration with a third party provider, like Sinch to the new Salesforce Marketing Cloud META-Direct offering. This features some additional platform enhancements including faster approval of templates and some self-serve account management capabilities. But… this is also joined with the announcement that on 1st June 2023, WhatsApp Template-Based Pricing is changing. This change is universal to all businesses using Marketing Cloud WhatsApp Messaging. Pricing changes rarely mean discounts for the majority of customers, so make sure you review what the pricing changes mean for you and your org!

As we’ve highlighted before, on occasion releases can get multiple bits of the release notes pie and we have one in this release.

!Spring 2023 Release Note highlighting the collapsible paths in Journey Builder

One of these screenshots from the release notes is from Spring ‘23. The other is from Summer ‘23. !The Office They’re the same picture meme comparing Spring 2023 and Summer 2023 Release Notes

In the age of Journey Builder being pushed as not just for 1:1 but also volume communications, journeys can get complicated and the use of decision splits can cause journeys to become unwieldy. This is absolutely a welcome feature, but it would be great to be able to use it in drafts as well as running journeys. Being able to copy a decision split and all of its dependencies in a collapsed form would be useful. Journey Builder product team - roadmap?

Journey Builder History is one of those items that has often been a pain point of using the tool. Many businesses need to be able to identify issues, potentially keep auditable reviews of what a journey has done to confirm an exact customer experience the history capability has been sorely lacking. With this release, there is a new REST API either being produced or (perhaps more likely) being supported officially after blog posts spilled the beans on something undocumented. Keen to see if there’s a value add beyond the previously undocumented methods or whether it’s just a new piece of documentation being advertised as a new capability.

Email activity windows are coming to Journey Builder, having previously only been available in Email Studio & Automation Studio. You’ll be able to implement burst windows by using just delivery windows and hourly thresholds independently from one another within Journey Builder. Hourly thresholds have been a longstanding staple of IP Warming and deliverability management, so have been in Journey Builder for a little while, but delivery windows to implement a burst is a nice new feature to have.

Decision Splits will lose the crown of being the only part of Journey Builder that can interact with both Contact and Journey Data as a result of this release. Journey Data is being added to Exit and Goal Criteria as a nice enhancement to enable users to compare what someone entered a journey with against what they have now. For example, if you have a goal in a journey to move someone from one Frequency Band to another one, but have a range of Frequency bands entering the journey, you should be able to compare all frequency bands in one journey. Previously you may have required a journey per entry frequency band to be able to say “Frequency Band has improved by 1 level”. Now you should be able to do all those comparisons within a single journey.

One of the key barriers to wider adoption of Journey Builder, especially in larger organizations is the throughput capacity of the tool. Whilst it has improved drastically in the last 5 years, starting at (anecdotally) circa 50,000 journey entries in an hour to 2m activities per hour across your tenant, for the largest of the large senders, that isn’t quick enough. If you have a sale email to get out the door, a pre-sale launch message, you don’t want that message landing with some customers hours earlier than others. You can’t give a consistent digital experience with that range delivery. This release introduces a new beta system optimization dashboard, to highlight which journeys right now are impacting performance, identify reasons why specific journeys may be running slow and provide advice to improve the speed of a journey. It’s definitely a step in the right direction to making Journey Builder more accessible for high volume throughput. Doesn’t solve the capacity limitations of the platform, but it will help users identify where they can prevent issues from appearing.

A common key decision for integrated SFMC and Sales/Service Cloud orgs is the use of Salesforce Data Entry Events as Entry Sources for Journeys. There are limitations on the Sales/Service Cloud side around Apex that currently can be difficult to identify at pace or diagnose issues at all because the Objects used on the CRM side are hidden within the Journey itself. The Journey dashboard will give you the option to filter and see which Journeys are using Salesforce Data Entry Events and then filter to specific objects and identify where you’re reaching those limitations. This will allow users to take preventative action and move Journeys to more sustainable methods of entry when outgrowing the capacity of the Salesforce Data Entry Events.

A bountiful release would not be such a joy without the inclusion of some new developer features. Admittedly, the way Salesforce has called this specific section out with just a single new feature (albeit a pretty neat one) does feel like a token gesture. Especially with the REST API for Journey History being bundled in with Journeys. Anyway… On to the actual Salesforce defined Developer focused feature release. Parse JSON with AMPscript to enable the use of JSON data in personalized content. The release notes for this call out the function as BuildRowsetFromJSON(), but we would assume it’ll be BuildRowsetFromJSON() when it goes live. I vaguely recall BuildRowsetFromJSON() was a wrong answer on one of the SFMC certification exams, so make sure when you take the exam you study for the exam, rather than the reality!

Another one that could possibly have been better served in the Developers category, but alas! Legacy REST API Routes are being changed to match the UI data access permissions. If you’re using the unofficial/undocumented legacy REST routes, don’t be surprised if they stop working if you don’t audit the permissions of Data Extensions first. The following routes are impacted:

  • GET /legacy/v1/beta/object/
  • GET /legacy/v1/beta/object/{co_id}
  • GET /legacy/v1/beta/object/{encoded_id}
  • POST /legacy/v1/beta/object/{encoded_id}
  • DELETE /legacy/v1/beta/object/{encoded_id}
  • GET /legacy/v1/beta/object/{de_id}/field
  • GET /legacy/v1/beta/object/{de_id}/field/{id}
  • UPSERT /legacy/v1/beta/object/{de_id}/field/{id}
  • DELETE /legacy/v1/beta/object/{de_id}/field/{id}
  • GET legacy/v1/beta/object/{id}/data
  • GET /legacy/v1/beta/object/{object_id}/relationship

<br /> <br />

Check the Shared Data Extension permissions to identify whether you’ll be impacted by this change. Such is the risk of undocumented/unofficial API routes, they may be super useful, but be aware that they can with minimal or in some cases no warning be taken away from you!

Salesforce has bundled a few things under their Cross Cloud category for this release, but it’s definitely more akin to the old school Distributed Marketing release notes of old. The Legacy REST Routes gets its second showing of the release here, but beyond that it is the Distributed Marketing show! These are summed up as:

  • Predefined customizable Subject Lines
  • Default values for Distributed Marketing Quick Sends
  • Distributed Marketing Reporting gets an extension

<br /> <br />

I’m sure these features are super useful, but Salesforce has given about 2 lines for each of these. If you have any specific experience that highlights the value of these changes, let us know and we’ll include it!

There’s a couple of neat things coming to Content Builder in this release, including the introduction of a Content Builder Recycle Bin. For many years we have waited for the option to restore something that has been deleted by accident rather than re-upload it. The release notes specify that it’ll be only the content owner who can recycle it fully, so if you have the right permissions laid out for it, it should be tricky for a user to accidentally remove production content owned by another user. Whereas now, it’s a couple of clicks and gone with a panicked call to Salesforce support.

Custom Domain Cloud Pages get a couple of useful new features around error handling and creating trustworthy customer experiences. I’m specifically keen to see the error pages, right now the options are limited across the board, whether this would be a consistent experience for expired links in emails vs just spelling a link wrong is yet to be seen. It would be great if this error page would make the original target URL available for logging and management purposes. Giving SFMC users the opportunity to capture these kinds of customer experience issues would enable users to resolve friction points in customer journeys as well as generate alerting capability simply. Very interesting to see what this specific feature encompasses!

Root content for Customer Domains, essentially giving users the option to determine default content if a subscriber attempts to copy the domain from a hosted cloud page to explore your offering. Previously this would error and you wouldn’t have an especially robust customer experience, these kinds of issues can impact the trustworthiness of your email activities to customers. Providing users the option to create a default fallback is what many Email Marketers are used to when delivering dynamic content, this is a long time overdue.

Event Notification Service gets a bump in this release, prior to this release the ENS only sent notifications around the Transactional Messaging API, this is being extended to cover Automation Studio errors as well. The notification will include Automation Name, Type, Business Unit (not sure if it’ll include the friendly name or just the MID), File Location and Error Details. This could be useful in supporting automated retries of erroneous automations, based on the response of the Notification. Definitely something to investigate when it goes live.

Automation History Details as a CSV download. From the enterprise BU within an SFMC Org, you can jump into Setup and Download Automation History. This will help identify whether you have problematic automations that fail regularly at a glance without needing to visit each automation in turn. Useful for keeping the platform running in an optimal fashion.

Data Extension Storage report moves out of Beta and to General Availability in this release. There’s some useful tools in this, allowing you to identify Data Extensions that may be holding more data than is optimal, high volume Data Extensions such as Send Logs or Data View Archives could be typical culprits. But, it will also help you identify Data Extensions that may not be configured correctly and could be optimized to ensure the platform is running efficiently.

One of the hidden gems of SFMC is the ability to import from one Data Extension to another, bypassing the SQL timeout limit if you need to move the full depth and/or breadth of a Data Extension from one place to another without compromising the integrity of the source DE. Salesforce is claiming this is now 10 times faster than before, which would be game changing.

SQL Query optimization in line with validation, where Salesforce will start to highlight whether there are any specific code issues that could cause your query to be non-performant or prone to issues. Salesforce claims that it will also provide suggestions on how to improve your code. It’ll be interesting to see what these suggestions are like and whether it’s things like “You have 4 JOINs in your Query, you should consider splitting it” or if it will be more useful around optimal SQL code rather than just generic “do less in SFMC” type queries.

Package Manager is getting some industry specific templates to help marketers work faster. It’ll cover Landing Pages (Cloud Pages surely?), automations, content and journeys. May be worth having a look and seeing whether there is anything you’re not currently doing that would be worth adopting.

At the beginning of this article, the question was asked as to whether these new features are all a welcome value add or not. Summarily, most of them are. But, there are 2 key items to call out in the General App, Setup & Security that are staring right down the barrel of billables and contracts. Some users will recall the introduction of contact overage charges before Salesforce had introduced a non-developer option to delete contacts. It seems that Salesforce may have learned from their mistakes and are making user serviceable views of billable items available before the likely enforcement of some previously unmanageable line items on your contracts.

Do you have a large amount of regular automations? Do you have a group of users who are heavily using Automation Studio for their day to day activity? The release of Automation History Details as a CSV download will make it possible for you to identify your usage. You will then be able to monitor this against your contractual limits and with that information Salesforce may make a specific attempt to enforce overage charges. If you’re a heavy user of modular automations, you may need to refactor things to ensure your usage is within your contract or be prepared to negotiate if Salesforce comes knocking.

If you have a large volume of data in your SFMC org, whilst it’s not a Data Warehouse or Data Lake, you can store vast quantities of data in Marketing Cloud. You can do lots with data in Marketing Cloud. If you are doing lots with lots of data in Marketing Cloud, the general availability of the Data Extension Storage report to allow you to monitor how much your org is currently holding and where is the first step in you being able to prepare your org to make sure when it comes to contract renewal time that you’re using SFMC in an efficient manner to prevent being charged for over using the platform with contractual line items that users have had no mechanism to monitor until this release.

This may sound like conspiracy theory, but the pricing structure of SFMC has changed over the last few years. It now includes Automation run counts as well as Data Extension Storage as default line items. Whether the volumes are appropriate for your business will be down to your ways of working and the way you architect data from a cost and management perspective. Don’t get caught out by this, get a plan in place to review your contract and your current usage.

Besides the above, the new AMPscript function and new Cloud Page capabilities are pretty cool. I’m looking forward to seeing how they get used. This release is super focused on optimizing the use of the platform, which isn’t necessarily a bad thing. Everyone working in a more efficient manner will mean everyone can achieve more with their investment in Salesforce.

Read more
Jason Cort
Published 05/04/2023
Leadership
And the Oscar goes to....your team?

Tips and tricks on how you too can help grow your team to extraordinary heights

I have been told my training methods are a bit unorthodox.

Not because I am a tyrant, but because I can thoughtfully push my team to their limits without overwhelming them in the process. My goal as a manager is to inspire others to want to continue to enhance their marketing cloud skills and take pride in what they build.

Before we dig into my tips on inspiring and developing an award winning team. Yes, award winning. My current team has been a part of winning Oscars for Olympics coverage and my previous team was recognized by MediaPost for our marketing initiatives during COVID when no one was buying shoes. I have somewhere around 16 years of experience working in marketing cloud and 6 years leading digital communications teams. I have covered industries from biotech to retail to media while leading teams with humility and integrity. I am a tough but supportive manager who never hesitates to sing my team’s praises to leadership and remind them they are valued team members.


One of the easiest ways to inspire a team is to show them the value they bring back to the business. More often than not, when I join a team the CRM team has no idea the impact they are making on an email program, which in turn, makes an impact on the company’s bottom line. I am a champion for team collaborations and make it a point to break down the walls previously established. I call out the successes of all teams when reporting to leadership and when allowed I invite those teams typically excluded to attend these calls and hear me sing their praises. This is not only limited to my internal teams, but our agency partners as well. When people see they bring value to a project, they feel more invested in its success. When they feel more invested in our success, I have more to showcase to leadership. Showing other teams the value they bring to your team builds trust across the company and makes future collaboration an exciting proposal and not just another task on their project management tool.


This highlights another import skill for a manager. Leading with humility. Are you a manager who is never wrong or never makes a mistake? Well, then you are managing wrong. If there is one thing COVID lockdown taught us, it’s that everyone is human. People make mistakes, and people scared to make mistakes make EVEN MORE mistakes. As a leader I feel that any mistakes my team makes are a reflection on my leadership. This means I am not afraid to take the fall for any mistakes they make. Anytime, all the time, no exceptions. If there is an exception then we go to leadership together. (Mainly so they can answer any questions that I cannot on my own) The point here is I am by their side and not driving the proverbial bus they are under.

There is a rule on my team that if you are coming to me with an issue, please have started to also think of possible solutions. Meltdowns are fine, when appropriate, but we are going to gather ourselves after and find potential solutions. I am your manager and your leader, but not your mom. My two college age children have a similar rule now that they are becoming functional adult humans. I am here to help you problem solve and learn to become a more independent problem solver. It is hard to be a hands off parent and a hands off manager, but practice makes improvement. Don’t forget to celebrate the win when you find a solution together.


Listen when your team speaks up during calls. Are there projects or topics that catch their attention? When my email specialists feel stuck, I pull out a coding exercise that I have seen catch their interest and see if they can recreate the build. When a developer thinks they have mastered a new skill, I have another one waiting in the wings. I spend time every month making sure I have a backlog of skills for each team member prepared for when I see they need a nudge.

There are so many fun experimental pieces of content you all will likely never see in an NBCSports newsletter, but my team now has the skills to bring it up when we want to try something a little different to prevent subscriber fatigue.


Leave room for errors. When your team feels safe to experiment and make mistakes, their productivity increases as well. “But Corrina, we are in a time crunch” you say? This is where years of experience comes into play. I have created a library of common projects I have completed over the years. Any project that I know is going to stretch a team member, I have a backup version I can share as an example or that I can leverage to quickly build a solution alongside the team member. My team is not afraid to tell me they are overwhelmed and need me to show them how to complete a task. I do not get upset that they have been challenged beyond their comfort zone and need to ask for assistance. This is how we grow as a team and feel supported by each other.


Make learning fun for your team and acknowledge the work they are putting in to improve their value to the business. As I have already mentioned, I have a library of example and code snippets. I share those on a public location within the company so that anyone can go in and look for a skill they might want to learn. Not only do I have a library but some of the skills I have planted easter eggs within the account to acknowledge that someone is trying out one of my AMPscript blocks or SQL queries. Sport McSports fan has lots of fun data if you know how to find him with SQL. Did you forget to put in a fall back for your AMP, well then sheriff cat is here to remind you that there is additional content still to add.


Finally, and the one my team likes the most; have a Mary Poppins’ bag of some kind. Mine is literal since I only see them a few times a year. I usually come with gifts of some sort that I have picked up at industry or sporting events. I also have a figurative version. As in, I always have a positive bit of feedback handy when they need it most. If you don’t think you can have a bag of surprises, just reach out to your vendors. Most are more than happy to send your team swag on your behalf. If you want to get fancy, like me, keep an eye out for items on sale that you know would cheer up your team and then store it for a special day.


When I joined NBCSports I was provided 10 leadership principles for Operations and Technology. These 10 principles have helped me define my leadership principles as tangible and measurable objectives. There are a few that my team live by that I feel every team could leverage.

<ol class=“pl-9 list-decimal”> <li class=“leading-7”>Lead from the Front</li> <li class=“leading-7”>Be an Owner, not a Renter</li> <li class=“leading-7”>Win with Integrity</li> <li class=“leading-7”>Trust & Respect, the business run on it</li> </ol>

<br />

These are just a few of the ways I approach management and leadership. None of them have anything to do with coding, I can teach just about anyone to build an email or write AMPScript, but the way I treat them as their leader and mentor is what keeps them from burning out too quickly and looking for new opportunities.

Be on the lookout for a future post where I break down my learning library and give you an inside look at the materials I use to teach email development and Salesforce Marketing Cloud skills internally to build a team industry leading email specialists.

Read more
Corrina Cohen
Published 03/31/2023
Salesforce Announces Salesforce Marketing Cloud Constituent Engagement Cloud

As Salesforce continues to invest in delivering capabilities and features for specific industries and verticals, we’re pleased to share the all new Salesforce Marketing Cloud Constituent Engagement Cloud (SFMCCEC).

This is the second Salesforce foray in bespoke capabilities underneath the SFMC umbrella since the rebranding of Pardot to Salesforce Marketing Cloud Account Engagement. This suite of new capabilities brings you closer to your local area with enhanced data integrations through the power of Salesforce Genie Data Mulesoft Cloud. This newly renamed platform enables near real time integrations with security cameras, your local economy and with the power of Einstein GPT, global data can help identify problems in your district before your constituents are even aware of them.

But, what is the value of all of this data if you’re not able to act upon it? The all new Constituent Journey Builder empowers you to do more to your constituents with its additional unique flow control, messaging and experience management capabilities. These new tools ensure the people that you represent receive the care and attention that they deserve.

Introducing the new features:

Similar to the Wait until Event option within Salesforce Marketing Cloud ExactTarget Engagement Cloud for Non-Accounts, this allows your campaign management team to configure multiple potential distraction options.

When a constituent is in a Wait until Distracted position, any messaging from the individual is placed into the highest possible priority folder and locked away until they are ready to be engaged with.

Much like the Wait until Distracted, the Wait until Invested option puts the local community in a holding pattern until there has been sufficient investment made. This tool is great for managing high profile requirements where there is already significant investment, such as reducing the overwhelming burden of safety measures on rail-based logistics.

Many users of Marketing Cloud have become fans of Path Optimizer and Einstein’s excellent Send Time Optimization. However, Bombardment is an all new option that leverages all channels available within Salesforce Marketing Cloud. Whenever a prospective voter opens their device, Bombardment surfaces a Push Notification. Whenever they open an application on their phone or tablet, Bombardment serves an In App Message. Leveraging the power of Einstein, using the global panel data of Email Engagement data, Bombardment is guaranteed to be at the top of their inbox by sending your emails right when they open an email from any SFMC org feeding in to the global data.

With information so readily available across the globe, it’s important that you ensure the message you want constituents to see is the message that they see. Whilst Social Studio is falling out of the Marketing Cloud product suite, you’re able to integrate SFMCCEC with any platform that supports our easy to integrate API. If there is a moment where your message is not the one being discussed, create a diversion and bring people back to the point you’ve got to make.

But, engaging with your constituents is about more than just outreach and responding to the needs of those in your district. One of the key capabilities added to SFMCCEC is the uniquely positioned “Plead the Fifth" button.

Politics is an incredibly subjective field to be in and it’s well known that reading text presented on a screen can be open to interpretation. The all new “Plead the Fifth" button takes any risk of that happening out of the equation by enabling users to quickly and irrevocably remove content from their SFMCCEC account. This industry leading removal feature ensures that if there is any risk of misinterpreting messaging around campaign funding, specific social interactions or even just helping out a long term friend, it can be swiftly managed. The one click approach ensures that no matter if it’s 1, 2 or even over 30 individual instances can just be removed from your SFMCCEC account, enabling you to get on with the job at hand without distraction.

These capabilities are sure to streamline, simplify and supercharge your relationships with your constituents, through the power of Salesforce.

Read more
HowToSFMC
Published 05/22/2022
Summer '22 Release Notes Summary

As we rapidly approach the Summer ‘22 release, the optimism of 3 releases a year bringing bigger releases is being put to the test! Those of you who are in both the Salesforce CRM ecosystem and the SFMC ecosystem may have noticed that the Sales & Service Cloud release notes landed a few weeks before SFMC. Don’t expect this to change, release notes for SFMC will continue to be released between 2-3 weeks prior to the release. But, enough about the release notes launch scheduling - let’s get into the nitty gritty of the things you can expect to gain and the existing features you should be prepared to lose.

You’ll probably notice a few of these are just early warnings for the third and final release of the year. I’m guessing it’s been identified that giving people 2 weeks notice before a feature gets turned off that could completely break an org is probably a bad idea, so announcing it early does 2 jobs. It pads out the release and it gives customers further warning. Feel free to come up with your own Forward Looking Statement/Safe Harbor/That thing that every Salesforce presentation has joke to go in here. With the amount of things being retired or removed at the moment, a backwards looking statement may also be needed!

If you’re in an SFMC instance with data being sourced through the Marketing Cloud Connector, you’ll most likely be familiar with the option to use a field to filter which records get synchronised. (Really important to manage your Contact count in SFMC - if you’re not familiar with it already and you have too many contacts, read up on the subject!) As announced in this release, the Winter ‘22 release in October will be removing the ability to use a formula field from Sales/Service Cloud. Existing objects will persist, but if you need to modify or update them you won’t be able to do that with a formula field. You’ll need to get the field updated with APEX or SSJS or even Journey Builder if you need a code-free way to do it.

There’s a couple of changes on the way with AMPscript. No new capabilities or functions, but still important to be aware of.

For those who use nested AMPscript within subject lines, in February 2021 you’ll lose the ability to use nested AMPscript for this purpose. Currently, AMPscript gets two processing passes which enables you to derive a variable and then add it to a subject line. This isn’t just referenced in the release notes, but also has its own knowledge base article. Take a look and find out if you’re impacted by this!

If you’re using a multi-org connected SFMC instance, in order to prevent unauthorised access to data between business units, AMPscript will be restricted to the same connected app users. This ensures that the same data you see in a Data Extension within the Business Unit is what gets used to personalised and dynamically populate your content.

With the imminent end of life status of Classic Email, this release will see it moving to being a view only component of the platform. If you’re using anything Classic Email, you’ll need to migrate to Content Builder. All of the classic email studio/web studio tools will be fully retired in June 2022. This isn’t new, but the clock is ticking!

If you’re working in an instance with Intelligence Reports for Engagement Advanced, you’ll be able to leverage a number of Data Extensions to add audience attributes for context. The screenshots from the documentation suggest you’ll be able to use a number of data extensions and connect them to Datorama. These will then be available in the Queries module for you to reference. Queries module will also get the option to export results as a CSV in addition to OCR and PARQUET.

As the work continues with Package Manager, this release brings us:

  • More content types and assets
  • Update existing data extensions with the package configuration
  • The option to skip top level items in the package
  • Deploy complex packages using an Installed Package rather than loading the same zip files multiple times

It’s still sometimes tricky using Package Manager, so your mileage may vary. But, these are some much needed improvements to the product!

Mobile Studio continues to get enhancements and new features added, including some additional capabilities included in Salesforce CDP and behavioural triggered activities.

Leverage the power of Salesforce CDP to create MobilePush audiences, using the same process you would for any other channels by creating a segment and selecting Mobile App (MobilePush) as the contact point.

Users will be able to set up to 10 Push Message / 10 In-App Messages to be listened to for triggering activities. It’s unclear whether this limit is capped by Business Unit, App or Instance, so if you’re running multiple apps, you may need to keep an eye on that!

Throughout the release, there’s a rolling deployment of Mobile App Event and Exit criteria for Journey Builder, which the behavioural triggers mentioned above can leverage. One key thing to call out with these Entry Events is that there isn’t a Data Extension created like other Entry Events. If you do need that, you’ll need to come up with an appropriate solution for it.

You’ll also find Journey Builder is finally getting the In-App Messaging channel “Wait until Engagement” activity. Bit by bit, the “Wait until engagement” wait activity is taking over Journey Builder!

This release brings some new things released under the Einstein umbrella. Which apparently Google Analytics falls into now. Not sure I saw where that move got announced or the whole thing got brought together (Previously Google Analytics has been stored under Data Management in release notes). Key things to watch out for:

  • Einstein is getting some updates to include the impact of MPP on its predictions
  • Copy Insights subject line tester will identify different language factors impacting your subject lines, providing guidance on how to tweak your subject line
  • What-If Analysis for MobilePush and Email activities to predict future saturation to determine the optimal number of messages to maximise your on-target saturation level
  • Einstein Content Selection is also getting the ability to use local weather for content. It’s unclear whether this is an Einstein facelift of the old Live Weather Block.

Trigger File Drops from Cloud Storage

After announcing in August 2021 that it would be possible to import data from Amazon S3 storage, this release adds a beta option for Azure Blob Storage, boasting up to 10x faster transfer speeds. So if you’re using Azure Blob Storage for a data warehouse, you can sign up for the beta by visiting the registration page. But, the biggest change is the option to use files in these Cloud platforms to trigger automations. There’s mention of a Trigger API, it would be interesting to see if this extends to files in the Import folder on the SFMC EFTP!

If you’re on Stack 1 or Stack 4, there’s an EFTP Key rotation due on June 22. Update your keys so you’re not caught out by unexpected downtime.

On the whole, this release is slim and features mostly updates on the optional extras like CDP/Interaction Studio and Datorama (Intelligence) Advanced rather than the core SFMC platform. But, there are some glimmers of progress with things like the Triggering File Drops from Cloud Storage. Where now you may have to create Azure Data Factory Pipelines or AWS Transfers to the SFMC EFTP so you can trigger activities when a file lands, you may be able to leverage a new and as yet unrevealed API instead. Will this API mean that the workarounds currently being used for File Drops may not be needed?

Consider optimism somewhat tempered with this release. The couple of things that are going to be available for general availability for those who have bought the core platform are decent. But, if there’s ever a signal that Salesforce is heavily invested in the ancillary products - this is it!

Read more
Jason Cort
Published 03/31/2022
Introducing Salesforce Marketing Cloud - Dark Mode

Many of us in the Salesforce Marketing Cloud space have been battling through with enabling Dark Mode for our emails and our customers for the last couple of years. We’ve been battling through code and we’ve been doing it in the context of a bright white email development environment in Content Builder.

Dark mode is a great way to ensure customers are able to interact with your campaigns in a way that doesn’t create a jarring experience when you land in their inbox. With plugins and extensions to browsers to enable Dark Mode becoming more and more prevalent, we may be able to say farewell to our preferred hacky workaround and say hello to SFMC - Dark Mode.

We’ve managed to get hold of a previously unseen screenshot of an early iteration of what Dark Mode could look like within SFMC. This high contrast approach aims to be a little easier on the eyes than the default white we are all used to, but what remains to be seen is how will status messages like errors and success pop ups work in this new design?

Right now, there’s no clarity as to whether this push towards making dark mode available to SFMC users will extend to Content Builder emails enabling dark mode specific content options. But, with the relatively recent inclusion of the presentation tag in content builder emails, it’s definitely not off the cards! So for those of you who are using the drag and drop tool, you make sure you have a quick read of these excellent resources for dark mode emails from the wider community.

Email On Acid: Dark Mode for Email: What it is and How to Cope

Litmus: The Ultimate Guide to Dark Mode for Email Marketers

Once you’ve had a read on the do’s, don’ts and how to’s of dark mode email rendering, keep your ears peeled for a future release where maybe Salesforce will enable you to drag and drop in the dark for the first time.

Read more
HowToSFMC
Published 03/10/2022
job market
How To Not Get 'Burned' In Today's 'Hot Market'

The past couple years (2020 and 2021) have been crazy years, not just because of the pandemic and all the crazy things going on in the world - but also because of the job market. Email Marketing, especially in relation to Salesforce Marketing Cloud, careers have blossomed to unprecedented heights over this time.

This means we are all looking at opportunities and compensation options that we may not have seen for many years further in our careers. This sounds amazing right? Well it is - but as with anything, there is always stuff you need to watch out for to ensure you find what is best for you and not get blinded by the ‘shiny’ salary offers being thrown at you.

To that extent, HowToSFMC put together a quick Q&A panel to help address this and ensure we help people both job seekers as well as hiring managers find the best path forward to make a happy future. This panel included:

Amanda Turner - Moderator

Genna Matson - Retained Job Seeker

Heather McCullough - Job Seeker (current role)

Jessica Lewis - Job Seeker (no current role)

Aysha Marie Zouain - Hiring Manager (Brand Side)

Greg Gifford - Hiring Manager (Agency Side)

The goal of the panel was to give voice to as many sides and aspects of the job market as possible to ensure we pass the most relevant and impactful information.

Although the panel was not recorded, we wanted to provide a resource that housed this information for easy reference for those that may have missed it or want a refresher from the call.

This is the ‘hot take’ from each panelist on the current market. These sections are purely the opinion of the person speaking and are not representative of HowToSFMC’s thoughts or beliefs.

Genna Matson is a Salesforce Marketing Champion, former SF MVP, and a Marketing Cloud subject matter expert. She is very active in many communities and community groups as well as being one of the co-founders of HowToSFMC. She is currently 3x certified in Salesforce Marketing Cloud and is representing for someone that has stayed at their current job (although she has recently transitioned to a new role).

“Entry level curious self-starters will excel in the current environment. Those who prefer a fully structured training program to learn on might want to get exposure to trailhead before entering the resource pool.”

Essentially Genna is discussing how with this growth and huge demand in our market, onboarding and training are greatly impacted. A lot of times you will be left to essentially ‘figure things out on your own’ which can be jarring for many people and make them very uncomfortable. So for those that are not comfortable with a more ‘trial by fire’ environment, you may want to do more preparation for the role prior to diving in.

Genna goes on to state: “Remember that your skills are highly valuable and in the business world you have to be loyal to yourself before anyone else or you will not get your full value and set others coming behind you at a disadvantage on entry.” Although other context is hugely important, Genna emphasizes you need to focus on getting yourself in at a level that is matching with your value. Coming in low can not only harm you, but those that come in after you.

Genna gives us one last piece of advice: “There are lots of communities in this ecosystem; Trailhead, Localized and Virtual Community Groups, HowToSFMC, StackExchange, EmailGeeks, Women of Email, Women of MarTech and the list goes on. 90% of community members LOVE helping and assisting and providing guidance and membership. Please reach out, to me personally @gemliza (twitter) on all the things, or to any community member at anytime about anything. Helping you, watching your development and success brings us joy.”

Heather is a current SF Marketing Champion and is 4x certified in Salesforce Marketing Cloud. She has had over 20 years of experience in the industry and is currently working as a Salesforce Marketing Cloud Architect.

Heather was the representative to the job seeker that transitioned into another job from their current position. In that vein, she wanted to share a few tips on what to do to ensure you make the right decision:

Research the company you’re interviewing with - Glassdoor or Comparably (or similar)

Highly recommend taking notes when interviewing with multiple companies at the same time

Write down your questions, don’t let excitement of the interview make you forget them.

Make sure to ask the most important subjective questions of each person you interview with. It can add some color for your decision making process down the road

Then, be honest with yourself when evaluating a job offer, evaluate it against your goals. If you love the company but it’s going to take you in the wrong direction career-wise, then maybe it’s not the best move.

Heather further expands by saying: “The good news is that right now you have options, keep asking for what you’re worth and if you don’t find what you’re looking for right away just give it some time.”

Jessica is a certified Email Specialist and Salesforce Ranger. She has worked across many different career paths and many different focuses - but ultimately settled on Salesforce Marketing Cloud. She is currently working as a Manager of Product Operations.

Jessica was representing a job seeker that did not currently hold another position. She was able to find a wonderful opportunity right before the event - but that, in our opinion, further validated her points of views to show the success of her approach.

Jessica cited her two-month journey as a full-time job seeker in this market and how that journey affected her. Right around December of 2021 the job she previously held was eliminated as part of a staffing reduction.

This was not the holiday season she was expecting and certainly she was not prepared to be re-entering the job market. Now, she recounts us a few unique experiences and shares a very small sample of what she did, what she did not do, what she should have done, and what she should not have done during her time on the market.

“I want to start by telling you about a job offer I received.,” Jessica begins. “This was the first offer extended to me, and I’d had an amazing conversation with the hiring manager. I was completely sold on working with her and her team, but I still didn’t know what the company actually did. This was a yellow flag to me personally because I have previous experience working for companies that were hard to pin down, and those jobs did not end well for me.” She states that the decision not to take this job could be considered a “it’s not you, it’s me” situation.

“So what did I do to help me make a decision?” she asks. “I found people on LinkedIn who had this company listed as a previous employer, and I contacted them to ask about their experience.” She then tells about her surprise that people actually replied and that although they had different jobs than what she was applying for, the themes they mentioned about the company were common.

“This leads into the thing I didn’t do, which was accept an offer I wasn’t excited about as a whole.” she said. “I have the tremendous fortune to finally be on a career path that is in demand, and I have the privilege of choice.” She goes on to talk about how hot the market is, stating she applied for 84 jobs and had 82 interviews over a 37-day period!

The thing she says she “should have done” is be firm about “getting a budgeted salary range for each opportunity before the first call.” Although she had little trouble with this, the salary transparency is a major factor in which phone screens one should take. She says the most important thing is to not “move forward with job opportunities that did not offer competitive pay.” The market is a seekers market and there is no need for the seeker to diverge from the market pay they deserve.

“I’m not proud to tell you I kept going in a few opportunities I knew I wouldn’t accept if they were offered to me because of the salary. They made me feel I had options when my last job was taken away.” Jessica stated.

She concludes her advice by stating: “I think my last job should have given us free therapy sessions after the layoff!.”

Aysha Marie Zouain is currently a Senior Manager of Marketing Automation with over 10 years of experience in email marketing and general digital marketing. Over the last few years, she has developed a specialization in SFMC, but has worked across multiple platforms and functions over her career. She holds the SFMC Email Specialist certification and is an SF Marketing Champion. She is the representative for a hiring manager from the brand side (non-agency) and is also a bilingual Spanish speaker and is of mixed Dominican and Lebanese heritage.

Aysha started off discussing her thoughts on recruiters. “Many are quite decent though its like a coin toss, you never know what you may get.” She goes on to state that recruiter quality is very inconsistent, even in major recruitment againes: “…agencies like Robert Half and Creative Circle, I’ve had great experiences while some of my peers have not.”

So if recruiters are not the best source, where else can you go? Aysha shares this: “Generally I find listings in email geeks community, emailgeeks.io, women of email and LinkedIn…” these along with networking and word of mouth as you grow into your place in the community.

Once you have found a candidate pool, then Aysha recommends you start discussion on the “potentials about organization, lunch and learns, education and conference budget.” She continues: “I make sure to have at least 1 and if possible 2 a year since conferences have been crucial to me professionally as I move into higher stations and wished it had been available to me before as an employee of larger corporations.” This is not a common practice in other jobs and may help differentiate your offer from the offer at other companies for the candidate.

Now you have a candidate and you need to make sure they are the right fit…how? “Ask specific nuanced platform questions that only professionals would know about.” She then elaborates: “Some of my favorites are as follows: Why are lists used instead of data extensions? Why are data extensions better? What is the difference between personalization strings and AMPscript logic? What would you do if Automations stops working?” By digging into knowledge that is not clearly defined or ‘memoizable’ helps to weed out those that actually know the platform and those that are just trying to get inside the door.

Lastly, she tackled the compensation and salary issue. She says that in today’s market it is hard to find budget to account for this salary growth, but through working with those that are higher up in the organization and reviewing the budget prioritization they have made great strides in becoming more ‘on market’ than many.

It is not something, especially at a larger organization, that can be easily done - so there are many pitfalls you need to be aware of as you join some of these large places that do offer that salary. Most likely there is a wage gap between those that are current employees and those that were hired in - which can cause major issues as time goes on.

So if there is just no way to get budget, what do we do? “Hire contractors - which makes me a bit sad since the work only continues to pile on as practitioners.” Aysha shares that in the current market, the short term solution is the smart solution. The temporary help will get the job done without significant long term impact to budget.

One last word of advice that Aysha wanted to share, “Always counter [on an offer] and ask “Is this negotiable” and let the employer respond. Professionally, I respect those who do more.” She continues: “Personally it shows me that you care about your bottom line as much as working for your future employer.”

Greg is a SF MVP, Marketing Champion and 4x certified SFMC Subject Matter Expert. He has his own SFMC focused blog, Gortonington.com, and is very active in many communities, such as EmailGeeks, Salesforce StackExchange as well as a speaker at multiple events and community groups. He is a co-founder of HowToSFMC and has a book, Automating Salesforce Marketing Cloud, that is set to be released in May of 2022.

Greg is representing the perspective of the hiring manager on the agency side. He starts off stating: “I must say that the past couple years have been the most chaotic years from a resourcing perspective I have ever seen. With such a boom in the industry, the amount of work coming in has exponentially increased, which is awesome, but it also true for most other companies as well. This means everyone is scrambling to get in new resources.” He continues “At first this was great as it was pulling in people that needed the jobs and having most of the market resources being hired.” But, as those resources were all hired, it very quickly turned to existing employees transitioning to new roles instead. Greg continued by saying “This causes chaos for those managers as they now not only need to hire for the new work, but also find a comparable resource to fill the resignation.”

It is at this point, he stated that he felt the salary inflation began. People were so desperate to bring in new people that they started throwing money at the problem in hopes to draw in new resources away from their current employers. This led people to “get new opportunities they might not have been able to have for years - further escalating needs and compensation.”

“The problem this leads to is sustainability.” He begins. Further expanding that although it is amazing to have the power as a job seeker to get these awesome opportunities, as a manager we need to think beyond the ‘now’ and think about the future. Offering the crazy salaries can bring in great talent and produce great things now, but when the market ‘cools off’, you are going to be left with a negative budget - which no company will enjoy. “Cooling off of incoming work means you now need to cut down your cost - usually meaning termination and reduction of your team.” he states. “Which is, in my opinion, a very short-sighted type of behavior and not at all a way to appropriately grow a team.”

This can lead to giving your company a bad reputation when layoffs come around, meaning that in the long run you are going to be losing all that great talent and making it harder for you to get in new talent. “Then comes to the issue of - how do I retain/hire at a sustainable rate while the market is so crazy?” he asks. “The answer - a lot of luck and a lot of guesswork.” There is no definitive game plan, as this type of situation has not really been seen before in this market. “You need to gamble…ALOT” he further expands. “The most important thing to do is to create an environment of transparency and have the conversation with your team about their future and ensure that they have all the facts in hand when they are presented with these outside opportunities.”

Greg states that he feels the market has reached its peak and that over the next year or so it will begin to decline. “This means that some of those ‘too good to be true’ jobs will have a very limited lifespan.” Greg states. He goes on to talk about how at this point taking a new job could be even more risky than it was before. Starting a new job puts you as the least senior, and usually the last person in tends to be the first person out. This on top of the likelihood of your compensation being higher, means that you are a more significant cost in comparison to others - reducing your overall value to the organization.

“Losing your new job can become a very high risk endeavor.” he says. “If you do not prepare for it, you can find yourself in a position where jobs are hard to come by and anything available is far lower than what you were originally were making.” He states this is because when the market cools, many very talented people will be looking for jobs during a time when there are only a few jobs out there. His final advice for the job seeker is to “put in as much if not more effort into every interview and job opportunity you get as does the hiring manager. This is to ensure the opportunity is something that fits with what you want, not just in the type of job, but in job security, longevity and future career pathing.”

He wanted to emphasize that this is not to say that you should not explore new opportunities nor ask for what you are worth - just be careful and considering when looking at them. There are still many awesome opportunities out there and sometimes even taking a job with a short lifespan can lead to greater things in your career later. “I just would emphasize that you need to do your homework and get the full picture on these opportunities, not be blinded by the shiny new salary they are throwing out.”

To help keep this write-up succinct and relevant, we will only be displaying a few of the questions and answers here. Most of the rest were answered via the above overviews provided by our panelists.

Where should a ‘day 0’ job seeker go to ramp up and learn in order to get a Salesforce Marketing Cloud position in this market?

Honestly, that really would depend on what type of role in SFMC you are looking for. If you are looking for a more technical or developer type route, you should likely look towards Salesforce Stack Exchange along with the technical blogs out there like MateuszDabrowski.pl, ampscript.xyz, sfmarketing.cloud and gortonington.com. These combined with utilizing Trailhead as an overview and the official docs will help get you there. To supplement this as well, you could look at the ampscript.guide and the wonderful new tool by Pato Sapir, MC Snippets - which lets you utilize AMPscript and SSJS without requiring you to have an SFMC instance. It is not all-encompassing and is still in Beta, but is a huge step in the right direction to help day 0’ers level up to day 1.

For those focused more on admin and strategy, you will likely need to dig into more over-arching resources focusing on things like integrations, ETL strategies, Digital Messaging and Journey optimization and efficiencies. There is no specific places that I can highly recommend for this as usually these come from a more general knowledge area and then get specialized via research on the specific cloud.

Also, joining communities such as HowToSFMC, EmailGeeks, Women of Email and others is a great way to get exposure to people that are not only already doing this day in and day out, but are excited to help and mentor people. These communities are probably one of the greatest accelerators for careers in Marketing Cloud.

Is it risky to join a company that has not much Marketing Cloud history? Or could this bring some advantages as well?

Oh very much risky - but as you alluded to in the question it also can bring some huge opportunities as well. Becoming a ‘founding’ member to a company’s Marketing Cloud practice can sky-rocket your career and get you tons of great opportunities to grow and expand as the practice grows. The risk is though that maybe the practice will not do well or is not valuable enough to the company and they decide to just trash the whole thing and there is no longer ANY Marketing Cloud jobs there and your entire team is now jobless.

Or even worse, you could do all this effort to build things up and grow the team, and the company sees a more ‘renowned’ subject matter expert is on the market and they bring them over you. He then creates a ceiling for you and receives all the credit for the growth you made. Which can very easily stunt your career.

Long story short, joining a newly built practice is like joining a startup - there is really only two ways it is gonna go: really good or really bad. There are very few times it is anything in-between.

I just passed my email specialist exam and have limited hands-on experience through my training class, but I lack paid experience. What can I do to help my LinkedIn profile and resume stand out to help land my first Marketing Cloud job?

This is the main issue a lot of day 0’ers face. You get all the training and certifications, but the job market is usually looking for a minimum of 1 year practical experience. How can you get experience if no one will hire you without experience?

The recommended approach here is to look at volunteer work or start some free-lancing or contract work. Usually you will be paid pretty poorly and will be treated not so great while doing some pretty boring and monotonous work, but it is a great way to get that experience. While also showing your capability to work in a fast paced environment with constant need changes and requirements which will be very appealing to many hiring managers. That on top of the fact you would be a ‘self starter’ and show drive and passion as without those, you would never get work as a freelancer or contractor.

That as well as applying for more entry or junior level jobs and focusing your interview on not what you currently know, but how you are learning and your capability and drive to learn more and put in the extra efforts to meet and exceed expectations. In junior roles, the drive and willingness to learn and grow are greater factors in deciding who to make an offer to than the existing skillsets.

The major focus and take-away that can be gathered from these panelists and their comments is to not let the market take advantage of you and to be careful in what you are doing in your career, whether it is looking for a raise in your current position, or seeking a new job. The market is hot, which means it is ripe with opportunity, but it also means its volatile and can burn you and limit your career growth as well if you are not careful.

The major take-away points would be:

Do your research on the market and fair rates

Do your research on each opportunity that comes your way. This includes the company, the job, the culture and more.

Negotiate. What is presented is usually not the top offer - just negotiate with the understanding that the company is not made of money so it needs to be a give and take of sorts to find the fair value.

Sustainability should be the major focus for hiring managers - spending high budgets on new talent can easily burn your team and put you in a much worse spot in the future

Consider seniority and longevity in the current or new position as once things cool down, there will be a large pool of talent out there with very little jobs - meaning compensation will lower and expectations will increase due to supply and demand.

Thank you all for your participation in this Q&A panel and big thank you to the panelists that spoke during it. If anyone has further questions they want to ask, please feel free to join the HowToSFMC.com Slack Community where there are tons of Salesforce Marketing Cloud users of all levels that will be able to get you the answers you need!

Read more
HowToSFMC
Published 03/01/2022
Spring ‘22 SFMC Release Notes

Welcome to 2022 and our first of 3 releases this year (Yes! Only 3 releases from now as Salesforce has brought the SFMC release frequency into alignment with the CRM Platform), the optimist within me is hoping that this means we’ll get some bigger and more complete features as a result of this change. That said, the cynic in me wonders if the reason the release schedule has been reduced is because there’s not going to be as much coming out. But, let’s be optimistic!

As many of us will be aware, Discover reporting is going away (you’ve only got a few weeks now. 1st April and it’ll officially be retired), so if you’re someone with Datorama for Marketing Cloud in your org, make sure you’ve got your reports migrated. The delta between Datorama and Discover reporting capability is getting smaller again with this release. Key changes are some new measures have been added to bring closer parity between the two reporting tools and finally email activities that have been configured to be suppressed from reporting are actually suppressed from reporting!

MobilePush and its SDK has had some new capabilities in the new release. But, before getting into the details of the SDK, the whole thing has had a rebrand. It’s no longer the MobilePush SDK, it is now the Engagement SDK. The change is as a result of the merging of the MobilePush/Salesforce CDP SDK. The aim for this is to streamline implementation with universal methods between the two.

In-App and Push both get a range of new Journey Builder capabilities. You’re now able to trigger journey entry for either of these activities. You’ll be able to listen for events on up to 10 Push/In-App Messages to bring someone into a journey. There is also the option for using these events as journey exit criteria, this is going to be rolled out throughout March so don’t worry if it’s not in your org already!

A couple of other developments to call out

  • In-app is brought in line with Push with the wait until event flow control option
  • Android 12 Deep Linking App Link Verification is available for configuration in Setup

We’ve had a few releases with Classic Email Studio web tools being retired and the schedule around that. You have until June 2022 to get your content migrated and become familiar with the new tools. If you’re in an org with Classic Editor pages you should see these moved to Content Builder and the legacy experience has been removed with all cloud pages moved to the new Cloud Pages experience.

There has been some quality of life changes in the new Cloud Pages experience including:

  • Code resources now being created with Content Builder and can be unpublished
  • Advanced Settings & Unpublishing is now available in the gear/cog menu with the page details
  • After much frustration, when publishing a Cloud Page the new page URL is now clickable immediately rather than needing to copy and paste it
  • Display and sorting preferences now persist
  • It’s now possible to unpublish a Microsite

Einstein is getting a few nifty features and consistencies between Journey Builder and non-Journey Builder sending. For those who are using Send Time Optimisation in Journey Builder, you’ll have this feature available to you in Automation Studio Send Email Activity. So if you’ve not been enticed to Journey Builder for some of the Einstein capabilities, little by little they’re moving into non-Journey Builder options.

Momentum analytics is a nice feature to see for Email Engagement Scoring. Seeing how your contacts are performing over time is helpful, previously if you were interested in this you could do that with Journey Builder + Update Contact Journey Activities to track this over time. So, it’s nice that it’s now something you can see at an aggregate level without additional effort. Along with the momentum analytics the model card has been updated to share additional insight to how Einstein is making its decisions.

Content tagging and selection gets some attention including multiple criteria and multi asset attribute values spotlighting with the Einstein Content Selection content builder block. So you’ll be able to make your spotlight more broad with OR logical operators or more specific with AND filtering. If you’re using Einstein Content Tagging, you’ll be pleased that you can add some nested tags. In order to enable this, an admin will need to disable current Einstein Content tags, delete them and then it may take up to 24 hours to complete - then reactivate. If you’re using Einstein Content Selection for anything, make sure you check out the API that now includes the Daily Log reporting endpoint.

There’s a few things that are worth calling out but don’t really fit into other categories and aren’t necessarily huge changes to warrant a whole topic. But,

  • SMS is getting some new Status Codes in the MobileConnect API. You can check them out here
  • Email Form Shared Items are no longer available in response capture options. There were reports of inconsistent behaviours between different stacks, so the decision made was to remove that capability.
  • Javascript Web Tokens are now able to be generated with AMPscript and work within the context of OMM rendering engines (so you’ll be able to use it in messages and landing pages). It feels a bit odd to put this in the list of small changes, but there is so little documentation about this yet to add much more!
  • Enhanced Personalisation flow for Quick Sends from Sales/Service Cloud, various additional attributes are appended and available for the Journey Entry Source when using Case/Opportunity/Lead records as the source.
  • Package Manager will now allow you to update existing assets rather than create new assets each time. You’ll have the choice when deploying a package.
  • S3 File Locations config has some changes to bring parity between naming SFMC and AWS.
  • File Locations REST API will now allow you to change S3 and External FTP credentials as well as your usual creating/managing and deleting file locations for file transfers.

Overall, this release includes some interesting new capabilities but it feels like the product managers could do more to highlight the capabilities. For example, the opportunities with JWT could be pretty significant but all we have in the release notes is less than 45 words and it doesn’t link through to any other documentation or use cases. This is brand new to SFMC and should enable all kinds of interesting examples of what can be done. So if there’s anyone on the product team who would be interested in sharing some additional information for use cases, ideas from internal use cases etc. do reach out to us!

For the first release of 2022 and the first of the 3 releases per year vs 5 per year, it feels conservative in nature. I hope the release in June pushes the output a little harder!

Read more
Jason Cort
Published 12/10/2021
SOAP
12 Days of Scriptmas - 2021

As 2021 draws to a close, it’s time to open up the door on the 2021 edition of the HowToSFMC Scriptmas tradition where the team and the community share some of their top scripts, tips, hacks and workarounds.

New for 2021 we’ll be including some specific API functionality that may otherwise go unnoticed or under utilised.

Every day in the lead up to the big day, starting on Monday 13th December up until Christmas itself, we’ll be revealing one scripted piece of goodness for you to grab and use to your heart’s content for 2022 and beyond.

On the first day of Scriptmas Lesley Higgins gave to me… An awesome utility for validating an email address within SFMC.

All you need to do is pop in your Installed Package credentials and an email address and you’ll find out if the email address has a valid Syntax, MX Record attached or if it’s going to be rejected out through ListDetective! Winner.

<details> <summary>Click here to see the Day One Script</summary>

<script runat="server">
  Platform.Load("core","1.1.5");
  try {
    var email = "email@example.com"
    
    var authEndpoint = "your auth endpoint"
    var clientId = "your clientId"
    var clientSecret = "your clientSecret"
    var payload = {
      client_id: clientId,
      client_secret: clientSecret,
      grant_type: "client_credentials"
    };
    var url = authEndpoint + '/v2/token'
    var contentType = 'application/json'
    var accessTokenRequest = HTTP.Post(url, contentType, Stringify(payload));
    if (accessTokenRequest.StatusCode == 200) {
      var tokenResponse = Platform.Function.ParseJSON(accessTokenRequest.Response[0]);
      var mcAccessToken = tokenResponse.access_token
      var rest_instance_url = tokenResponse.rest_instance_url
      };
    if (mcAccessToken != null && email != null) {
      var headerNames = ["Authorization"];
      var headerValues = ["Bearer " + mcAccessToken];
      var jsonBody = {
        "email": email,
        "validators": [
          "SyntaxValidator",
          "MXValidator",
          "ListDetectiveValidator"
        ]
      }
      var requestUrl = rest_instance_url + "/address/v1/validateEmail";
      var validateEmail = HTTP.Post(requestUrl, contentType, Stringify(jsonBody), headerNames, headerValues);
      var aRes = Platform.Function.ParseJSON(validateEmail.Response.toString());
      var valid = aRes.valid;
      if (valid) {
        var status = true;
        var message = "success";
      }
      else {
        var failedValidation = aRes.failedValidation;
        var status = false;
        var message = "Please enter a valid email address";
      };
    }
    else {
      var status = false;
      var message = "server error";
    };
    var response = {
      "ok": status,
      "message": message
    }
    Write(Stringify(response));
  }
  catch (e) {
    Write("<br>" + Stringify(e))
  }
</script>

</details>

Huge thank you to Baby Shark AMPscript competition Honourable mention, Lesley Higgins for this super useful script!

On the second day of Scriptmas Greg Gifford gave to me… A robust way to find the first date of a given day of the week of next month. So, whether you have events on the first Friday of the month or if you update promotional offers on the 2nd Wednesday of the month, you can grab this snippet and adapt to your hearts content! No more manually reading calendars and working it out by hand!

<details> <summary>Click here to see the Day Two Script</summary>

%%[
set @dayStr = "Tue,Mon,Sun,Sat,Fri,Thu"
/* Order and name of days included will determine what day of week (DOW) you are looking for */
/* You will want to exclude the DOW you are looking for from string and then go backwards of week in listing days */

/* Used in FOR statement for setting Day Shift */
set @dayRS = BuildRowSetFromString(@dayStr,",")

/* Setting up date pieces for next month */
set @now = now()
/* set @now = "2021-03-20" */
set @nextMonth_Date = dateadd(@now, 1, "M")
set @nextMonth_Month = DatePart(@nextMonth_Date, "M")
set @nextMonth_Year = DatePart(@nextMonth_Date, "Y")

/* Setting date to first of next month and then getting the day of week (e.g. Mon, Tue, etc.) */
set @firstOfnextMonth_Date = dateparse(concat(@nextMonth_Month,"/01/",@nextMonth_Year))
set @dayOfFirst = FORMATDATE(@firstOfnextMonth_Date,"DDDD")

IF @dayOfFirst == "Thu" THEN
  SET @day_Shift = "-1"
ELSE
  /* Default day shift of 0 meaning that the first is the correct day */
  SET @day_Shift = 0

  /* FOR loop to get the day shift to find first date matching day */
  FOR @i = 1 to Rowcount(@dayRS) DO
    set @row = Row(@dayRS,@i)
    set @dayVal = Field(@row,1)
    if @dayOfFirst == @dayVal then set @day_Shift = @i ENDIF 
  NEXT @i
ENDIF

/* Calculate actual date of first occurance of selected Day of Week */
SET @FirstWedOfMonth = DATEADD(@firstOfnextMonth_Date,@day_Shift,"D")
]%%

%%=V(@FirstWedOfMonth)=%%

</details>

Huge thanks for Greg Gifford for sharing this super useful snippet of code, so many use cases for this!

Some people may tell you that the best things come in small packages, but here at H2 we know that isn’t always the case.

So without further ado - On the third day of Scriptmas, Genna Matson gave to me… A quick and reliable way to create Data Extensions for SFMC Data Views. Grab this snippet of code and never worry about having to do the same old task every time you set up a business unit ever again!

<details> <summary>Click here to see the Day Three Script</summary>

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

var debug = 0;
var bizUnit = 10000000; // << business unit <<


var rootFolder = 123456; // << enter categoryId for top level in BU <<

//Create new folder
var folderCustomReports = "CustomReports_" + bizUnit
var folderDataViews = "DataViews_" + bizUnit

var newFolder = {
  "Name" : folderDataViews,
  "CustomerKey" : folderDataViews,
  "Description" : "Data Views",
  "ContentType" : "dataextension",
  "IsActive" : "true",
  "IsEditable" : "true",
  "AllowChildren" : "false",
  "ParentFolderID" : rootFolder
};
var folderStatus = Folder.Add(newFolder);

var viewsFolder = Folder.Retrieve({Property:"Name",SimpleOperator:"equals",Value:folderCustomReports});
var viewsFolderID = viewsFolder[0].ID;

if (debug == 1) {
  Write('<br>folderStatus: ' + Stringify(folderStatus));
  Write('<br>viewsFolderID: ' + Stringify(viewsFolderID));
}


var prox = new Script.Util.WSProxy();

// create DataView Report DEs
var dataViewRpt1 = bizUnit + "_DV_Unsubscribe";
var dataViewRpt2 = bizUnit + "_DV_Complaint";
var dataViewRpt3 = bizUnit + "_DV_Open";
var dataViewRpt4 = bizUnit + "_DV_Bounce";
var dataViewRpt5 = bizUnit + "_DV_Sent";
var dataViewRpt6 = bizUnit + "_DV_Click";
var dataViewRpt7 = bizUnit + "_DV_Job";

// Unsub
var de1 = {
  "CustomerKey": dataViewRpt1,
  "Name": dataViewRpt1,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "OYBAccountID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "ListID",
    "FieldType": "Number"
  },
  { "Name": "BatchID",
    "FieldType": "Number"
  },
  { "Name": "SubscriberID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "SubscriberKey",
    "FieldType": "EmailAddress",
    "IsRequired": true
  },
  { "Name": "EventDate",
    "FieldType": "Date",
    "IsRequired": true
  },
  { "Name": "IsUnique",
    "FieldType": "Boolean"
  },
  { "Name": "Domain",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
    }
  ],
    "CategoryID": viewsFolderID
};

// Complaint
var de2 = {
  "CustomerKey": dataViewRpt2,
  "Name": dataViewRpt2,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "OYBAccountID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "ListID",
    "FieldType": "Number"
  },
  { "Name": "BatchID",
    "FieldType": "Number"
  },
  { "Name": "SubscriberID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "SubscriberKey",
    "FieldType": "EmailAddress",
    "IsRequired": true
  },
  { "Name": "EventDate",
    "FieldType": "Date",
    "IsRequired": true
  },
  { "Name": "IsUnique",
    "FieldType": "Boolean"
  },
  { "Name": "Domain",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
    }
  ],
    "CategoryID": viewsFolderID
};

// Open
var de3 = {
  "CustomerKey": dataViewRpt3,
  "Name": dataViewRpt3,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "OYBAccountID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "ListID",
    "FieldType": "Number"
  },
  { "Name": "BatchID",
    "FieldType": "Number"
  },
  { "Name": "SubscriberID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "SubscriberKey",
    "FieldType": "EmailAddress",
    "IsRequired": true
  },
  { "Name": "EventDate",
    "FieldType": "Date",
    "IsRequired": true
  },
  { "Name": "IsUnique",
    "FieldType": "Boolean"
  },
  { "Name": "Domain",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
    }
  ],
    "CategoryID": viewsFolderID
};  

// Bounces
var de4 = {
  "CustomerKey": dataViewRpt4,
  "Name": dataViewRpt4,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "OYBAccountID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "ListID",
    "FieldType": "Number"
  },
  { "Name": "BatchID",
    "FieldType": "Number"
  },
  { "Name": "SubscriberID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "SubscriberKey",
    "FieldType": "EmailAddress",
    "IsRequired": true
  },
  { "Name": "EventDate",
    "FieldType": "Date",
    "IsRequired": true
  },
  { "Name": "IsUnique",
    "FieldType": "Boolean"
  },
  { "Name": "Domain",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "BounceCategoryID",
    "FieldType": "Number"
  },
  { "Name": "BounceCategory",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "BounceSubcategoryID",
    "FieldType": "Number"
  },
  { "Name": "BounceSubcategory",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "BounceTypeID",
    "FieldType": "Number"
  },
  { "Name": "BounceType",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "SMTPBounceReason",
    "FieldType": "Text",
    "MaxLength": 2000
  },
  { "Name": "SMTPMessage",
    "FieldType": "Text",
    "MaxLength": 2000
  },
  { "Name": "SMTPCode",
    "FieldType": "Number"
  }
  ],
    "CategoryID": viewsFolderID
};  

// Sent
var de5 = {
  "CustomerKey": dataViewRpt5,
  "Name": dataViewRpt5,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "OYBAccountID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "ListID",
    "FieldType": "Number"
  },
  { "Name": "BatchID",
    "FieldType": "Number"
  },
  { "Name": "SubscriberID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "SubscriberKey",
    "FieldType": "EmailAddress",
    "IsRequired": true
  },
  { "Name": "EventDate",
    "FieldType": "Date",
    "Ordinal" : 2,
    "IsRequired": true
  },
  { "Name": "IsUnique",
    "FieldType": "Boolean"
  },
  { "Name": "Domain",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
    }
  ],
    "CategoryID": viewsFolderID
};  

// Click
var de6 = {
  "CustomerKey": dataViewRpt6,
  "Name": dataViewRpt6,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "OYBAccountID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "ListID",
    "FieldType": "Number"
  },
  { "Name": "BatchID",
    "FieldType": "Number"
  },
  { "Name": "SubscriberID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "SubscriberKey",
    "FieldType": "EmailAddress",
    "IsRequired": true
  },
  { "Name": "EventDate",
    "FieldType": "Date",
    "Ordinal" : 2,
    "IsRequired": true
  },
  { "Name": "IsUnique",
    "FieldType": "Boolean"
  },
  { "Name": "Domain",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "URL",
    "FieldType": "Text",
    "MaxLength": 2000
  },
  { "Name": "LinkName",
    "FieldType": "Text",
    "MaxLength": 2000
  },
  { "Name": "LinkContent",
    "FieldType": "Text",
    "MaxLength": 2000
  }
  ],
    "CategoryID": viewsFolderID
};  

// Job
var de7 = {
  "CustomerKey": dataViewRpt7,
  "Name": dataViewRpt7,
  "Fields": [{
    "Name": "AccountID",
    "FieldType": "Number"
  },
  { "Name": "AccountUserID",
    "FieldType": "Number"
  },
  { "Name": "JobID",
    "FieldType": "Number",
    "IsRequired": true
  },
  { "Name": "EmailID",
    "FieldType": "Number"
  },
  { "Name": "FromName",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "FromEmail",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "SchedTime",
    "FieldType": "Date",
    "Ordinal" : 2
  },
  { "Name": "PickupTime",
    "FieldType": "Date",
    "Ordinal" : 2
  },
  { "Name": "DeliveredTime",
    "FieldType": "Date",
    "Ordinal" : 2
  },  
  { "Name": "EventID",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "IsMultipart",
    "FieldType": "Boolean"
  },
  { "Name": "JobType",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "JobStatus",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "ModifiedBy",
    "FieldType": "Number"
  },
  { "Name": "ModifiedDate",
    "FieldType": "Date",
    "Ordinal" : 2
  }, 
  { "Name": "EmailName",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "EmailSubject",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "IsWrapped",
    "FieldType": "Boolean"
  },
  { "Name": "TestEmailAddr",
    "FieldType": "EmailAddress"
  },
  { "Name": "Category",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "BccEmail",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "OriginalSchedTime",
    "FieldType": "Date",
    "Ordinal" : 2
  },
  { "Name": "CreatedDate",
    "FieldType": "Date",
    "Ordinal" : 2
  },
  { "Name": "CharacterSet",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "IPAddress",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "SalesForceTotalSubscriberCount",
    "FieldType": "Number"
  },
  { "Name": "SalesForceErrorSubscriberCount",
    "FieldType": "Number"
  },
  { "Name": "SendType",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "DynamicEmailSubject",
    "FieldType": "Text",
    "MaxLength": 500
  },
  { "Name": "SuppressTracking",
    "FieldType": "Boolean"
  },
  { "Name": "SendClassificationType",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "SendClassification",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "ResolveLinksWithCurrentData",
    "FieldType": "Boolean"
  },
  { "Name": "EmailSendDefinition",
    "FieldType": "Text",
    "MaxLength": 50
  },
  { "Name": "DeduplicateByEmail",
    "FieldType": "Boolean"
  },
  { "Name": "TriggererSendDefinitionObjectID",
    "FieldType": "Text",
    "MaxLength": 50
  },

  { "Name": "TriggeredSendCustomerKey",
    "FieldType": "Text",
    "MaxLength": 50
  }
  ],
    "CategoryID": viewsFolderID
}; 

var res = prox.createBatch("DataExtension", [ de1, de2, de3, de4, de5, de6, de7 ]);

</script>

</details>

A massive thank you to Genna Matson for this super useful script! Grab it, use it, save yourself some time!

We may all be counting down the seconds to the big day, but sometimes you don’t need to worry about that… So - on the fourth day of Christmas, Rafal Wolsztyniak gave to me - A nifty trick to remove seconds from a timestamp!

<details> <summary>Click here to see the Day Four Script</summary>

Select Cast(a.dateField as DateTime2(0)) as DateTimeWithSeconds
, Concat(Year(a.dateField), '-', Right(Concat(0, Month(a.dateField)), 2), '-', Right(Concat(0, Day(a.dateField)), 2), ' ', Right(Concat(0, Datepart(hour, a.DateField)), 2), ':', Right(Concat(0, Datepart(minute, a.DateField)), 2)) as CustomDateTimeWithoutSeconds
From dataSet a

</details>

Big thanks Rafal for this useful little snippet! When you don’t need seconds, this is a great way to simplify your data!

On the Fifth day of Scriptmas Jason Cort gave to me… FIIIVE GOLD…Wait, we did that joke last time. Dangit. Well, he gave us a useful little script to see how your automations have performed over time! Just pop the names in and add in your MID and you’ll be able to see what’s happened! (Great for when you come back after Christmas and don’t want to check everything manually!)

<details> <summary> Click here to check out the Day Five Script</summary>

<table>
  <tr>
    <th>AutomationName</th>
    <th>StartTime</th>
    <th>Status</th>
  </tr>


<script type="javascript" runat="server">
Platform.Load("Core","1.1.1");
    var mid = ""; // Put the MID in here
    var prox = new Script.Util.WSProxy(),
    objectType = "AutomationInstance",
    cols = ["Name","Status","StartTime"];
    filter = {
      Property: "Name",
      SimpleOperator: "IN",
      Value: [] }; // Fill in your list of Automations you want to see the history of here (include Quotes and commas between them!)
    moreData = true,
    reqID = null;
    prox.setClientId({"ID": mid});

while(moreData) {
    moreData = false;
    var data = reqID == null ?
           prox.retrieve(objectType, cols,filter) :
           prox.getNextBatch(objectType, reqID);

    if(data != null) {
        moreData = data.HasMoreRows;
        reqID = data.RequestID;
        if(data && data.Results) {
           for(var i=0; i< data.Results.length; i++) {
                var automationName = data.Results[i].Name;
                var automationStart = data.Results[i].StartTime;
                var automationStatus = data.Results[i].Status;

Write("<tr><td>" + automationName + "</td><td>" + automationStart + "</td><td>" + automationStatus + "</td></tr>")
            }
 
        }
    }
}
</script>   
</table>

</details>

Massive thank you to Jason for sharing this!

There are some things that if you don’t make SFMC write it down when it happens and sometimes the out of the box Send Log just isn’t enough! On the sixth day of Christmas, Aysha Marie Zouain gave to me… A template for a custom Send Logging solution! Just pop this in your email template and see everything logged for you to analyse at a later date!

<details>

<summary>Click here to see the Day Six Script</summary>

%%[ InsertDE('ent.B2C_SendLog','p1_utm_term',__AdditionalEmailAttribute1,'p2_utm_source',__AdditionalEmailAttribute2,'p3_utm_campaign',__AdditionalEmailAttribute3,'p4_campaigncode',__AdditionalEmailAttribute4,'p5_brand',__AdditionalEmailAttribute5,'p6_onsite',__AdditionalEmailAttribute6,'p7_business',__AdditionalEmailAttribute7,'p8_dept',__AdditionalEmailAttribute8,'p9_',__AdditionalEmailAttribute9,'p10_',__AdditionalEmailAttribute10,'dateadded',NOW(),'JobID',JobID,'BatchID',_JobSubscriberBatchID,'ListID',ListID,'SubscriberID',SubscriberID,'SubscriberKey',_subscriberKey,'ClientID',memberid) ]%%

Big thanks to Aysha for sending this super useful little solution for custom send logging!

</details>

Building user interfaces can be tricky, particularly when the content is dynamic.

If you ever find yourself needing to output a folder structure or navigation “breadcrumbs” - then have we got the SSJS script for you!

This handy little code snippet from @Adam Spriggs (inspired by none other than Zuzanna Jarczynska) will output a traditional breadcrumbs folder structure on your cloud page; great for helping to navigate your data extension or content folders!

<details>

<summary> Click to see the Day Seven Script</summary>

// via https://sfmarketing.cloud/2019/10/14/find-a-data-extension-and-its-folder-path-using-ssjs/
function getFolderPath(categoryID) {
   var list = "";
   var path = function(id) {

        if (id > 0) {
            var results = Folder.Retrieve({Property:"ID",SimpleOperator:"equals",Value:id});
            list = results[0].Name + " > " + list;
            return path(results[0].ParentFolder.ID);
        } else {
            return id;
        }
    };

    path(categoryID);
    return list;
}

</details>

Huge thank you to both Adam for this function and Zuzanna for the inspiration!

We all know that whilst it’s best data practice to capture a forename and a surname as two different fields, some places just don’t have that capability! But, don’t worry Jacob Edwards has your back! On the Eighth day of Scriptmas, Jacob gave to me - a sweet template for splitting names in SQL in SFMC!

<details>

<summary>Click to see the Day Eight Script</summary>

SELECT
 LEN(FullName) as firstNameLength
,CHARINDEX(' ',REVERSE(FullName),0) as lastSpaceIndex
,LEN(FullName)-CHARINDEX(' ',REVERSE(FullName),0) as endFnameIndex
,Substring(FullName,1,LEN(FullName)-CHARINDEX(' ',REVERSE(FullName),0)) as FirstName
,RIGHT(FullName, (CHARINDEX(' ',REVERSE(FullName),0))) as LastName
,Substring(RIGHT(FullName, (CHARINDEX(' ',REVERSE(FullName),0))),1,2) as LastNameInitial
FROM [Names]

</details>

Thanks Jacob for this super useful little query!

We all know the limitations of building form when building a Cloud Page using the native Smart Capture capabilities. Whether you’re looking for unique data capture options or some bespoke processing, sometimes you need something more unique to your business. On the ninth day of Scriptmas, Tony Zupancic gave to me - an epic starting point to build custom forms! Just add in your fields, set the RequestParameters and point the processing URL to your new Cloud Page itself you’re good to go!

<details>

<summary>Click here to see the Day Nine script</summary>

%%[
    set @EmailAddress = RequestParameter('EmailAddress')
    set @FullName = RequestParameter('FullName')
]%%

<!DOCTYPE html>
<html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>Page Title</title>

        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    </head>
    <body>

<!-- Check to see if EmailAddress is empty -->
%%[
    if Empty(@EmailAddress) then
]%%

<!-- If EmailAddress is empty show the form -->
<!-- Make ACTION page URL -->
<form method="POST" action="processingURL.com">
    <div class="form-group row">
        <label for="EmailAddress">Email</label>
        <div class="col-sm-10">
            <input type="email" class="form-control" name="EmailAddress" id="EmailAddress" placeholder="What's your email address?">
        </div>
    </div>
    <div class="form-group row">
        <label for="FullName">Full Name</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" name="FullName" id="FullName" placeholder="What's your name?">
        </div>
    </div>
    <div class="form-group row">
        <div class="col-sm-10">
            <button type="submit">Submit</button>
        </div>
    </div>
</form>

<!-- If EmailAddress is not empty process the form and show thank you message -->
%%[
    Else
        /*UpsertData and UpdateData*/
        UpsertData('FormDE',1,'EmailAddress',EmailAddress,'FullName',FullName)
]%%

<h1> Thank you for your submission! </h1>

%%[EndIF]%%

</html>

</details>

Huge thanks to Tony Zupancic for this super useful template!

There are times when you really need to have one-to-many relationships in your data and there are times where a one-to-many relationship may just cause you problems (especially with things like Data Filters and Journey Builder decision splits). Fortunately, there are ways and means to bring your data to single rows where you’ll be able to use different operators to get the result you need, whether it’s 1:M or 1:1 with delimited strings!

So without any further ado… On the tenth day of Scriptmas, Greg Gifford gave to me - a super helpful SQL query to convert a set of many rows into a delimited string! (Just make sure your target Data Extensions can handle all the data that could come their way!)

<details>
<summary> Click here to see the Day Ten script</summary>

SELECT a.id,
emailStr = STUFF(
  (
    SELECT ',' + b.email
    FROM [myDE] b
    WHERE a.id = b.id
    FOR XML PATH('')
  ), 1, 1, '')
FROM [myDE]
GROUP BY ID 

</details>

Thanks Greg for this super little SQL query to help make data more accessible to more of SFMC!

One of those little pains that many SFMC users need to overcome is the fact that you can’t get details about a Job from a child business unit in the enterprise BU from the _Job Data View (even though you can get everything else!). Thankfully it’s still possible to get those details without having to switch business units thanks to the SOAP API. On the eleventh day of Scriptmas, Jason Cort gave to me - A script that pulls all of the JobIDs and EmailNames from a child BU thanks to WSProxy.

<details>

<summary>Click here to see the Day Eleven script</summary>

<script type="javascript" runat="server">
Platform.Load("Core","1.1.1");
    var targetDE = "" // Put in the name of a Data Extension configured with JobID as a PrimaryKey, EmailName and SendDate
    var mid = ""; // Put the target business unit MID in here
    var prox = new Script.Util.WSProxy(),
    objectType = "Send",
    cols = ["EmailName","ID","SendDate"]; // These are the attributes retrieved from the Send SOAP API Object - https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/send.html
    filter = {
      Property: "ID",
      SimpleOperator: "greaterThan",
      Value: "0" }; // This will get you all of the send jobs for the BU in the MID field
    moreData = true,
    reqID = null;
    prox.setClientId({"ID": mid});
while(moreData) {
    moreData = false;
    var data = reqID == null ?
           prox.retrieve(objectType, cols,filter) :
           prox.getNextBatch(objectType, reqID);
    if(data != null) {
        moreData = data.HasMoreRows;
        reqID = data.RequestID;
        if(data && data.Results) {
           for(var i=0; i< data.Results.length; i++) {
                var emailName = data.Results[i].EmailName;
                var emailJobID = data.Results[i].ID;
                var emailSendDate = data.Results[i].SendDate;
                var rowUpdate = Platform.Function.UpsertData(targetDE,["JobID"],[emailJobID],["EmailName","SendDate"],[emailName,emailSendDate]);
            } 
        } 
    }
} 
</script>

</details>

Thanks Jason for this helpful little script!

As we all wrap up for the final day of Scriptmas 2021, we thought that we would finish up with an epic little trick that will save time for everyone who uses Postman to interact with the SFMC APIs. Simply set up a new collection and add todays script in the collection Pre-request Script tab (and make sure the variables match your environment!) to feel the benefits for all of 2022.

On the twelfth and final day of Scriptmas 2021, Adam Spriggs gave to me - An awesome tool to make Postman pre-fetch your authentication token so you don’t have to jump between tabs when you’re building or testing some of the SFMC APIs! Huge time saving and it’s neater and easier to boot.

<details>

<summary> Click here to see the Day Twelve script</summary>

var authEndpoint = pm.environment.get("authEndpoint")
var clientId = pm.environment.get("clientId")
var clientSecret = pm.environment.get("clientSecret")

pm.sendRequest({
    "url": `${authEndpoint}v2/token`,
    "method": "POST",
    "header": {
        "Content-Type": "application/json"
    },
    "body": {
        "mode": "raw",
        "raw": JSON.stringify({
            "grant_type": "client_credentials",
            "client_id": clientId,
            "client_secret": clientSecret
        })
    }},
    function(err, response) {
        var jsonData = response.json()
        if(jsonData && jsonData.access_token) {
            pm.environment.set("accessToken", jsonData.access_token)
        }
    }
)

</details>

Massive thank you to Adam Spriggs for this time saving revelation.

Thank you everybody for your contributions, thank you to the community for an amazing 2021 in the face of such a tricky year. Thank you everyone for reading all of these scripts, tips and tricks. If you’ve read any of these and want to get involved with the H2 community, come and join us on slack!

Read more
HowToSFMC
Published 12/09/2021
cloud page
12 Days of Scriptmas - 2022 Winners!

The HowTo Crew must have been extra good this year because Santa left few extra presents under the tree so, we’ve decided to give them to a few of the folks who submitted scripts for Scriptmas! To add a little sparkle we decided to make a cloud page to randomize the winners. :sparkle emoji: Details on that a little later on.

Without further ado, the winners of Scriptmas 2022 are…

Tim Felch

Tim has won a physical copy of [Automating Salesforce Marketing Cloud] (https://www.amazon.com/Automating-Salesforce-Marketing-Cloud-productivity/dp/1803237198), as well as a Trailblazer Hoodie, an Astro plushy, and a Salesforce Certification voucher!

Cenk Irman <br /> Akash Israni <br /> Jake Wiesenthal <br /> Elliott Davidson <br />

All addtional winners will recieve a digital copy of [Automating Salesforce Marketing Cloud] (https://www.amazon.com/Automating-Salesforce-Marketing-Cloud-productivity/dp/1803237198), a Trailblazer Hoodie, and a Salesforce Certification voucher!

Thank you again to all who submitted scripts for Scriptmas '22 and we’ll see you next year! <br>

!Scriptmas Randomizer Selector

Now that we know who the winners are and what they won, let’s take a look at how we selected these winners.

To make things fair, we randomly selected between the 12 chosen script creators who would be taking the grand prize as well as the other prizes. This was done via a CloudPage on SFMC using SSJS. We thought this would be fitting with the theme of helpful scripts and advocacy of Marketing Cloud.

Now, how do we make SSJS do a random selection? Lets dive in and take a look!

When broken down, the randomizer is a do/while() statement combined with a simple function. See below script for example:

  do {
    //entriesRS is an array of objects (taken from a DE holding all the entries)
    var winnerIndex = getRandomWinner(entriesRS.length);

    //winnerRS is an object
    var winnerRS = entriesRS[winnerIndex];

    var winnerID = winnerRS.ID;
    var winnerName = winnerRS.Name;

    var upsert = Platform.Function.UpsertData("Winners_2022",["ID"],[winnerID],["Name","Prize"],[winnerName,prize]);
    //Prize is set above do/while loop for grand prize

    //remove winner from eligible array
    entriesRS.sort(function(x,y){ return x == winnerRS  ? -1 : y == winnerRS ? 1 : 0; });
    entriesRS.shift();

    var prize = "Winner"
    cnt++;
  } while (cnt < 5)

  function getRandomWinner(max) {
	return Math.floor(Math.random() * max);
  }

The main crux of the randomization is the combination of Math.random() and multiplying it by the max number. random() will generate a completley number between 0 and 1, this then multiplied times the maximum number you want displayed will give you a random number between 0 and the maximum number. Now, this number will not be a whole number, so to make sure we only return a whole number, we add on Math.floor() to reduce the number down to the whole number displayed, removing all decimals. From there we then get a random number between 0 and the maximum number, which we can then use as an index for our array of objects, to select the winning row.

To better explain that, here are the breakdown steps to how it was selected:

  1. An array of objects was retrieved using Rows.Retrieve() on a Data Extension holding all the entries information
  2. Using the randomizer function, getRandomWinner(), we were returned with a whole number between 0 and the number of entrants
  3. This whole number was then used as the index for selecting an object out of the array of objects we previously retrieved
  4. This chosen object now contained all the information on that selected winner that we then upserted into a new data extension
  5. Utilizing a do/while() look, we did this for a total of 5 times to collect each of the winners for our event

<br /> Now that we understand the script, let’s look at how we decided to run it.

Of course we could have just made this a script activity and run it and called it a day, but that seemed a bit…anti-climactic, so we decided to instead create a form on a CloudPage, that upon submit ran this script then displayed the winners on the page. Below are the requirements of this CloudPage and the script contained within:

  • Data Extension: Selected_Entries
  • Data Extension: Winners_2022
  • CloudPage (self-submit)

Now that we know what assets are needed, let’s dive into what these assets are and what they need to contain.

This one is fairly simple, it will hold all the current entries along with the necessary information. For example in this case, we had the following columns:

  • ID | Number | Primary Key
  • Name | Text (50) | Nullable

Now to be fair, ID was not necessary, but I tend to like making my primary keys numbers or otherwise simple and unique values. The next data extension has a bit more to it though…

Although this has a bit more to it then the previous, it really is just one field…Prize.

This field is a text field, with max character length of 50 and is nullable. This will show whether the person won the Grand Prize or not.

The CloudPage is the most intricate part. But honestly, outside the script and a couple elegant functions and conditionals, it really is still fairly simple.

It is a few Rows.Retrieve() to get the entries and validate if the winners have already been chosen or not. From there it is a group of conditionals checking:

  • if winners are chosen then display the winners html code, hide form.
  • if winners are not chosen, but not submitted, then show form, hide winner html
  • if winners are not chosen, but is submitted, then run randomizer script, show winner html, hide form.

We then have a form that just has a simple submit button that will POST to itself passing along the simple input of ‘submit = 1’ to let the page know that the form button was pushed and to run the randomizer script. Outside that, it is a simple display of AMPscript variables showing the winners pulled from the winner data extension.

That is really it! It has some complicated parts and some twisty/turney type coding to it, but in general it is not too complex.

<details>

<summary>Click here to see the full code for the CloudPage:</summary>

<script runat="server">
  Platform.Load("Core","1.1.1");
  try {
    var debug = 0;
    var debugForm = 0;

    var submit = Request.GetFormField("submit");
    var winDE = DataExtension.Init("Winners_2022");
    var winRS = winDE.Rows.Retrieve();

    if ((winRS.length < 1) && (submit)) {

      var entriesDE = DataExtension.Init("Selected_Entries");
      var entriesRS = entriesDE.Rows.Retrieve();

      var prize = "Grand Prize"
      var cnt = 0;

      do {
        var winnerIndex = getRandomWinner(entriesRS.length);

        var winnerRS = entriesRS[winnerIndex];

        var winnerID = winnerRS.ID;
        var winnerName = winnerRS.Name;

        if (prize == 'Grand Prize') { 
          TreatAsContent('%' + '%[ SET @GrandPrize = "' + winnerName + '" ]%' + '%') 
        } else { 
          TreatAsContent('%' + '%[ SET @Prize' + cnt + ' = "' + winnerName + '" ]%' + '%') 
        }

        var upsert = Platform.Function.UpsertData("Winners_2022",["ID"],[winnerID],["Name","Prize"],[winnerName,prize]);

        //remove winner from eligible array
        entriesRS.sort(function(x,y){ return x == winnerRS  ? -1 : y == winnerRS ? 1 : 0; });
        entriesRS.shift();

        var prize = "Winner"
        cnt++;
      } while (cnt < 5)

    } else if (submit) {
      
      winRS.sort(function(x,y){ return x.Prize == "Grand Prize"  ? -1 : y.Prize == "Grand Prize" ? 1 : 0; });

      Variable.SetValue("@GrandPrize",winRS[0].Name);
      Variable.SetValue("@Prize1",winRS[1].Name);
      Variable.SetValue("@Prize2",winRS[2].Name);
      Variable.SetValue("Prize3",winRS[3].Name);
      Variable.SetValue("Prize4",winRS[4].Name);
      debugWrite('winRS',Stringify(winRS))

    } else if (winRS.length < 1) {
      Variable.SetValue("@form", "1")
    } else {
      
      Variable.SetValue("@GrandPrize",winRS[0].Name);
      Variable.SetValue("@Prize1",winRS[1].Name);
      Variable.SetValue("@Prize2",winRS[2].Name);
      Variable.SetValue("Prize3",winRS[3].Name);
      Variable.SetValue("Prize4",winRS[4].Name);

      if (submit != 1) {  (debugForm) ? Variable.SetValue("@form", "1") : ''; }
    }

  function debugWrite(name,val,overwrite) {
      if (debug && !overwrite) {
        Write('<b>' + name + ': </b>' + val + '<hr>')
      }
    }

  function getRandomWinner(max) {
    return Math.floor(Math.random() * max);
  }

</script>
<!DOCTYPE>
<html>
  <head>
    <style>
      body {
        background-color: aqua;
        margin: 0;
        font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Sego UI, Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,ui-monospace, SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monopsace;
      }
      .container {
        max-width:1000px;
        margin: 0 auto;
        background-color: 
      }
      .logo {
        width:100%;
        padding: 30px;
        background-color: 
        text-align: center;
      }
      .h2Logo {
        max-width: 200px;
      }

      .display {
        background-color: 
        width: 100%;
        padding: 30px;
      }

      h1 {
        text-transform:capitalize;
      }

      .subheading {
        font-size: 1.4em;
        margin: -22px 0 20px;
        font-style: italic;
        color: 
      }

      .copy {
        margin: 0 0 30px;
      }
      .divTable{
        display: table;
        width: auto;
        min-width: 600px;
      }
      .divTableRow {
        display: table-row;
      }
      .divTableHeading {
        background-color: 
        display: table-header-group;
      }
      .divTableCell, .divTableHead {
        border: 1px solid 
        display: table-cell;
        padding: 7px 14px;
      }
      .divTableHeading {
        background-color: 
        display: table-header-group;
        font-weight: bold;
      }
      .divTableFoot {
        background-color: 
        display: table-footer-group;
        font-weight: bold;
      }
      .divTableBody {
        display: table-row-group;
      }
      .GrandPrize {
        background-color: rgb(247, 90, 90);
      }
      .Prize {
        background-color: rgb(105, 190, 105);
      }   
      form {
        margin: 0 auto;
        text-align: center;
      }
      input[type=submit] {
        font-family: inherit;
        font-size: 1.2em;
        color: 
        width: 250px;
        padding:20px 50px; 
        background:rgb(17, 187, 94); 
        border:0 none;
        cursor:pointer;
        -webkit-border-radius: 10px;
        border-radius: 10px; 
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="logo"><img class="h2Logo" src="https://res.cloudinary.com/howtosfmc/image/upload/v1614651577/HowtoSFMCPlaceholder_White_vlsjf0.png" /></div>
      <div class="display">
      %%[if @form == 1 then]%%

        <div class="heading"><h1>Scriptmas 2022</h1></div>
        <div class="subheading">It is time for us to randomly select the winners for Scriptmas 2022!</div>
        <div class="copy">Using randomization scripting in SSJS on a SFMC CloudPage, we will select 1 Grand Prize winner and 4 other prize winners for this year's Scriptmas event. By Clicking the button below, we will have each of these selected and then displayed</div>

        <form method="POST">
          <input type="hidden" name="submit" value="1" />
          <input type="submit" value="Select the winners!">
        </form>
      %%[else]%%
        <div class="heading"><h1>Scriptmas 2022</h1></div>
        <div class="subheading">The winners have been selected for our grand and other prizes!</div>
        <div class="copy">A big thank you to all that have entered, and a hearty congratulations to those selected. Without further ado, displayed below are the randomly selected prize winners for the 2022 Scriptmas event by HowToSFMC!</div>
        <div class="divTable">
          <div class="divTableBody">
            <div class="divTableRow GrandPrize">
            <div class="divTableCell"><b>Grand Prize</b></div>
            <div class="divTableCell"><b>%%=v(@GrandPrize)=%%</b></div>
            </div>
            <div class="divTableRow Prize">
            <div class="divTableCell">Prize 1</div>
            <div class="divTableCell">%%=v(@Prize1)=%%</div>
            </div>
            <div class="divTableRow Prize">
            <div class="divTableCell">Prize 2</div>
            <div class="divTableCell">%%=v(@Prize2)=%%</div>
            </div>
            <div class="divTableRow Prize">
            <div class="divTableCell">Prize 3</div>
            <div class="divTableCell">%%=v(@Prize3)=%%</div>
            </div>
            <div class="divTableRow Prize">
            <div class="divTableCell">Prize 4</div>
            <div class="divTableCell">%%=v(@Prize4)=%%</div>
            </div>
          </div>
        </div>
      %%[endif]%%
      </div>
    </div>
  </body>
</html>

<script runat="server">
  
  } catch(e) {
    Write(Stringify(e))
  }
  
</script>

</details>

Well, that is pretty much it! I hope you enjoyed it and can use this in some way to help assist in your future needs. So long and thanks for all the fish!

Read more
HowToSFMC
Published 12/09/2021
SOAP
12 Days of Scriptmas - 2022

As 2022 draws to a close, it’s time to open up the door on the 2022 edition of the HowToSFMC Scriptmas tradition where the team and the community share some of their top scripts, tips, hacks and workarounds.

Every day in the lead up to the big day, starting on Monday 12th December up until Christmas Eve, we’ll be revealing one scripted piece of goodness for you to grab and use in the new year!

On the First Day of Scriptmas, Ralph gave to me (us)…

A handy way to dynamically toggle campaign content so you can spend more time with family and let the Salesforce Elves handle the hard work!
Simply set your Start and End dates at the top of your CloudPage or Email…

Sprinkle a bit of Scriptmas magic around your campaign content…

And enjoy a bit of EggNog while the SF Elves are hard at work.

<details> <summary>Click here to see the Day One Script</summary>

%%[
/* CALCULATE IF THE CAMPAIGN IS ACTIVE */
SET @Senddate = SystemDateToLocalDate(GetSendTime(FALSE))
SET @StartDate = DateParse("01/06/2022 00:00:00 AM")
SET @EndDate = DateParse("31/12/2022 11:00:00 PM")
SET @DateDiffStart = DateDiff(@StartDate, @Senddate  ,"D")
SET @DateDiffEnd = DateDiff(@EndDate, @Senddate,"D")

IF @DateDiffStart >= 0 AND @DateDiffEnd <= 0  THEN
	SET @CampaignPeriod = "TRUE"
ELSE
	SET @CampaignPeriod = "FALSE"
ENDIF
]%%

/* Wrap your campaign content with the following */
%%[IF @CampaignPeriod == "true" THEN]%%
Insert campaign info here
%%[ENDIF]%%

</details>

<br />

Huge thank you to Ralph van den Broeck for the submission and helping us kick off another cheerful year of Scriptmas!


On the Second Day of Scriptmas, our good friend Lesley Higgins shared with us…

A creative way to display SFMC data on to CloudPages and External Websites by using JavaScript Code Resource pages.

With a dash of Sever-Side JavaScript and a pinch of Client-Side JavaScript, this script is as sweet as a candy cane!

<details>

<summary>Click here to see the Day Two Script</summary>

<form>
  <!-- Anchor for dynamically populated HTML -->
  <div id="container"></div>
  <!-- Submit Button -->
</form>

<!-- Load in SFMC Code Resource -->
<script type="text/javascript" src="https://cloud.<coderesource>.js"></script>
(function (/* root, doc */) {
  <script runat="server">
    Platform.Load("core","1.1.5");
      var filter = { 
        Property: "Pricebook2Id", 
        SimpleOperator: "equals", 
        Value: "18DIGSF" 
      }

      //Initiate Data Extension
      var de = DataExtension.Init("Data-extension-external-key");

      //Retrieve rows based on filter
      var results = de.Rows.Retrieve(filter);

      //Stringify and pass row data to AMPscript/Client-Side Javascript
      var resultsString = Stringify(results);
      Variable.SetValue("@results", resultsString);

  </script>

   var results = %%=v(@results)=%%;

   for (var i = 0; i < results.length; i++) {
      var checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.id = results[i].Web_Name;
      checkbox.name = 'Sample';
      checkbox.value = results[i].ProductCode;
      var label = document.createElement('label')
      label.htmlFor = results[i].Web_Name;
      label.appendChild(document.createTextNode(results[i].Web_Name));
      var br = document.createElement('br');
      var Container = document.getElementById('container');
      Container.appendChild(checkbox);
      Container.appendChild(label);
      Container.appendChild(br);
    }
}(window, document));

</details>

<br />

Thanks for spreading the Scriptmas cheer Lesley!

Wanna hang out with Lesley? Give her a follow on Twitter!


On the Third Day of Scriptmas, Matt gifted us…

Some really handy SQL that automates a process that used to take the Salesforce Elves quite some time! The Elves are very fond of using Data Extensions to keep track of all the toys but they need to work in shifts.

Here’s a nifty SQL script that picks up everyone from the DoubleChecked_NiceList Data Extension that hasn’t made their way to DoubleChecked_NiceList_processed on the next run.

The Elves can even customize it a bit with additional WHERE clause criteria!

<details> <summary>Click here to see the Day Three Script</summary>

    SELECT
        s.PrimaryKey,
        s.FirstName,
        s.LastName,
        s.EmailAddress
    FROM [Source Data Extension] s
        LEFT JOIN [Target Data Extension] t 
            ON s.PrimaryKey = t.PrimaryKey
    WHERE
        t.PrimaryKey IS NULL 

</details>

<br />

I think it’s safe to say that Matt Brulet is on the Salesforce Nice List this year for sharing this awesome SQL with us!

Matt can be found on LinkedIn and Twitter!


On the Fourth Day of Scriptmas, Robert shared with us…

An awesome bit of HTML and AMPscript that the Salesforce Elves use to send the Naughty and Nice List to Santa. With this in their workshop toolkit, they are able to build report tables in the time it takes them to sing our favorite holiday song All I Want for Christmas is an Automated Table!

<details> <summary>Click here to see the Day Four Script</summary>

 %%[

  /*  
        OVERVIEW:  build HTML tables dynamically based upon your client details. Create support tables to drive the table you want per the contact Account_Type

        This allows the HTML tables to contain up to 10 columns
  */

 

  /* code to determine Core/WFN/RS/CS  */

    set @lookupvalue = AttributeValue("Contact ID")
    set @Account_Type = AttributeValue("Account Type")
    set @Client_Type = AttributeValue("Client Type")


    IF @Account_Type == 'ACC1' AND @Client_Type == 'Core1' THEN

        set @email_BU = 'BU1'
        set @HeaderTDStyle = ' style="text-align: center; vertical-align: center; background-color:

    ELSEIF @Account_Type == 'ACC2' AND @Client_Type == 'Core2' THEN
        set @email_BU = 'BU2'

        set @HeaderTDStyle = ' style="text-align: center; vertical-align: center; background-color:grey; font-family: TaubSans,Arial,sans-serif; letter-spacing:0.2px;line-height:1em;font-size:12px;padding:6px;margin:0px;" '

    ELSEIF @Account_Type == 'ACC3' AND @Client_Type == 'Core3' THEN

        set @email_BU = 'BU4'

        set @HeaderTDStyle = ' style="text-align: center; vertical-align: center; background-color:blue; font-family: TaubSans,Arial,sans-serif; letter-spacing:0.2px;line-height:1em;font-size:12px;padding:6px;margin:0px;" '

    ELSE

        set @email_BU = 'Missing'

    ENDIF

    /* ================================== */

    /* enter the email title here */

    set @email_title = "test"
    set @email_BU = "CS"

    /* enter the email title here */

    /* ================================== */

    /*    COMMENTS    */

    /*  
        HEADERS:
        Header data stored, one row per header column
        fetch data for header text and sort columns
        hide column feature if needed 


        DATA:
        Raw data stored in typical row/column
        Fetch data dependent upon where header data is found and pull in by column order
     */

 

    var @i, @HeaderRows, @HeaderRows, @HeaderRowCount

    var @rows, @row, @contact_id, @prevcontact_id, @numRowsToReturn, @company_code, @deferral_amount, @prior_company_code

    set @numRowsToReturn = 0

    /* write the CSS styles here  */

    set @HeaderTRStyle = ' style="color:

    set @HeaderTRStyle = ' style="background-color: 

    set @TRStyleOdd = ' style="background-color: 

    set @TRStyleEven = ' style="background-color: 

    set @TDStyleOdd = ' style="background-color: 

    set @TDStyleEven = ' style="background-color: 

    set @HeaderRows = LookupOrderedRows("test_table_headers2", 0, "col_sort_num asc", "email_title", @email_title, "email_title_sub", @email_BU)

    set @HeaderRowCount = rowcount(@HeaderRows)

    /* Start Header Row If */
    if @HeaderRowCount > 0 then 
        for @i = 1 to @HeaderRowCount DO
            set @HeaderRow = row(@HeaderRows, @i)
            set @col_title = trim(field(@HeaderRow,"col_title"))
            set @col_sort_num = trim(field(@HeaderRow,"col_sort_num"))
            set @col_hide  = trim(field(@HeaderRow,"col_hide"))

            /*  create column headers in this block of code  */
            if @i == 1 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp1 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol1 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 2 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp2 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol2 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 3 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp3 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol3 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 4 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp4 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol4 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 5 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp5 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
               set @Hcol5 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 6 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp6 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol6 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 7 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp7 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol7 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 8 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp8 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol8 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 9 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp9 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol9 = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            elseif @i == 10 and @col_title != "" and @col_hide == "" then
                set @Hcol_disp10 = concat('<th', @HeaderTDStyle, '>', @col_title, '</th>')
                set @Hcol10  = concat("col", trim(field(@HeaderRow,"col_sort_num")))

            endif
        next @i

        /* create the HTML table */
        output(concat("<table style='border: 1px solid 
    ]%%

    <!--
        [if mso]>
        <tr>
            <td style="height:0.1pt">
                &nbsp;
            </td>
        </tr>
        <![endif]
    -->
 
    %%[
        /* write the TR(row) */

        output(concat('<tr ', @HeaderTRStyle, ' >'))

        /* write the TH(column headers) */
        output(concat(v(@Hcol_disp1),v(@Hcol_disp2) ,v(@Hcol_disp3) ,v(@Hcol_disp4) ,v(@Hcol_disp5) ,v(@Hcol_disp6) ,v(@Hcol_disp7) ,v(@Hcol_disp8) ,v(@Hcol_disp9) ,v(@Hcol_disp10) ))

        /* close the HTML row */
        output(concat("</tr>"))

 
        /* lookupOrderedRows("data extension name", number of rows to return, sort columns, WHERE criteria 1, WHERE criteria 1 value, WHERE criteria 2, WHERE criteria 2 value, etc...)  */

        set @Rows = LookupOrderedRows("test_table_list", 0, "data1, data2", "email_title", @email_title, "email_title_sub", @email_BU, "Contact Id", "0031P00001Eny4HQAR")

        set @RowCount = rowcount(@Rows)

        /* Start Row Display If */
        if @RowCount > 0 then 
            for @i = 1 to @RowCount DO
                
                /* create the rows/columns of data here */
                set @Row = row(@Rows, @i)
                set @vcol1  = ""
                set @vcol2  = ""
                set @vcol3  = ""
                set @vcol4  = ""
                set @vcol5  = ""
                set @vcol6  = ""
                set @vcol7  = ""
                set @vcol8  = ""
                set @vcol9  = ""
                set @vcol10 = ""

                /* set style for odd/even rows */
                IF mod(@i,2) == 0 THEN
                    output(concat('<tr ', @TRStyleEven, ' >'))
                    set @TDStyle = @TDStyleEven
                ELSE
                    output(concat('<tr ', @TRStyleOdd, ' >'))
                    set @TDStyle = @TDStyleOdd
                ENDIF

        
                output(concat('<tr ', @TRStyle, ' >'))
                if length(@Hcol1) > 1 then
                    set @vcol1  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol1)), '</td>')
                endif

                if length(@Hcol2) > 1 then
                    set @vcol2  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol2)), '</td>')
                endif

                if length(@Hcol3) > 1 then
                    set @vcol3  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol3)), '</td>')
                endif

                if length(@Hcol4) > 1 then
                    set @vcol4  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol4)), '</td>')
                endif

                if length(@Hcol5) > 1 then
                    set @vcol5  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol5)), '</td>')
                endif

                if length(@Hcol6) > 1 then
                    set @vcol6  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol6)), '</td>')
                endif

                if length(@Hcol7) > 1 then
                    set @vcol7  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol7)), '</td>')
                endif

                if length(@Hcol8) > 1 then
                    set @vcol8  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol8)), '</td>')
                endif

                if length(@Hcol9) > 1 then
                    set @vcol9  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol9)), '</td>')
                endif

                if length(@Hcol10) > 1 then
                    set @vcol10  = concat('<td', @TDStyle, '>', trim(field(@Row,@Hcol10)), '</td>')
                endif

                output(concat(@vcol1, @vcol2, @vcol3, @vcol4, @vcol5, @vcol6, @vcol7, @vcol8, @vcol9, @vcol10))

                output(concat("</tr>"))

                next @i

            /* End Row Display If 8?
            endif 

        /* End Header Row If *?
        endif 

        output(concat("</table>"))
        output(concat("<!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]-->"))
    ]%%

        </td>
    </tr>

</table>

</details> <br />

Thank you for sharing this awesome script with us Robert Forrester!

Robert can be found on LinkedIn!

On the Fifth Day of Scriptmas, Jake sent us…

Some SQL and AMPscript that the Salesforce Elves really wish they had before this Holiday season!

The Elves Data Extensions are filled with records of Salesforce wishes and gifts by all the good Trailblazers. This set of SQL and AMPscript would have helped them combine each Trailblazers many wishes into one email for each to send to Santa!

This way Santa wouldn’t get so many emails and it would be easier for him to make sure all Trailblazer wishes are coming true!

It’s not only great for Trailblazer wishes, but e-commerce wishlists, product refills, and many other use-cases!

<details> <summary> Click here to check out the Day Five Script</summary>

This data will include more than 1 row per Subscriber

Let’s call this the “todays_total_purchase_reminder_data” Data Extension

SELECT DISTINCT 
    SubscriberKey, 
    refill_id,
    refill_name,
    dosage,
    next_purchase_reminder_date
FROM 
    purchases
WHERE 
    next_purchase_reminder_date > DATEADD(DAY, -1, GETDATE());

Let’s call this “todays_total_purchase_reminder_data_GROUPED” Data Extension

SELECT
    SubscriberKey
FROM 
    Refill_Overdue
GROUP BY 
    SubscriberKey

Now you will have a send list with only SubscriberKey and will only have 1 unique row for each Subscriber.

The next step will be creating an email that can perform a lookup function on the “todays_total_purchase_reminder_data” DE to get the data to display info on 1 or more records for the single email send.

%%[
    /* 3rd Step Define AmpScript */
    SET @SubscriberKey = [SubscriberKey]

    /* Perform lookup on the Data Extension we populate with our 1st query */
    SET @rows = LookupRows("todays_total_purchase_reminder_data", 
                            "SubscriberKey", 
                            @SubscriberKey)

    SET @rowCount = rowcount(@rows) 

    /* 
        If you need each separate row from the 1st DE to be presented in the email via separate blocks of content, then follow instructions similar to https://ampscript.guide/lookuprows/ where the FOR loop loops through each row with a new block of html 
    
        If you want to build 1 sentence that separates each row of data in plain English with commas, then follow this BONUS Scriptmas code! 
    */

    /* 
        Get a string of refill Names combined... 
        original source: https://salesforce.stackexchange.com/questions/79214/how-to-create-loop-for-subject-line-using-ampscript 
    */
   
   
   IF @rowCount > 0 THEN 
      FOR @i = 1 TO @rowCount DO 
            SET @row = row(@rows, @i) 
            SET @refill_name = ProperCase(field(@row, "refill_name"))

            IF NOT EMPTY(@refill_name_loop) THEN
                  IF @i == @rowCount AND @i > 1 THEN
                        SET @refill_name_final = CONCAT(@refill_name_loop, " and ", @refill_name)
                  ELSE
                        SET @refill_name = CONCAT(@refill_name_loop, ", ", @refill_name)
                        SET @refill_name_final = CONCAT(@refill_name_loop, ", ", @refill_name)
                  ENDIF
            ELSE

                  SET @refill_name_final = @refill_name

            ENDIF

            SET @refill_name_loop = @refill_name
      next @i
   ENDIF

]%%

<!-- @refill_name_final will return simply 1 refill name with 0 commas if there's only 1 refill row for a Subscriber in the 1st DE
@refill_name_final will return a string similar to "refill1 and refill2" when the Subscriber has 2 refill rows in the 1st DE
@refill_name_final will return a string similar to "refill1, refill2, refill3, and refill4" for a Subscriber that has more than 2 refill rows -->
<p>You are due to refill the following items %%=v(@refill_name_final)=%%</p>

</details>

<br />

Thanks for the awesome code Jake Wiesenthal!

Jake can be found on LinkedIn.

On the Sixth day of Scriptmas Tim sent to us…

A CloudPage SSJS script that helps us generate SQL! As you can imagine, the Elves have a LOT of queries to run and a LOT of Data Extension fields to include. With this script they can automate building their queries!

<details> <summary>Click here to see the Day Six Script</summary>

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

    var custKey = 'DEKeyGoesHere';
    var DEName = '[DENameGoesHere]';
    var alias = 'b'; /* Alias of choice goes here */  
    
    var myDE = DataExtension.Init(custKey);
    var myFields = myDE.Fields.Retrieve();
    
    var itemsToSelect = 'SELECT ';
    
    for (i = 0; i < myFields.length; i++) {
    if(i===0) {
        if(alias) {
        itemsToSelect += alias + '.[' + myFields[myFields[i].Ordinal].Name + ']<br>';
        }
        else{
        itemsToSelect += '[' + myFields[myFields[i].Ordinal].Name + ']<br>';
        }
    }
    else {
        if(alias) {
            itemsToSelect += ', ' + alias + '.[' + myFields[myFields[i].Ordinal].Name + ']<br>';
        }
        else{
        itemsToSelect += ', [' + myFields[myFields[i].Ordinal].Name+ ']<br>';
        }
    }
    }   
    itemsToSelect += 'FROM <br>' + DEName + ' ' + alias; 
    Write(itemsToSelect);
</script>

</details>

<br />

Big Scriptmas thank you to Tim Felch for sharing this great script!

Tim can be found on LinkedIn!

On the Seventh Day of Scriptmas, Elliott gave to us…

A SQL script helps the Salesforce Elves create a pivot table to make sure that everyone on Santa’s Nice List has been good little Trailblazers that have been opening their Holiday Emails in the last 90 days!

<details>

<summary> Click to see the Day Seven Script</summary>

SELECT A.[Subscriberkey],'Open' as 'StatType',
[EventDate] as 'DateOccured', [isunique] as 'unique', B.[Emailname], A.[JobID]

FROM _Open A
LEFT JOIN _Job B
ON A.[JobID] = B.[JobID]
WHERE [Eventdate] > dateadd(day, -90, GETUTCDATE())

UNION

SELECT [Subscriberkey],'Send' as 'StatType', [EventDate] as 'DateOccured',
'True' as 'unique', B.[Emailname], A.[JobID]


FROM _Sent_DV
LEFT JOIN _Job B
ON A.[JobID] = B.[JobID]
WHERE [Eventdate] > dateadd(day, -90, GETUTCDATE())

UNION

SELECT [Subscriberkey], 'Click' as 'StatType', [EventDate] as 'DateOccured',
[isunique] as 'unique', B.[Emailname], A.[JobID]

FROM _Click A
LEFT JOIN _Job B
ON A.[JobID] = B.[JobID]

WHERE [Eventdate] > dateadd(day, -90, GETUTCDATE()) AND [Linkname] <> 'Unsub'

UNION

SELECT [Subscriberkey],'Bounce' as 'StatType', [EventDate] as 'DateOccured',
'True' as 'unique', B.[Emailname], A.[JobID]

FROM _Bounce A
LEFT JOIN _Job B
ON A.[JobID] = B.[JobID]

WHERE [Eventdate] > dateadd(day, -90, GETUTCDATE())

</details>

<br />

Thanks Elliott Davidson, for this very handy script!

Elliott can be found on Instagram: @Ell_Dingo.

On the Eighth Day of Scriptmas, Cenk gave to us…

A super cool SSJS script that has the Salesforce Elves jumping for joy! While in the holiday rush, you can imagine all of the Elves working as hard as they can to get the Naughty and Nice lists squared away for Santa! Try as they might, sometimes those EmailAddress fields get named emailAddress, Email, emailAddr, or any number of different things.

At the end of the year Papa Elf asks the Data Squad to do an audit of Data Extensions so they can do a post-holiday audit and clean up!

This script is exactly what they asked for this Scriptmas!

<details>

<summary>Click to see the Day Eight Script</summary>

<script runat="server">
    Platform.Load("core", "1");
    var sfmc = new Script.Util.WSProxy();
    try {
        var request = sfmc.retrieve("DataExtension", [ "Name", "CustomerKey", "CategoryID", "IsSendable"], {
            Property: "CustomerKey",
            SimpleOperator: "isNotNull",
            Value: " "
        });
        var de_email = [];
        var all_de = request.Results;
        for (var k in all_de) {
            var fields = sfmc.retrieve("DataExtensionField", [
                "FieldType",
                "Name"
            ], {
                Property: "DataExtension.CustomerKey",
                SimpleOperator: "equals",
                Value: all_de[k].CustomerKey
            });
            for (var f in fields.Results) {
                if(fields.Results[f].FieldType == 'EmailAddress' && !fields.Results[f].Name.indexOf(':') && (!all_de[k].CustomerKey.indexOf('_Salesforce') &&!all_de[k].Name.indexOf('_Salesforce') ) ){
                    de_email.push({
                        DEName: all_de[k].Name,
                        DEKey: all_de[k].CustomerKey, 
                        DEField: fields.Results[f].Name
                    });
                }
            }
        }
        var email_de = de_email.join(",");
        Write(Stringify(de_email));
    } catch(error) {
        Write(error);
    }
</script>

</details>

<br />

Passing along the Data Squads thanks Cenk Imren!

If you want to send your thanks as well, Cenk can be found on LinkedIn and his website!

On the Ninth Day of Scriptmas, Elise gave to us…

A bit of Zodiac fun stitched together with some AMPscript! The Salesforce Elves fancy themselves astrologers and star gazers, with the AMPscript below, they will never have to guess what the current Zodiac sign is!

<details>

<summary>Click here to see the Day Nine script</summary>

%%[
    var @pattern, @date, @month, @day, @zodiacSign

    SET @pattern = "^0*(\d+)$"
    SET @date = Now()
    SET @month = FormatNumber(RegExMatch(datePart(@date, "M"), @pattern, 1),"G")
    SET @day = FormatNumber(RegExMatch(datePart(@date, "D"), @pattern, 1),"G")

    IF @month == 1 THEN
    IF @day <= 19 THEN
        SET @zodiacSign = 'Capricorn'
    ELSEIF @day > 19 THEN
        SET @zodiacSign = 'Aquarius'
    ENDIF
    ENDIF
    IF @month == 2 THEN
    IF @day <= 18 THEN
        SET @zodiacSign = 'Aquarius'
    ELSEIF @day > 18 THEN
        SET @zodiacSign = 'Pisces'
    ENDIF
    ENDIF
    IF @month == 3 THEN
    IF @day <= 20 THEN
        SET @zodiacSign = 'Pisces' THEN
    ELSEIF @day > 20 THEN
        SET @zodiacSign = 'Aries'
    ENDIF
    ENDIF
    IF @month == 4 THEN
    IF @day <= 19 THEN
        SET @zodiacSign = 'Aries' THEN
    ELSEIF @day > 19 THEN
        SET @zodiacSign = 'Taurus'
    ENDIF
    ENDIF
    IF @month == 5 THEN
    IF @day <= 20 THEN
        SET @zodiacSign = 'Taurus' THEN
    ELSEIF @day > 20 THEN
        SET @zodiacSign = 'Gemini'
    ENDIF
    ENDIF
    IF @month == 6 THEN
    IF @day <= 20 THEN
        SET @zodiacSign = 'Gemini' THEN
    ELSEIF @day > 20 THEN
        SET @zodiacSign = 'Cancer'
    ENDIF
    ENDIF
    IF @month == 7 THEN
    IF @day <= 22 THEN
        SET @zodiacSign = 'Cancer' THEN
    ELSEIF @day > 22 THEN
        SET @zodiacSign = 'Leo'
    ENDIF
    ENDIF
    IF @month == 8 THEN
    IF @day <= 22 THEN
        SET @zodiacSign = 'Leo' THEN
    ELSEIF @day > 22 THEN
        SET @zodiacSign = 'Virgo'
    ENDIF
    ENDIF
    IF @month == 9 THEN
    IF @day <= 22 THEN
        SET @zodiacSign = 'Virgo' THEN
    ELSEIF @day > 22 THEN
        SET @zodiacSign = 'Libra'
    ENDIF
    ENDIF
    IF @month == 10 THEN
    IF @day <= 22 THEN
        SET @zodiacSign = 'Libra' THEN
    ELSEIF @day > 22 THEN
        SET @zodiacSign = 'Scorpio'
    ENDIF
    ENDIF
    IF @month == 11 THEN
    IF @day <= 21 THEN
        SET @zodiacSign = 'Scorpio' THEN
    ELSEIF @day > 21 THEN
        SET @zodiacSign = 'Sagittarius'
    ENDIF
    ENDIF
    IF @month == 12 THEN
    IF @day <= 21 THEN
        SET @zodiacSign = 'Sagittarius' THEN
    ELSEIF @day > 21 THEN
        SET @zodiacSign = 'Capricorn'
    ENDIF
    ENDIF
]%%

</details>

<br />

Thanks for this great AMPscript Elise Carlson.

Elise can found over on LinkedIn!

On the Tenth day of Scriptmas, Akash gave to us!

A very helpful SQL timesaver for the Salesforce Elves! Thanks to just a small change in how they were writing their SQL case statements, the can simplify their script and be less repetitive!

<details>
<summary> Click here to see the Day Ten script</summary>

Instead of using this:

SELECT 
CASE
  WHEN Region = ‘North’ THEN ‘Brand A’
  WHEN Region = ‘South’ THEN ‘Brand B’
  ELSE ‘Brand C’
END AS Brand
FROM dataExtention 

We can use this:

SELECT 
Brand = CASE Region
  WHEN ‘North’ THEN ‘Brand A’
  WHEN ‘South’ THEN ‘Brand B’
  ELSE ‘Brand C’
END
FROM dataExtention

</details>

<br />

Thanks for this timesaving adjustment Akash Israni!

Akash can be found on LinkdIn and Twitter.

On the Eleventh day of Scriptmas, Corrina shared with us…

Some extremely helpful AMPscript that the Salesforce Elves have been using all holiday season! With the AMPscript below, the Elves are able to set up a handy dynamic Sender Profile so they can control how their emails are being sent out of SFMC and personalize how they are being seen in the inbox!

<details>

<summary>Click here to see the Day Eleven script</summary>

%%[ var @customerId, @AccountId, @SubId, @SubName, @customerId, @AccountId, @ManagerId, @ManagerName

set @customerId = [_subscriberKey] 
set @AccountId = Lookup("Contact_Salesforce","AccountId","_ContactKey", @customerId)  
set @SubId = Lookup("Account_Salesforce","Advisor__c","Id", @AccountId)
set @FromName = Lookup("User_Salesforce","Name","Id", @SubId)
set @ManagerId = Lookup("Account_Salesforce","Manager__c","Id", @AccountId)
set @ManagerEmail = Lookup("User_Salesforce","Email","Id", @ManagerId)]%%

  %%=v(@FromName)=%%
  %%=v(@ManagerEmail)=%%

</details>

<br />

Thanks for this awesome AMPscript Corina Cohen!

Corrina can be found on LinkedIn.

On the Twelfth day of Scriptmas, Cameron gave to us…

A nifty little solution to see which of your Salesforce Elves is working hard this festive season! With the SSJS below, Scriptmas Santa can quickly see which Elves are on the working hard list and which are taking a little Scriptmas break of their own!

<details>

<summary> Click here to see the Day Twelve script</summary>

<script runat="server">
Platform.Load("Core","1");
try {
  var prox = new Script.Util.WSProxy();
  var cols = ["Name","CustomerKey","NotificationEmailAddress", "UserID", "ActiveFlag", "Email", "IsAPIUser", "AccountUserID", "LastSuccessfulLogin", "CreatedDate", "Roles"];
  var filter = {
    LeftOperand: {Property: "Email",SimpleOperator: "like",Value: "@"},
    LogicalOperator: "AND",
    RightOperand: {Property: "ActiveFlag",SimpleOperator: "equals",Value: "true"}
  };
  var res = prox.retrieve("AccountUser", cols, filter);
  Write(Stringify(res.Results)+"<br><br><br>");
    Write("<table border=1><tr><th>Name</th><th>Email</th><th>CreatedDate</th><th>LastSuccessfulLogin</th><th>Roles</th></tr>");
    for (i = 0; i < res.Results.length; i++) {
      Write("<tr><td>" + res.Results[i].Name + "</td><td>" + res.Results[i].Email + "</td><td>" + res.Results[i].CreatedDate + "</td><td>" + res.Results[i].LastSuccessfulLogin + "</td><td>");
      for (r = 0; r < res.Results[i].Roles.length; r++) {
        Write(res.Results[i].Roles[r].Name + "<br>");
      }
    Write("</td></tr>");
    }
  Write("</table>"); 
  }
catch(error) {
  Write('Message: ' + error);
}
</script>

</details><br />

Passing holiday cheer to Cam Robert for this gift!

Send Cam a thank you note on LinkedIn or through his website!

Discovered under the wrapping paper strewn on our desks, from Corrina Cohen, a magical hack__*__ to delight our Salesforce Elves!

Busy Elves love to multitask and this script does just that. But be sure to store your Client ID and Secret Key outside the activity to keep it safe.

__*__Undocumented and Unsupported RESTProxy Magic within!

<details>

<summary> Click here to see the Stocking Stuffer script</summary>

//NOTE1: Store as a code resource if you arre going to include your client id and secret 
//NOTE2: It is alreay in a Try/Catch for testing if you want to remove it to simplify the file

<script runat="server">
var api = new Script.Util.RestProxy("Client Id", "Client Secret");
try{
var jbdate = {
  "ContactKey": "",
  "EventDefinitionKey":"JOURNEY BUILDER API EVENT KEY",
  "Data": {
    "subscriberkey":"",
    "email":"",
    "firstname":"",
    "lastname":"",
    "ANY OTHER FIELDS YOU WANT POPULATED":""
  }
 };

 var res1 = api.Post("/interaction/v1/events", jbdate);
 
 Platform.Response.Write(Platform.Function.Stringify(res1));

} catch(e1)

{Platform.Response.Write(Platform.Function.Stringify(e1));}
     
</script>

</details> <br />

Pass holiday cheer to Corrina Cohen for showing this magic at LinkedIn!

Read more
HowToSFMC
Published 10/28/2021
Release Notes
October 2021 - Release Notes Summary

It’s time to bring 2021 to a close as far as SFMC releases, it’s the final release of 2021! This year has had a few false starts and a couple of reruns when it comes to releases, iOS 15 Open-rate-geddon has started to take hold and there’s still bits of working out how to deal with it to come. Since the last release Salesforce announced the new mascot Brandy, which I was disappointed to find out wasn’t SF stepping in to the Cognac game. But, there’s a bunch of new things coming to you as a nice way to wrap up 2021.

The majority of this release covers Journeys and Messaging & Einstein, but we’ll get to those shortly. First things to share are what is scheduled to leave or has left SFMC since the last release. We are waving goodbye to the MSCRM Connector for Microsoft Dynamics as a result of Microsoft moving to the Unified Interface by MS Dynamics. The connector is no longer compatible with SFMC, so if you’re using MSCRM Connector, you’ll need to start using Email Studio to cover these use cases

  • Adding subscribers
  • Sending Emails
  • Accessing tracking details

The Marketing Cloud Connector Package log has been retired, Salesforce are recommending you clear out any log files that you’ve still got floating around as part of a drive to make the platform more secure. This will impact those of you who are using an integrated Salesforce org with SFMC and find yourself troubleshooting journeys/triggered sends that rely on Salesforce CRM data. Salesforce advises moving to more secure methods of monitoring performance such as using the Initiate Log process and reviewing the activities for Triggered Sends or JBIntBulkManager.

Classic Web Tools are still on their way out, much like Classic Content this may stick around for a little while whilst everyone migrates their content to Content Builder and the new Cloud Pages experience. In August you will have lost the ability to create new piece of Classic Content Microsites and Landing Pages, early 2022 you’ll lose the ability to edit existing content and then by June 2022 it all gets unpublished, taken offline and gone from the internet. So if you’re using Classic Web Tools to host a preference centre or something business or compliance critical - get a migration project started ASAP. Last thing you want is for this to happen and have no time to act on it! Salesforce have also migrated Cloud Pages created with the classic editor to Content Builder, so do a quick audit of what you’ve got to make sure nothing has gone missing.

Non-Tenant Specific Endpoint URLs are still on the way out. Salesforce claims it is still redirecting old URLs to new TSE specific URLs, but there’s no guarantee that will continue, let alone for how long. There have been comments in the community that some users have seen pages become inaccessible overnight, so much like Classic Web Tools, don’t leave it to the last minute. Piece of advice from Salesforce is to not hardcode URLs in templates and instead to use functionality like CloudPagesURL with a Page ID reference to make sure your links are always up to date.

Web & Mobile Analytics is on its way out, if you’re using the Abandoned Cart tile in Web & Mobile Analytics, you should spend some time looking in to Behavioural Triggers instead, if you need to export the data to power Abandoned Browse, Cart or Wishlist type behaviours then take a look in to the Einstein Recommendation integration with Contact Builder. If you get this done right, it’ll populate data directly in to Data Extensions so you can grab it directly within the platform rather than a whole pile of data shepherding!

If you’re using the current Lead Capture app for Facebook/Google Leads, you’ll need to look in to updating that in November when the new version comes out. You’ll need to uninstall the current one and install the new one.

But enough about what’s being retired, sunset, end of life or whatever term you prefer to use. Let’s cover some of the fun stuff that we’re excited to play with when it lands with us.

As mentioned before, this is very much Einstein / Journeys & Messaging taking the bulk of the new features this time around. After the last release really failed to give any insight as to what Salesforce were planning to do with iOS 15 and the new “privacy” capabilities, we’re finally getting to see what’s going on. So, here’s a summary of whats going to impact your favourite Einstein featured.

Feature What’s happening
Send Time Optimisation Using a new Engagement Rate (appears to be a blend of Open and Click data) to determine best send time
Copy Insights Using a new Engagement Rate to determine languages insights and make predictions
Content Insights Click to Open rate is replaced with Click Through Rate
Messaging Insights No real change, the model uses relative changes rather then absolute changes in performance to determine when to alert
Engagement Frequency Using a new Engagement Rate and Unsubscribe Rate to improve accuracy over solely Open Rates
Engagement Scoring Using a new Engagement Rate to determine personas rather than a matrix of clicks and opens

I would expect these changes to take a little bit of time to bed in and I suspect they’ll be tweaked over time as the iOS 15 change continues to roll out and impact more users. Once we know what normal open behaviour looks like again in the future, expect these things to be revised.

Copy Insights will now allow you to compare up to 10 subject lines at once to help predict performance. This should streamline your processes and make selecting the optimal subject line more straightforward. This is only available for English language subject lines (Salesforce doesn’t specify if there is any difference between British/American English but I would hope not!)

If you’re using Salesforce CDP, you’ll be able to use Einstein driven segmentation using EinsteinEmailEngagementScores and EinsteinMobileEngagementScores objects in the CDP attribute library.

When you run content tests, Einstein will now highlight predicted winners with a little badge if there are statistically significant results.

Engagement scoring now uses local or global averages to determine how your subscribers perform in the context of your org or on a global basis. If you are opted in to the global data pool, you’ll benefit from the wider range of data available to you, if you’re not then you’ll be limited to your local performance.

Each of the Einstein Models now includes a quick high-level summary of what AI data is consumed and how it is used. Adds some great clarity to the technology for those who don’t need to get into the details.

Journeys get a new history dashboard, some new filtering options and improved pagination. Which is long overdue because Journey History has been a pain to navigate for any org with a substantial amount of journeys for as long as I can remember.

Mobile App Events will be made available as entry sources to Journeys. This should allow you to manage mobile app experiences using Journey Builder as a single point of orchestration. But for now it’ll just be focused on the direct communication between your app and Journey Builder. Keep an eye out as this may not be provisioned for you as part of this release, this is going to be rolled out over the remainder of the year.

If you’re looking to do other things with Mobile Push/In-App messaging, in the interm, if you’ve upgraded to the “next-generation MobilePush SDK” you will be able to configure In-App Messaging based on specific behavioural triggers. This includes Session limiting notifications, dynamically expiring notifications on a user by user basis, delaying an in app message within Journey Builder as well as identifying exit events to cancel a message. Definitely worth taking a look into if Push or In-App Messaging are part of your channel mix!

So there’s plenty of updates to push, recommendation is that you take a look through the documentation for iOS and Android to see if there’s any opportunities that the documentation doesn’t cover that would work for you.

A key thing that has come out of the new release is the Partner Seed integrations for the AppExchange. With the imminent departure of Return Path seeding capabilities from any org that currently uses it, it’s been a bit of a wait to find out what is going to be happening.It looks like Salesforce is hedging its bets on getting third parties to do the heavy lifting, which may mean that there are some inconsistent user experience depending on who your supplier is. Just remember that there is no native seeding in Journey Builder activities yet - there’s a load of complexities as to why but fingers crossed with all of the effort in Journey Builder for the last few years it’s only a matter of time.

Interactive Emails gets a neat quality of life bonus where if there’s a rendering issue of your form element in an inbox, it’ll by default link through to the Interactive Email Cloud Page that was built for the form.

New REST API capabilities for managing File Locations, access keys, S3 buckets and much more will hit your orgs throughout November. It would have been nice to have had this update _before _the mandated end date of the default SFMC Decryption key. Migrating multiple File Transfer activities may have been a painful experience for some users. But having a programmatic way to manage keys is definitely a useful addition.

Cloud Pages added to Package Manager. Definitely useful to have, it does make me wonder if there’s some Cloud Pages APIs hidden somewhere that we could interact with… If you’ve done anything interesting with Cloud Pages via an API, let us know!

Overall there’s some really useful and promising items in this release, some of it feels a bit “Better late than never” - especially things like the Partner Seed integrations. It’s something so fundamental to a lot of businesses and it’s taken so long to get it announced post Validity pulling out of the reseller agreement with Salesforce. But - it’s definitely better to have it than not, it’s definitely a useful thing to have and something in light of iOS 15 that if you have any deliverability requirements, you should investigate a Seeding solution, especially if you’re using dedicated IP addresses.

It’s good to see so many enhancements to Einstein in response to iOS 15, it’s all a little bit ambiguous at the moment in terms of what an “Engagement Rate” looks like and how it manifests in any specific businesses. But again - better late than never. We’ve had a few months of knowing that this was coming and Salesforce kept their cards close to their chest. What remains to be seen is how effective Einstein will be in the new auto-open event world that we’ve recently entered.

I’m really looking forward to seeing all of the Mobile Push and In-App Messaging opportunities coming our way in the next few months. If you’re experienced with Mobile Push/In-App Messaging and would be interested in sharing some insights with the H2 community, get in touch and we’d be happy to support!

This is the last release of 2021, there’s been a range of awesome new capabilities and some capabilities still needing to reach maturity. But on the whole, lots of positives this past 12 months and I’m keen to see what we’ll get in 2022.

Read more
Jason Cort
Published 08/06/2021
August 2021 - Release Notes Summary

We’re up to the 4th and penultimate SFMC release of 2021. The last release before iOS15 comes in and open-rate-geddon besmirches all Email Marketing teams KPIs and results. The only release to have ever happened during an Olympic Games that has occurred on an odd numbered year. The last release prior to the major announcement coming at Marketing In Motion on August 12. Sign up here to be the first to know what’s going on. But until then… the August 2021 release. We’ve been through them, we’ve summarized them, here’s your one hit wonder of what to expect in the SFMC August 2021 release. (nb: If you’ve read the June 2021 release notes. Some of these will be familiar)

Marketing Cloud V2 connector is gone. That’s it. It’s gone. It’s been on its way out since October 2020. I really hope everyone has updated their connector by now. There’s not much more to be said. Thank you V2 Connector, you were a great starting point and sometimes a headache… But it’s long overdue for you to have been made to retire. Cloud Pages Legacy Experience is saying goodbye. In possibly the shortest retirement run for anything in SFMC to my memory, it’s also riding off into the sunset. If you’ve not been trying the “new” Cloud Pages experience, unfortunately you’re going to have to get used to it now. But, the new experience isn’t a bad thing, it has some imperfections (such as not being able to run some scripts in the preview anymore) - but it has some nice benefits, like copying pages. So, SFMC gives with one hand and takes with the other!

The announcement of Email Studio Classic Web Tools going away in June 2022, the ability to create new classic Microsites or Landing Pages is going away in this release. You’ve got until June 2022 to get them all migrated to the new Cloud Pages. Anything you have in the classic tools is going to be unpublished and inaccessible anymore. Another example where you should move to the new tools now and migrate when opportunities arise. Microsoft Dynamic on-premises integration gets turned off in 3 months. You’ve got until October 29th this year to determine how you’ll manage this situation. Salesforce have put together some comprehensive details on how to deal with it available here. Finally in the “going away” box - If you’re still using the Legacy ASPX UI for Email Studio, you’re getting upgraded to the newer UI. Enjoy your free upgrade!

More updates to Package Manager including granular permissions for the tool, which is definitely a welcome update. The addition of Shared Data Extensions and Synchronized Data Extensions is definitely useful, but there’s specific caveats around how they get deployed with regards to whether they are deployed as shared or local Data Extensions. There’s too much to specifically cover in this summary, but check out the full release note here. Package Manager is also getting a new landing page, no screenshots in the release notes but fingers crossed it’s an improvement on the list view we have today.

After the previous release allowed you to import S3 data, the new release will allow you to export data to S3. You’ll be able to use IAM Roles to authenticate when configuring your S3 bucket as an export destination. (Fingers crossed we get something for Azure sometime soon… Hey Salesforce, you announced moving SFMC to Azure 2 years ago. Any news on when you’ll get native Azure transfer capabilities?)

If you’re not using MFA yet, you’ve got 6 months before you’ll have no choice. Make sure you start getting this set up in time, rather than a last minute scramble! Check the documentation here if you’ve not provisioned this yet.

Many businesses with connected Sales Cloud and Marketing Cloud instances will be using an attribute on the source object as a record collection filter. If that attribute changes on the Sales Cloud side, you’ll now find that your synchronisation gets paused rather than completely breaking. Which is nice.

This is pretty self explanatory, if you’re using Distributed Marketing Bulk Sending, you can add some custom personalization tabs to add a little complexity and nuance to it. But Salesforce recommends no more than 10 of these to keep it performant.

You may recognize this one from the last release, looks like it got postponed. But up until this gets released you’re required to load content via a CSV or through the user interface. The new Einstein Content Selection API will allow you to introduce content via an API from your digital asset management solution or CMS to ensure Einstein knows as much as is relevant to provide the best customer experience. You can also use the API to update Subscribers as and when an attribute value changes rather than relying on other scheduled processes.

Cast your eyes back to the June 2021 release summary… ! !

So if you’re using Einstein Engagement Scoring in any great amount, you’ll be able to configure your specific thresholds for different engagement scoring definitions. Really looking forward to seeing this one land when the release goes live, especially with the iOS15 open rate-geddon due to land between this and the final release of 2021.

Someone has taken a Conversion Rate Optimisation course at Salesforce based on the summary of this. If you’re not sure what Einstein can do for you, the App Facts will help you to compare what they should be able to do in a side by side fashion. For anyone who is unsure about what they can expect Einstein to help them with in their workflows, this should be a good one stop shop for you.

Another one that’s getting a second bite of the release notes review pie having been featured last time around. Doesn’t look like there’s anything different, so I’ll quote the previous summary.

Einstein Content Asset testing is being added to Einstein Content Selection, so if you’re using Content Builder drag and drop blocks, you’ll be able to drag an Einstein Content Testing block into your email and see the results within Einstein Content Selection. You’ll also be able to view performance analytics at a tag level, so where Einstein is tagging your content for you you can drill into the Performance Analytics tab to see how different tags are performing.

Insights will now check the language of your subject line to identify if there is bias around age, gender, orientation etc… If Einstein does detect bias, it will provide an alert and encourage users to consider their content choices to prevent bias being introduced. Details for this are available here and will help users to identify attributes that may introduce biases.

We’ve had the Wait Until API Event option available to us for the better part of the last 4 months and this looks to be much of the same, only focused on Push notification engagement. If an individual doesn’t act within your specified window, they’ll go down an alternative path. It’s great to see more and more Push type activities added to Journey Builder to bring a more unified way of working regardless of your customers preferred channels.

If you use Sitecore, you can use the Sitecore Connect activity to send contact statuses through to Sitecore as the contact moves through the journey. Does what it says on the tin really.

These are now live and whilst currently there is a redirect in place for those who have older CloudPages without Tenant Specific Endpoints, it would be wise to look at starting to migrate those to the Tenant Specific Endpoints. There’s no indication of how long the redirect will be in place for and if this is how you collect or manage customer subscription statuses this would be a bad reason to find out you’re affected! This does only affect people without a private domain, if you have a private domain you’re all good.

This looks to be another little hangover from the June (and April) 2021 release notes where this was announced, but seemingly not actioned. The default timing is being cut from 14 days to 2 days. Makes it quicker for contacts to be removed for you to bring them back in at a later stage. If you’re not sure what this means for you, take a look at the June 2021 and the April 2021 release notes and do a ctrl-f for Contact and you’ll find some more info about it there.

Currently if you get a large queue of people sitting in a Journey Builder email send activity as a result of an unsubscribe or any other reason, you would need to raise a support case to clear that queue. In the new release, you’ll be able to jump into the journey version, click on View Queue and you’ll have the option to delete the queue.

If you’re in an org with SFMC with the Pro edition, Datorama Reports for Marketing Cloud is now an option for you. Reach out to your account exec to find out if it’s included in your current contract or if it’s an additional line item. If you’re already using Discover reporting I would suspect it’s included but definitely worth a check! It’s a great tool to get under your belt (and it’ll only get better as they bring all the attributes into it)

If you’re subscribed to Datorama Reports Advanced, you’ll find a few new things made available to you including:

  • Using Query Builder to get more granular subscriber level insights
  • A single place to manage your calculated fields and custom KPIs
  • Management of App Credentials for all platforms available within Datorama in the Cross Channel tab

This is a slightly peculiar set of features for this release. Besides the fact that there seems to be a number of items included in previous release notes coming back through again. Some with very little change in the release note itself (Looking at you Contact Suppression).

It doesn’t feel like there’s anything earth shattering in this release. It feels like a range of items that Salesforce has been warning are going away are actually due to be closed down. After the last set of releases were very much about “here’s some cool new stuff you get!” with Journey Builder and Einstein, there’s only a few things here that stand out for me. What I’m surprised and slightly disappointed about is there has been no announcement in these release notes about how Einstein STO and the email engagement models are going to respond to the iOS15 release. Salesforce has been seriously encouraging businesses to introduce these machine learning algorithms to their workflows and their campaign management for over a year now. But, with one fell swoop a huge chunk of many businesses’ customer base is about to have that data validity massively undermined and Salesforce (at least in a public setting) seems to be pretty quiet about it.

This is the last set of release notes before iOS15 lands and there is no indication of how Salesforce recommends you deal with this change to the data feeding their product. If you’ve put a load of your eggs into the Einstein basket as a result of the investment in the tools over the last year, reach out to your account exec and ask how they are managing this change. With just one release left to go in 2021, what’s on your wishlist? We’ve got 10 weeks until that release gets announced so not long before it’ll be 2022!

Read more
Jason Cort
Published 05/29/2021
Release Notes
June 2021 - Release Notes Summary

It feels like only yesterday I was writing about an upcoming batch of feature enhancements, retirements and changes - time flies when you’re in the world of SFMC.These features should all land in your org between 5th and 19th June - if you want to see the official release features webinar from Salesforce, you can sign up to that here. However, if you can’t wait that long, I’ve been through the whole lot and have summarised what I think you need to know in time for the release.

It’s a very “giving” centric release this time around with the key element being another reminder that the V2 connector is going away… It was supposed to have already gone away back at the end of March but I’d guess that some big clients haven’t been reading these release notes reviews and there’s been a short extension. If you know anyone who is responsible for an SFMC instance that is connected to Salesforce CRM via the V2 connector, do them a favour and send them a link to this page.

Classic Cloudpages is officially at its end of life, starting from August 2021 the ability to create new Microsites and Landing Pages will be retired. January 2022 you’ll no longer be able to edit them and then in June 2022 they will be removed from the internet on your behalf. So, this is a burning platform warning - You have a year to get anything that is currently on a classic Landing Page or Microsite off of that infrastructure and into Content Builder. Please do not leave this to the last minute, there are very real contact facing risks if you have things like preference centres hosted in Classic. You do not want to be a part of the team who didn’t migrate preference centres and face the legal ramifications for preventing people from unsubscribing.

But, it’s not all doom and gloom and things going away, as I mentioned above - there’s a lot being added and enhanced in this release so let’s move on to the fun stuff!

Package Manager is getting an update in the June release to cover more elements of SFMC as a platform. Which is good. But, I’ve not been able to successfully deploy a full package based on what is currently supported… which is not so good. So, take this one with a pinch of salt. That said, if you’ve managed to get it working and have built any awesome packages that you think other Marketing Cloud users would benefit from then please get in touch. We’d love to feature some community packages to help others with this new feature. Whether it’s a bunch of standard SQL queries or an automation that enables you to identify contacts without a channel address and mark them for deletion. Get in touch and we can host and link out to your website/social profiles.

Distributed Marketing is getting a couple of comforting features in the new release which will be good news for those who have to rely on others to load content by allowing you to load images from your local machine. Marketing Cloud does recommend limiting the file size below 3Mb though, which is less than if you were hosting within Content Builder at 5Mb.

Also coming is the Distributed Marketing Collaborative Campaign (Auto Send) release going general availability rather than the limited release from last time. Just remember, anything you do from a personalised experience perspective using this tool is at the campaign level, not the person. So, if you do use this just be aware of the compromises you may have to make for your contacts.

There’s a couple of new features and expansions hitting Ad Studio in the next few weeks, including the ability to get increased visibility and control of failed leads when using Lead Capture capability. So keep an eye out for that in your Sales Cloud instance!

You will also be able to “target and tailor advertising to the millennial and Gen Z markets” using customer emails in Snapchat. You can also use Snap Lookalike Audiences to expand your reach within the Snapchat advertising ecosystem. Just remember to authorise your Snapchat account before you try to create an audience to throw at Snapchat.

Einstein gets a raft of new toys to play with in this batch of release including some predictions and previews you can use before sending out a campaign, rather than it being post event analysis for the user.

Predict Subject Line Performance will be made available in the Einstein Copy Insights dashboard, using historic subject line performance as part of a testing package. It’s not clear how this would work in relation to the target audience (or even if it does), so it may not work with pre-existing subject line optimisation that you may have in place. Such as if you already do sentiment analysis of subject lines at a contact level. But for a broad brush approach it should help level up email performance.

Einstein Content Asset testing is being added to Einstein Content Selection, so if you’re using Content Builder drag and drop blocks, you’ll be able to drag an Einstein Content Testing block into your email and see the results within Einstein Content Selection. You’ll also be able to view performance analytics at a tag level, so where Einstein is tagging your content for you you can drill into the Performance Analytics tab to see how different tags are performing.

Integrate your Einstein Content Assets with the new Einstein Content Selection Asset API. Previously you were dependent upon uploading content via the user interface or in a batch via a CSV. The new API will allow you to push assets from a Digital Asset Management platform directly to Einstein Content Selection.

Einstein STO gets a nice upgrade in this release, previously you would only be able to see the sending profile of a whole SFMC Org or Business Unit. Which is fine, but if you’re doing advanced segmentation you may want to manage the flow of communications better by knowing when a specific audience is most likely to engage with an email. With this release you’ll be able to select a Data Extension with contacts in to get a specific audience STO profile and whether there is sampling involved with the dataset.

Engagement Frequency gets the multi-channel treatment by being made available for MobilePush activities as opposed to just Email. This will include the Frequency Split activity in Journey Builder too. Unfortunately, the release notes suggest that this will need to be configured within the activity in Journey Builder, so it won’t automatically detect whether the next activity is a Push or an Email send and apply the appropriate criteria. But if you’re worried you’re sending too many Push messages, this will help to mitigate against that with the help of Einstein.

If you’ve yet to get Datorama enabled for your account, I would highly recommend you do so as soon as possible. With Discover on the way out and needing to learn a new tool that is completely different (but also pretty cool in some ways!) then please don’t leave it too late. In a past life I’ve had over 60 Discover Reports and I do not envy anyone who has to migrate that many reports to a new tool.

Datorama Reports Advanced is now officially released as part of this deployment, there’s really not much clarity about who will already have access to this based on the description of “Who” in the release notes page. It just says the upgrade is available to Corporate and Enterprise customers as well as Pro customers who have access to Discover. So contact your account rep to find out more. Datorama Advanced appears to allow a wider range of performance metrics as opposed to just Email/Journey metrics to be displayed. One such example is a new Audience Insights dashboard for your Ad Studio activities.

There are some non-premium enhancements to Datorama as well, thankfully! One of the previous limitations with the tool was that only reports of up to 8000 rows were able to be returned from a pivot table. This limitation has been removed, as has test data. Which is good because seeing a load of poorly performing Journeys called SIMULATION was not that helpful overall.

So whilst classic Landing Pages and Microsites are on their way out, Content Builder and CloudPages are getting some new functionality and improvements. There’s a few little ones to just list off

  • The Create button in Content Builder should start to load quicker when you access the tool, rather than it taking a few seconds to appear or in some cases not appear because of a timeout.
  • Canadian SMS preview for Content Builder is now limited to up to 160 characters for GSM messages, non-GSM and concatenated have different preview caps.
  • Smart Capture gets the ability to upsert into a DE with Primary Keys attached. Previously it would just append data which wasn’t ideal for some use cases.

Tenant Specific Endpoints are coming for CloudPages. This doesn’t appear to affect anyone who has a private domain, but if you currently have Pages hosted on a https://pub.exacttarget.com or https://[GUID].pub.sfmc-content.com these will be updated to be https://[TenantSpecificEndpointString].pub.sfmc-content.com. These URLs will be live from June 2021 and pages created after that will automatically be configured with the specific endpoint URLs. There will temporarily be a redirect service in place for older CloudPages, but there’s no indication for how long that will be the case.

Google Analytics 4 will be included in the Google Analytics integration between GA and SFMC. So you may find some of the old UTM Parameters you currently use are no longer needed for tracking your SFMC campaign performance. Whether this is accurate for your use cases will likely be dictated by the wider analytics ecosystem, but it may be possible to free up some attributes to add new capabilities to your reporting.

CloudPages & New CloudPages Experience will both be getting renamed. New CloudPages experience will become CloudPages and CloudPages will become CloudPages Legacy. The release notes state that this will be in place for one release before CloudPages Legacy gets removed. If you’ve not gotten used to the new experience, there’s now a short window of opportunity to get familiar with it. The next release is only in August.

WhatsApp messaging will be able to leverage contact details for sending messaging now. Previously you were required to include the recipient phone number in the sending Data Extension, so this brings it more aligned with other channels.

The opportunity to self-serve for some standard configuration and business rules gets a new feature - the option to disable the requirement for the out of the box preference centre. For many orgs the standard preference and subscription centre doesn’t meet their requirements and previously you would need to contact support to change a business rule configuration to use a custom solution.

The Marketing Cloud Connect API will be upgraded to v51 to unlock some of the more recent objects within Salesforce CRM, Loyalty Management and Order Management. These have been unavailable in SFMC since their release due to the MC Connect API being out of date, this does happen on occasion but has been significantly better in the last couple of years. So, if you have a Salesforce CRM use case with Loyalty Management and Order Management, keep an eye out from w/c June 21st 2021 and you’ll be able to Synchronise these objects and use them for Journey Builder entries.

Contact Delete default suppression is being chopped down to just 2 days compared to the original suppression of 14 days. This is expected to happen in a future release, but if you have any existing process to delete contact using the Contact Delete process and have left it on the default, this is something to keep an eye out for. The importance of this is that 14 days suppression prevents contacts being reintroduced or messaged, which provides contingency to other systems that may be aiming to introduce contacts to SFMC. By reducing the default to 2 days, contacts will be eligible to re-enter SFMC after just 2 days as opposed to 2 weeks from a deletion action. Make sure if you’re deleting contacts using the Contact Delete capabilities that any systems integrated with SFMC are able to act on the deletion within those 2 days otherwise jump in to SFMC now and change the date to prevent that risk coming to fruition.

Well, it’s obvious that Einstein is the Salesforce current favourite toy right now after the last 12 months scuffling over that title with Journey Builder! But, a lot of the challenges that have been raised about Einstein are starting to be addressed, including the fact that it has previously been so heavily geared towards reactive analytics or huge sweeping predictions that it’s impossible to really validate them. The layers of the mystery box of machine learning seem to be coming a little more to the forefront and the value of using historical data to determine the future is starting to come to fruition. This is where Einstein should be.

What I would love to see from Einstein in the future is the ability to introduce custom weighting & third party data to the scoring. If we can start introducing transaction data to make sure we’re not just sending an email that Einstein thinks someone will open and when they will open it, but when they are most likely to open and transact from it.

The opportunities of these tools and the capabilities afforded by AMP Email (Make sure you tune in to Connections 2021 where HowToSFMCs Genna Matson, Salesforce MVP, Marketing Champion will be talking through AMP Email alongside Eliot Harper, Salesforce MVP) could really give SFMC a huge edge when it comes to an integrated customer experience.

On the whole, there’s a lot of new toys to play with in this release, especially if you’re an Ad Studio user and have the budget available to add Datorama Advanced Reporting. If you’re not making the most of Einstein already, there’s more and more reasons being added to the argument that you should include it in your workflow.

Read more
Jason Cort
Published 04/08/2021
One More Time - Winners Announced

We said we would invite you all to do it One More Time & you did not disappoint. We had some absolutely amazing entries to this challenge and it was definitely a tough decision picking some of these winners. Fortunately, the individuals who came through and clinched the victory have taken the time to break down their solutions and help share their experience for us all to learn. Take a look through these articles and let us know if you think they managed to Get Lucky or if they were just Doin’ It Right.

Efficiency: The challenge lyrics are 2303 characters long, and your job is to find the least amount of characters necessary in SSJS to output these lyrics. This challenge will be measured solely via the character count. We will be utilizing https://www.charactercountonline.com/ to count the characters in your submission.

Winner: Rafał Wolsztyniak (Article)

Twitter handle: @HelloRafal - yep, no posts there

LinkedIn Profile: https://www.linkedin.com/in/rafal-wolsztyniak/

Trailblazer.me id - https://trailblazer.me/id/rwolsztyniak

Innovative: This category is all about the unique ways you can make SSJS work. We will be concentrating on not just the uniqueness but also the performance. Submitting something stunningly unique and cutting edge that triples the performance required, could severely hurt your chances.

Winner: Eliot Harper (Article)

Twitter handle: @eliotharper

LinkedIn Profile: linkedin.com/in/eliot

Trailblazer.me id: trailblazer.me/id/eliot

Simplicity: Good code should be clean, simple and easy to understand. The main goal of this category is to identify how much effort a beginner/non-developer would require to understand and utilize your script. The simpler the better, but it should also be performant and within best practices. “Dumbing it down” will not be the solution to this.

Winner: Manjunath S.R (Article)

Twitter handle - SfmcIn

LinkedIn Profile - https://www.linkedin.com/in/manjunath-sr-40232199

Salesforce Trailblazer.me id - https://trailblazer.me/id/manjunathsr

Special Jury Award: We all love good, clean, efficient code - but sometimes you have an idea that sounds bizarre enough that it might just work. Your script will still need to be functional, but if you feel that one of the best ways to flex your SSJS muscles is to produce something completely unexpected but incredible, then we want to see it too.

Winner: Vijaya Sankar Natarajan (Article)

Twitter handle - @vijayasankarn

LinkedIn Profile - https://www.linkedin.com/in/vijayasankarn/

Trailblazer.me id - https://trailblazer.me/id/vijayasankar

Huge congratulations to Rafał Wolsztyniak, Eliot Harper, Manjunath S.R and Vijaya Sankar Natarajan. Your vouchers will be with you shortly. We’re massively impressed by the skills and approaches that you’ve shared with us.

And yes, it was difficult to get through this post without mentioning Daft Punk calling it quits just after we posted this challenge originally. We promise it wasn’t our fault.

Thanks again for everyone who took part in this, there were some fantastic solutions presented. If you would like to tackle this challenge as part of your own learning exercises, take a look at the articles above and they may just inspire you. Whilst the competition may be over, we’re sure there are more solutions to this challenge so if you do have something you want to share, get in touch!

Read more
HowToSFMC
Published 04/08/2021
Winning Solutions
One More Time - Simplicity Winner

Start with breaking the given songs into words and stanza’s and then use it wherever is needed. Firstly assign all the repeated words to a variables

var nl = "&nbsp;<br>";
var omt = "One more time<br>";
var wgc = "We're gonna celebrate<br>";
var oy = "Oh yeah<br>";
var dsd = "Don't stop the dancing<br>";
var mgff = "Music's got me feeling so free<br>";
var cdf = "Celebrate and dance so free<br>";

Create stanza’s as much as possible to avoid the repeated use of variables/words

var stanza1 = omt + wgc + "Oh yeah, all right<br>" + dsd;
var stanza4 = omt + mgff + wgc + cdf;
var stanza2 = "Mmm, you know I'm just feeling<br>Celebration tonight<br>Celebrate<br>Don't wait too late<br>Mmm, no<br>We don't stop<br>You can't stop<br>"+wgc;
var stanza3= "Celebration<br>You know we're gonna do it right, tonight<br>Hey! Just feeling<br>Music's got me feeling the need<br>Need, yeah<br>Come on, all right<br>"+wgc;

Create possible groups to reduce the line of code and make efficiency

First group: In this example I have grouped below lines in one loop.

One more time
Celebrate and dance so free
Music's got me feeling so free
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free
One more time
Music's got me feeling so free
We're gonna celebrate
Celebrate and dance so free

Below is the logic for the above line of text:

for(j=1;j<16;j++)
{
Write(stanza4);
if(j==5)
Write(nl);
if(j==9)
Write(omt + mgff + wgc);
if(j==11)
Write(nl);
}

Wherever we need a newline or additional words add the condition to apply on that step inside the loop instead of breaking the loop.

Second group:

One more time
We're gonna celebrate
Oh yeah, all right
Don't stop the dancing
One more time
We're gonna celebrate
Oh yeah, all right
Don't stop the dancing
One more time
We're gonna celebrate
Oh yeah, all right
Don't stop the dancing
One more time
We're gonna celebrate
Oh yeah
One more time
One more time
We're gonna celebrate
Oh yeah, all right
Don't stop the dancing

Below is the logic for the above line if text:

for(j=1;j<6;j++)
if(j==4)
Write(omt + wgc + oy+omt+nl);
else
Write(stanza1);

In last combine all in one single group.

<script runat="server" language="javascript">
   Platform.Load("Core","1");
   //Assign the repeated words to a variables
   var nl = "&nbsp;<br>";
   var omt = "One more time<br>";
   var wgc = "We're gonna celebrate<br>";
   var oy = "Oh yeah<br>";
   var dsd = "Don't stop the dancing<br>";
   var mgff = "Music's got me feeling so free<br>";
   var cdf = "Celebrate and dance so free<br>";
   //create stanza's as much as possible to avoid the repeated use
   var stanza1 = omt + wgc + "Oh yeah, all right<br>" + dsd;
   var stanza4 = omt + mgff + wgc + cdf;
   var stanza2 = "Mmm, you know I'm just feeling<br>Celebration tonight<br>Celebrate<br>Don't wait too late<br>Mmm, no<br>We don't stop<br>You can't stop<br>"+wgc;
   var stanza3= "Celebration<br>You know we're gonna do it right, tonight<br>Hey! Just feeling<br>Music's got me feeling the need<br>Need, yeah<br>Come on, all right<br>"+wgc;
   //Wherever possible create loop/condition statement to avoid unnecessary usage of codes 
   for (i=1;i<7;i++)
   {
	Write(omt);
	if (i==2)
	{
	 Write(nl);
	 for(j=1;j<6;j++)
	  if(j==4)
	   Write(omt + wgc + oy+omt+nl);
	  else
	   Write(stanza1);
	 Write(omt + wgc + oy + dsd + omt+nl+stanza2);
	}
	if (i==5)
	 Write(stanza3);
	if (i==6)
	{
	 Write(cdf+mgff+cdf);
	 for(j=1;j<16;j++)
	 {
	  Write(stanza4);
	  if(j==5)
	  Write(nl);
	  if(j==9)
	  Write(omt + mgff + wgc);
	  if(j==11)
	  Write(nl);
	 }
	} 
   }
</script>
Read more
Manjunath S.R
Published 04/07/2021
One More Time - Efficiency Winner

Here&

<script runat="server">w="Celebration Celebrate celebrate Music's dancing feeling tonight right, Don't Need, We're can't dance don't gonna right we're yeah, Come Hey! Just Mmm, free just know late more need stop time wait yeah I'm One You all and got on, the too you Oh We do it me no so".split(' ');s="⣰⣰♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢯♫⣰♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢯♫⢘⢬⢷⢔♫⣰♫⢥⢹⢨⢰⢧⢕♫⢐⢖♫⢑♫⢘⢮⢸⢩♫⢥⢿♫⢻⢝⢬♫⢲⢛⢬♫⢚⢞⢒♫⣰⣰⣰⢐♫⢲⢨⢠⢞⢼⢽⢗⢖♫⢣⢤⢕♫⢓⢵⢾⢕⢷⢫♫⢙⢯♫⢢⢶⢳⢟♫⢚⢞⢒♫⣰⢑⢴⢜⣀⢦♫⢓⢵⢾⢕⣀⢦♫⢑⢴⢜⣀⢦⣷⣷⣷⣷⣷♫⣷⣷⣷⣷♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒⣷⣷♫⣷⣷⣷⣷".replace(/⣷/g,'♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒♫⢑⢴⢜⣀⢦').replace(/⣰/g,'⢱⢪⢭♫');for(c=0;c<s.length;c++){i=s.charCodeAt(c)-10384;if(i>=0){t=w[i]+' '}if(s[c]=='♫'){t='<br>'}Platform.Response.Write(t)}</script>

If you want to implement it, just paste it on a CloudPage:

  • either within the <body> tag of the page
  • or remove anything and allow the above code to be the only thing placed on the page

The code is not perfect and I realized a few things that I could have done differently to make the script shorter by a few characters (which I’ll mention down below).

First let&

<script runat="server">
var words = "Celebration Celebrate celebrate Music's dancing feeling tonight right, Don't Need, We're can't dance don't gonna right we're yeah, Come Hey! Just Mmm, free just know late more need stop time wait yeah I'm One You all and got on, the too you Oh We do it me no so".split(' ');
var structure = "⣰⣰♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢯♫⣰♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢯♫⢘⢬⢷⢔♫⣰♫⢥⢹⢨⢰⢧⢕♫⢐⢖♫⢑♫⢘⢮⢸⢩♫⢥⢿♫⢻⢝⢬♫⢲⢛⢬♫⢚⢞⢒♫⣰⣰⣰⢐♫⢲⢨⢠⢞⢼⢽⢗⢖♫⢣⢤⢕♫⢓⢵⢾⢕⢷⢫♫⢙⢯♫⢢⢶⢳⢟♫⢚⢞⢒♫⣰⢑⢴⢜⣀⢦♫⢓⢵⢾⢕⣀⢦♫⢑⢴⢜⣀⢦⣷⣷⣷⣷⣷♫⣷⣷⣷⣷♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒⣷⣷♫⣷⣷⣷⣷".replace(/⣷/g,'♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒♫⢑⢴⢜⣀⢦').replace(/⣰/g,'⢱⢪⢭♫');
 
for(c = 0; c < structure.length; c++){
    var index = structure.charCodeAt(c) - 10384;
    if(index >= 0){
        var text = words[index] + ' '
        }
    if(structure[c] == '♫'){
        var text='<br>'
        }
    Platform.Response.Write(text)
    }
</script>

You can see here that the scripts consists of 3 parts:

  • the words variable (w in the minified version) telling us what words should be in the lyrics
  • the structure variable (s) which tells us when words should appear in the song
  • and the for loop that is combining the two variables to output the lyrics on the CloudPage

This variable contains all words found in the lyrics of the Daft Punk hit sorted in such manner that words with the longest character count.

In the submitted code we see this variable starting as a string, but it&

w="Celebration Celebrate celebrate Music's dancing feeling tonight right, Don't Need, We're can't dance don't gonna right we're yeah, Come Hey! Just Mmm, free just know late more need stop time wait yeah I'm One You all and got on, the too you Oh We do it me no so".split(' ')

while the most compact way of writing the same set of words as an array would take 363 characters which is longer by 87 characters:

w=["Celebration","Celebrate","celebrate","Music's","dancing","feeling","tonight","right,","Don't","Need,","We're","can't","dance","don't","gonna","right","we're","yeah,","Come","Hey!","Just","Mmm,","free","just","know","late","more","need","stop","time","wait","yeah","I'm","One","You","all","and","got","on,","the","too","you","Oh","We","do","it","me","no","so"]

Arrays are objects in JavaScript that work like lists - they can contain multiple elements and each one of them can be accessed by calling their index (their location). To demonstrate this, let&

var words = ["We’re", "can't", "dance"];
// Index:       0        1        2

Indexes are zero-indexed which is to say that:

  • the index representing the very first element of an array is always a 0,
  • the second one element is accessed by 1, the third one by 2, etc.

We get values stored in arrays by using the name of the array and placing the location of the desired element in square brackets like so:

// Get values of individual array elements:
var firstElement  = words[0];   // Result: "We're"
var secondElement = words[1];   // Result: "Can't"
var thirdElement  = words[2];   // Result: "Dance"

We know the words now, but don&

The song has:

  • 434 words with the titular lyrics appearing 30 times

  • 117 lines in total (which means we need 116 line breaks

  • a chorus that&

      One more time
      Music's got me feeling so free
      We're gonna celebrate
      Celebrate and dance so free
    

which I managed to get represented in the script with a total of 266 characters.

s="⣰⣰♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢯♫⣰♫⣰⢚⢞⢒♫⢺⢡⢳⢟♫⢘⢬⢷⢔♫⣰⢚⢞⢒♫⢺⢯♫⢘⢬⢷⢔♫⣰♫⢥⢹⢨⢰⢧⢕♫⢐⢖♫⢑♫⢘⢮⢸⢩♫⢥⢿♫⢻⢝⢬♫⢲⢛⢬♫⢚⢞⢒♫⣰⣰⣰⢐♫⢲⢨⢠⢞⢼⢽⢗⢖♫⢣⢤⢕♫⢓⢵⢾⢕⢷⢫♫⢙⢯♫⢢⢶⢳⢟♫⢚⢞⢒♫⣰⢑⢴⢜⣀⢦♫⢓⢵⢾⢕⣀⢦♫⢑⢴⢜⣀⢦⣷⣷⣷⣷⣷♫⣷⣷⣷⣷♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒⣷⣷♫⣷⣷⣷⣷".replace(/⣷/g,'♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒♫⢑⢴⢜⣀⢦').replace(/⣰/g,'⢱⢪⢭♫');

You can see the structure is being stored in a string again and that&

The String.split() method is not used here, because we can treat strings as arrays of characters. This allows us to access each of them with the syntax for accessing array elements, for example if we would want to get the second letter of the word "One" we would call the index [1] and this would give us the letter n. Just as with arrays, we can utilize loops to execute the same code on each element of our object or, as in our case, each character.

The Dots

There&

This is known as the [Braille pattern dots-1568](https://en.wikipedia.org/wiki/Braille_pattern_dots-156

If we use the string method String.charCodeAt() on this character we can learn that this Unicode-encoded character has a decimal value of 10417. You know now that the words are stored in an array and that we can access individual elements of them with a number, so you probably know where this is headed - is used to get the word "One". If we subtract 10384 from the decimal encoding value, we land at a value of 33 - this is the index of the word "One" in our word array.

Character CharCode Index words[index]
10417 33 One
10410 26 more
10413 29 time

The Notes and Replacements

The song structure string contains characters which simply indicate line breaks in the code. The replace methods are used to compress the song structure by expressing parts that are often repeated by a single character in the original string:

Character Compressed string Resulting output Number of occurrences
⢱⢪⢭♫ One more time <br> 30
♫⣰⢓⢵⢾⢕⣀⢦♫⢚⢞⢒♫⢑⢴⢜⣀⢦ <br>One more time <br>Music’s got me feeling so free <br>We’re gonna celebrate <br>Celebrate and dance so free 15

The allows us to reduce the size needed to represent the structure from 555 to 266 and while I was writing this article I realized I totally missed an opportunity to compress the first chorus:

One more time
We're gonna celebrate
Oh yeah, all right
Don't stop the dancin

It&

  • the two conditional if statements could be optimized to become just one if... else statement
  • and the index variable didn’t really need to be declared

which would reduce the code length by 8 characters, but let&

for(c = 0; c < structure.length; c++){              //1
    var index = structure.charCodeAt(c) - 10384;    //
    if(index >= 0){
        var text = words[index] + ' '
        }
    if(structure[c] == '♫'){
        var text='<br>'
        }
    Platform.Response.Write(text)
    }

Here&

  • the for loop reads the characters of the structure string one by one
  • it calculates the index for the character as described previously
  • if the calculated value is above 0 (is not the line break character), temporarily assigns corresponding word and a space to the text variable (this way we can omit putting spaces into the structure string and have them added automatically)
  • if the code encounters the beamed eight-note (), the the <br> tag is assigned to the text variable
  • when both checks are done, the code writes the current text value to the body of the CloudPage

Platform vs Core library

Marketing Cloud developers have two libraries that they can choose, but in this case selecting the winner was quite simple when the function responsible for writing the output of the code to the page is used only once - it&

Platform.Response.Write(t) // 26 characters
Platform.Load("core","1");Write(t) // 34 characters

It was a fun challenge and I feel I got lucky with it winning despite many missed opportunities for further optimization. Let’s just hope that in the future we’ll get a chance to compete in a challenge like this ⢱⢪⢭.

 

I&

Read more
Rafał Wolsztyniak
Published 04/07/2021
One More Time - Innovative Winner

I recently participated in a contest organised by the HowToSFMC community, where contestants had to display the lyrics of the song ‘One More Time’ using Server-Side JavaScript (SSJS) in Salesforce Marketing Cloud, in an efficient manner.

My initial observation was that while the chorus and bridge in the lyrics are short and repetitive, but they follow an irregular pattern.

To present the lyrics in a manner that they could be reconstructed, I firstly identified distinct lines in the lyrics and stored them in an array—arrays are a great fit for this task, as they provide a very convenient method of storing multiple values in a single variable.

var lyrics = [
   "One more time",
   "We're gonna celebrate",
   "Oh yeah, all right",
   "Don't stop the dancing",
   "Oh yeah",
   "Mmm, you know I'm just feeling",
   "Celebration tonight",
   "Celebrate",
   "Don't wait too late",
   "Mmm, no",
   "We don't stop",
   "You can't stop",
   "Celebration",
   "You know we're gonna do it right, tonight",
   "Hey! Just feeling",
   "Music's got me feeling the need",
   "Need, yeah",
   "Come on, all right",
   "Celebrate and dance so free",
   "Music's got me feeling so free"
]

My next task was to reassemble these distinct lines back to their original order. The simple option would have been to create a separate array of indices from the first array and then output them. For example, the following lyrics:

One more time
Music’s got me feeling so free
We’re gonna celebrate

…could be stored in an array based on their zero-based index in the lyrics array. Then this array could be looped to retrieve the corresponding line and output with a line break, like the following example:

var order = [0, 19, 1]

for (var i = 0; i < order.length; i++) {
  Write(lyrics[order[i]] + '\n');
}

While this is simple, the issue is that there are a total of 112 lines (excluding paragraph breaks), which would require a very long array of indices. However, from studying the lyric patterns, I observed two pattern formations:

  1. A repeat pattern, where a set of lines were repeated two or more times, and
  2. An increment pattern, where a set of lines in the lyrics array appeared consecutively.

To take advantage of these patterns, I optimized my code by creating separate functions to parse each pattern.

To display repeating lines, I created a repeat function which accepts three arguments:

  1. An array of line indices (of the lyrics array) represented by the arr parameter,
  2. The number of times to repeat the lines represented by the count parameter, and
  3. An optional integer that inserts a line break after a section of repeated lines represented by the line parameter.

The function is provided below.

function repeat(arr, count, line) {
   for (i = 0; i < count; i++) {
      for (j = 0; j < arr.length; j++) {
         order.push(arr[j]);
      }
      if (line === i + 1) {
         order.push(-1);
      }
   }
}

If the line parameter is included, then a -1 index is appended to the array which will insert a line break (refer to my explanation of the [output function](

For example, the following code:

repeat([0, 19, 1, 18], 6, 2);

…will repeat lines 0, 19, 1 and 18 from the lyrics index, six times. Additionally, a paragraph break will be inserted after the second line set, resulting in the following output:

One more time
Music’s got me feeling so free
We’re gonna celebrate
Celebrate and dance so free // end of 1st line set
One more time
Music’s got me feeling so free
We’re gonna celebrate
Celebrate and dance so free // end of 2nd line set
// line break inserted here
One more time
Music’s got me feeling so free
We’re gonna celebrate
Celebrate and dance so free // end of 3rd line set

One more time
Music’s got me feeling so free
We’re gonna celebrate
Celebrate and dance so free // end of 6th line set

When the function is invoked, the resulting indices are appended to the order array using the JavaScript push() method.

The increment function accepts two arguments:

  1. A start index number (from the lyrics array), and
  2. An end index number.

The function is provided below.

function increment(start, end) {
   for (i = start; i < end + 1; i++) {
      order.push(i);
   }
}

When the function is invoked, the corresponding indices are appended to the order array using the JavaScript push() method. For example, the following code:

increment(5, 11)

…will return lines 5–11 from the lyrics index, resulting in the following output:

Mmm, you know I’m just feeling
Celebration tonight
Celebrate
Don’t wait too late
Mmm, no
We don’t stop
You can’t stop

The final task is to output the lines from the lyrics array in the order that they appear in the order array.

To achieve this, an empty result array is defined to store the resulting strings. The function accepts a single arr parameter (with the array of line indices), and adds each line to the result array using the JavaScript push() method.

The function is provided below.

function output(arr) {
   var result = [];
   for (i = 0; i < arr.length; i++) {
      result.push(lyrics[arr[i]]);
   }
   return result.join('\n');
}

The result array now contains an ordered array of lines to display. For example, including Write(Stringify(result)) in the function will return the following output:

["One more time",
"One more time",
null,
"One more time",
"We're gonna celebrate",
"Oh yeah, all right",
...
"Music's got me feeling so free",
"We're gonna celebrate",
"Celebrate and dance so free"]

Note that where paragraph breaks are required, the index value was defined as -1 which returns a null value (as there is no matching index in the lyrics array).

The final step is to join the values in the array and insert line breaks between each value. The JavaScript join() method converts the values of the array into a string. Additionally, a separator parameter is passed to the array to separate each line with a new line character (\n). And for paragraph breaks (indicated by an array value of null), this results in two consecutive line break characters (\n\n), to form a paragraph break.

Finally, the function is invoked by the following code, which retrieves the resulting string from the output() function and uses the Write utility function (provided by the SSJS Core Library) to output the resulting lyrics in the correct order.

Write(output(order));

While this script doesn’t have any practical context (I doubt that I’ll ever need to output song lyrics!), it was very helpful in creating a solution to parse datasets that contain similar characteristics, then process them in an optimal way. I look forward to applying this pattern to future projects!

The complete SSJS code is provided here.

Read more
Eliot Harper
Published 04/07/2021
One More Time - Special Jury Award Winner
  1. Login to the Marketing Cloud instance

  2. Navigate to Cloud Pages and click Create Collection.

  3. Name the Collection: !alt

  4. Hover over the collection and click Open Collection. !alt

  5. Click Create and select Landing Page in the next window !alt

  6. Enter the Name for the Landing Page and click Next in the Pop-up window: !alt

  7. In the next step, select any layout (Blank should work for us) and click Create: !alt

  8. In the Editor page, ensure the Code View is selected and on the left – Code Editor, replace all the lines with the following Code Snippet: !alt

    <script runat="server">for(Platform.Load("core","1"),a="Music's got me|and dance|so free|One more time|We're gonna|Come on|do it|too late|Celebrate|You|know|feeling|stop|yeah|Don't|Oh|the|right|all|dancing|Celebration|Need|tonight|Mmm|Just|no|We|can't|know I'm|wait|Hey!|,|<br>".split("|"),b="D~aDaaDa~EIaPN~SRaOMQTaDa~aOMQTaDaaXJ]YLaUWaIaO^HaXZa[OMaJ\\MaEIaDaDaDaUaJKEGRWa_YLaALQVaVNaFSRaEIaDaIBCaALCaIBCa~DaALCaEI~aIBC~a".split("~"),c="0121~323~12321~41~5675~71~5674~571~5672~71~5674".split("~"),d="a_J",f=e="",i=65;i<99;)e+=String.fromCharCode(i++);for(k in c)for(g=c[k],h=g.slice(-1),g=g.slice(0,-1),i=0;i<h;i++)f+=g.replace(/\d/g,function(a){return b[a]});for(k=0;k<384;)q=a[e.indexOf(f[k])],Write(!k&&q||k&&d.indexOf(f[k-1])>-1?q:q.toLowerCase()),++k<384&&"`"!=f[k]&&Write(" ")</script>
    
  9. Click Save and then Schedule/Publish: !alt

  10. Ensure Publish Immediately checkbox is checked and click Publish !alt

  11. Upon Successful publish, click on the link generated in the middle of the header section: !alt

The One More Time lyrics will be displayed!

The above code snippet is the minified version of the following code:

    Platform.Load("core", "1");
    SongString = "Music's got me|and dance|so free|One more time|We're gonna|Come on|do it|too late|Celebrate|You|know|feeling|stop|yeah|Don't|Oh|the|right|all|dancing|Celebration|Need|tonight|Mmm|Just|no|We|can't|know I'm|wait|Hey!|,|<br>".split("|");
    SongStringAsciified = "D~aDaaDa~EIaPN~`SRaOMQTaDa~aOMQTaDaaX`J]YLaUWaIaO^HaX`Za[OMaJ\\MaEIaDaDaDaUaJKEGR`Wa_YLaALQVaV`NaF`SRaEIaDaIBCaALCaIBCa~DaALCaEI~aIBC~a".split("~");
    SongStringCode = "0121~323~12321~41~5675~71~5674~571~5672~71~5674".split("~");
    ruleExclusion = "a_J";
    f = ASCIIChars = "";
    for (i = 65; i < 99;) 
        ASCIIChars += String.fromCharCode(i++);
    for (k in SongStringCode) {
        g = SongStringCode[k];
        h = g.slice(-1)
        g = g.slice(0, -1);
        for (i = 0; i < h; i++) {
            f += g.replace(/\d/g, function(a) {
                return SongStringAsciified[a]
            });
        }
    }
 
    for (k = 0; k < 384;) {
        q = SongString[ASCIIChars.indexOf(f[k])]
        Write(!k && q || k && ruleExclusion.indexOf(f[k - 1]) > -1 ? q : q.toLowerCase())
        ++k < 384 && "`" != f[k] && Write(" ")
    }
</script>

Analyze the song lyric to find:

  1. Unique strings.
  2. Strings that occur together.
  3. Count of phrases that repeat.

I used this tool to analyze: https://www.online-utility.org/text/analyzer.jsp

Manually identified the rules that are unique and differentiating:

  1. All first rows start with Capital Letters.
  2. Capital letters after certain punctuation marks, but not all (like exclamation mark ‘!’).
  3. Capital letters in the middle – for the character ‘I’.

Assign the single unique character for the repeating phrases to reduce the total character length for the SSJS Script. I used [ASCII Characters](https://www.ionos.com/digitalguide/server/know-how/ascii-codes-overview-of-all-characters-on-the-ascii-table/

Above three stages are the reflection of line 3 through 6. Notice all the values are arrays – string with split function.

_Line 3: _Unique phrases.

_Line 4: _ASCII Characters for each phrase in line 3.

Example:
ASCII Character Phrase
A Music’s got me
B and dance
C so free
D One more time
E We’re gonna
Character Sequence Phrase Combination
D One more time
aDaaDa One more time<br><br>One more time
EIaPN We’re gonna celebrate<br>Oh yeah, all right

_Line 5: _Algorithm to generate number of times the ASCII Code repeats.

For example: ASCII Converted String would be like this:

DaDaaDaEIaPNSRaOMQTaDaEIaPNSRaOMQTaDaEIaPN…

See number of times a sequence is repeated:

D aDaaDa EIaPNSRaOMQTaDa EIaPN SRaOMQTaDa EIaPN …

Here,

  • D occurs once.
  • aDaaDa occurs once.
  • EIaPN occurs thrice.
  • `SRaOMQTaDa occurs twice.

Similarly, a specific sequence keeps occurring multiple times. In order to, reduce the repetitive sequence, a number is allocated for each recurring sequence, resultant would be:

Index Sequence
0 D
1 aDaaDa
2 EIaPN
3 `SRaOMQTaDa
4 aOMQTaDaaX`J]YLaUWaIaO^HaX`Za[OMaJ\\MaEIaDaDaDaUaJKEGR`Wa_YLaALQVaV`NaF`SRaEIaDaIBCaALCaIBCa
5 DaALCaEI
6 aIBC
7 a

Now, the recurring sequence algorithm is be built and the last character is considered as count:

Explanation for this: 0121~323~12321~41~5675~71~5674~571~5672~71~5674

Sequence Repetition count
012 1
32 3
1232 1
4 1
567 5
7 1
57 1
567 4
57 1
567 2
7 1
567 4

Line 8 and 9 is to build the ASCII Characters.

Line 10 thru 19 is to unscramble the logic that we discussed above. Practically, it is all about replacing the numbers with the characters and then characters with the phrases of the song lyrics.

Line 21 thru 25 is to print the song lyrics. Additional logic handled here is where to capitalize the characters and where to proceed with the lowercase. Since the capitalization of the letter is handled based on the upcoming and previous word/string/character, the incremental operation is handled inside the looping structure.

Read more
Vijaya Sankar Natarajan
Published 04/01/2021
Get ready for Automation Studio Lightning

For those who are reading the below on a date that is not April 1st 2021 - This was our 2021 April Fools joke.

Everyone knows the Salesforce Mantra of ‘Clicks, not code’ and for a long time that has been challenging to deliver in the Marketing Cloud. The nuances and business specific requirements have historically introduced such complexity that Salesforce have had no choice but to allow end users to directly implement code. But, that may all change in April 2022 with the launch of Automation Studio Lightning.

Automation Studio Lightning is a large-scale overhaul of the Automation Studio we know and love and allows users to interact with the Marketing Cloud back end with clicks as opposed to code. Think of it like Journey Builder, but for everything you do in the Marketing Cloud. If you need to import a file from the FTP server, you’ll get to configure an “Entry Event” and either schedule it or have it respond to a file being placed on the server.

This new tool will bring the power of Automation Studio to non-developers who need to create complex workflows of data and campaigns. Using the drag and drop interface to create Automated PRocessing of Information fLows, FOr Other Less Specific use diagrams. With these you can drag activities into a running order rather than in steps. If you require parallelisation, each cluster of activities can be attached to another cluster of activities to run next to make sure you make the most efficient processes possible in the Marketing Cloud.

But, what about coding needs? This final push to eliminate end user coding requirements means that anything that currently requires code to work can be delivered using multiple steps in the Information Flows. It may not always be as elegant or efficient, but it will be far more accessible to those who are new to the platform or who don’t know and don’t wish to learn SSJS or AMPscript.

And Codey? Luckily for Codey, this push puts an end to a huge amount of the code based work for the Marketing Cloud. As a result, they will get an early retirement after handing over the reins to a new mascot to be announced later on in the year.

Do you have any questions about Automation Studio Lightning? Get in touch, we’d love to hear what your biggest queries are about the new platform due in the next year or so.

Read more
HowToSFMC
Published 03/29/2021
General
April 2021 - Release Notes Summary

As 2021 rolls on, so do the latest new features being brought to your Marketing Cloud orgs, courtesy of the release due to land between 10th-17th April. The priorities continue to be Journey Builder and operational efficiencies within the platform, including reducing the dependency on FTP file transfers for some customers depending upon your data infrastructure of choice.

Although the release has some interesting new capabilities landing in the next few weeks, it would be remiss to not mention the things that are due to leave us in the imminent future. First things first, V2 Marketing Cloud Connect - You have literally just a couple of days to get away from this and into the later integration. We summarised the options available in the previous release notes review.

If you’re using V2 and are on an edition of Salesforce CRM that supports it. If you are using a Salesforce Professional Edition, you will need to upgrade your Sales or Service cloud edition to Enterprise or Unlimited in order to access V5. If you’re not sure whether it makes sense for your business to do the upgrade, you can check out the difference between V2 and V5 here. If you’re not in a position to upgrade to V5, you may need to investigate other options for transferring data between your Marketing Cloud org and Salesforce.

But, it’s not just Salesforce Service/Sales Cloud that has an integration on its way out, if you’re using a legacy Microsoft Dynamics CRM On-Premise integration - October 29th this will be removed and you’ll need to rely on Email Studio functionality to carry on your activity. Dynamics is not my speciality, all I can suggest is you take a look at the release note here and check out the article from Microsoft on the enforcement of the Unified Interface in MS Dynamics here.

Salesforce continues to warn us of the eventual removal of Cloud Pages and its replacement with the New Cloud Pages Experience. It’s been almost a year since the preview was first announced and it was originally anticipated to replace in January 2021 so it could be relatively short notice that this experience replaces the current. It would be worthwhile spending time getting familiar with it before you don’t get a choice in the matter. If nothing else, the ability to copy a Cloud Page is definitely a perk!

There’s a suggestion that the Collect Code used for capturing data for Einstein Web and Email recommendations will be deprecated. If you’re actively using the collect code and streaming updates then you should keep an eye out for this change in the future. Reason for the change is that as an open API, it’s open to having unauthorised changes to recommendations data which would definitely be a little risky.

The final thing to be aware of is the as yet undetermined closure of legacy Stack Specific API Endpoints and the requirement of Tenant Specific Endpoints. There’s no end date in place for stack specific endpoints, so no urgent rush on it.

Now that all of the things that are going away is out of the way, let’s take a look at the new features to look forward to in the coming weeks.

If you’ve ever been in a position of needing to replicate successful implementations of an SFMC Org, you’ll be familiar with the process of either copying and pasting SQL Queries, writing duplication scripts and pointing between Business Units or Orgs. You may have previously used Deployment Manager, however, Package Manager is being positioned as the answer to enable you to deploy at scale. How this works with different elements is yet to be seen and I’m hopeful that it’ll enable some of the time consuming, repetitive elements of working with SFMC to be made much more swift. The screenshot provided by Salesforce in the release notes have this pegged as covering Assets, Attribute Groups, Automations, Data Extensions and Journeys. If this can be automated and be a method to create org wide snapshots on a daily basis to be retained, this would be a game changer.

This isn’t so much a new feature as it is a “We’ve suggested you turn this on, but some of you haven’t and therefore we’re no longer giving you the choice”. The feature is unchanged, except for the fact it is no longer an optional extra for your security. It’ll be enabled and the tickbox is going to go away.

This looks to be an interesting new piece of functionality for SFMC to pull data in from an S3 bucket. If you’re already using AWS to house your data and using an additional step in your process to load data to SFMC via FTP, this could remove some latency for you. You’ll just need to choose Amazon Simple Storage Service as a location type when you set up a new File Location. Not sure how this will work with File Drop automations (if at all), so perhaps Gortonington will need to revisit his blog post on the subject.

Auto Send is being given a limited release (Not sure how that makes it into the release notes, but it did so I’ll cover it) to some customers in the upcoming release. Auto Sending allows Campaign owners to provision an automatic send for a campaign, so for those who are selected for a campaign but not in a scheduled or bulk send to the linked Journey with default values. However, if the Auto Send is configured by someone who only has access to a proportion of the campaign audience - it’ll only be dispatched to that proportion of the campaign audience… and everything is personalised at a campaign level as opposed to a person level… So, I’ll call it Campaignalised instead.

If you’re an avid user of the Journey Analytics Dashboard, you may have thought “Dang, this takes up a lot of room and doesn’t show me a whole lot of data”. Well, that’s going to change as a result of a more data-intensive look and feel. You’ll need to integrate Google Analytics to see the dashboard in Journey Builder and from experience it does make things a little easier than flipping between multiple tools… Now if only Salesforce would condense the Journey Templates on the login screen. Or make them optional.

With the push to encourage more and more customers to use Einstein, Salesforce has a bit of a job to do to highlight the values, the benefits and the ways to push it further. If you build your own modelling tools, you can see exactly where your data is appropriate and suitable for your scenarios but as it stands in Einstein tools you don’t get much to look at besides a couple of gauges and some dots for Send Time Optimisation. In the new release you’ll get some more information about the data quality going into your Engagement Frequency and Engagement Scoring dashboards which is definitely nice to have.

Currently for Einstein Content Selection you may find yourself having to input an image URL for the assets to be included in the process. In the new release you’ll be able to use the URL, desktop files or files already in Content Builder, you could even use a HTML block, but only if it doesn’t contain any AMPscript. Nice to have if you’re an avid user of the tool.

For those who are using ECS, you may have had some challenges in the past of inappropriate content popping up in the Einstein Content Selection blocks in Content Builder. It’s definitely annoying when in the middle of a Global Pandemic or National Lockdowns that there’s a vacation deal that can’t be taken advantage of. But you can now set Spotlights on contents based on a specific attribute and its associated value. Now you can make sure nobody gets offered $25 off a flight even when they can’t go anywhere.

Einstein Messaging Insights will be giving some additional context to the conclusions it is making around the anomalies that may get presented. This should hopefully mean less time spent digging into an anomaly, only to find out that it’s not an anomaly it’s just a lapsed customer winback campaign that is performing as expected.

This will allow you to create a customised, near real time experience for contacts in Journey Builder. For those who are actively using Journey Builder, this has been a missing feature for a while. The ability to ensure a contact waits for a maximum amount of time to allow for a customer action to occur and then if not, provide a different customer experience is long overdue. Previously you would have to use multiple journeys, blend your operation between Journeys and triggered sends or just make all contacts wait for the maximum amount of time and use a decision split to determine the best course of action. This could be actioned from script activities, cloud pages or external systems, whichever makes the most sense for your use case - but definitely take a look when it goes live for you.

This is a useful addition to Journey Builder, which is notoriously difficult to triage issues where you see X people receive an email at one step, but suddenly only half as many make it to a later step. Take some of the guesswork out of what instigated the drop off or the increased failure rate so you can take action quickly. You’ll get up to 30 days worth of data in the view for non-wait activities, so it should even help with custom Journey Builder activities and more.

Apparently these are making a comeback. After announcing in May last year that .p12 certificates were being deprecated and providing an **Action Required **headline to the release note - these are back. You can now choose whether you want to go with a .p8 Auth Key or a .p12 certificate when you’re sending push notifications on the upgraded HTTP/2 sending service.

After launching a few months ago, Salesforce is making it easy for you to not send WhatsApp messages to contacts who are opted out. As a user of WhatsApp in SFMC, you are charged for every attempted message dispatch, regardless of whether it is delivered or not. So, even if the customer never received a WhatsApp message because they were opted out, you would still have been charged for it. Nice to have some way of preventing being charged for trying to do the impossible!

When it comes to deleting contacts, quicker seems to be the preference of SFMC users. The default currently is 14 days, but this will be reduced to 2 days in an upcoming release. The way contact deletion works, contacts enter a suppressed state and they cannot be interacted with or reintroduced to your org. 14 days is quite a long time, especially if someone has a legitimate requirement to be reintroduced. As a result, many customers have this suppression time set to between 0 and 2 days rather than the default 2 weeks. This just brings what has been determined best practice by SFMC users to be the default in the tool.

This is definitely not an oversized release, there are a few nice to haves being introduced which is good to see. That said, there has been a decrease in the scale and the impacts of releases in the last 12 months with the odd big hitter here and there. Is Salesforce innovating hard enough and fast enough?

I think there’s a big desire from engineers and end users to push the platform harder with the likes of a REST API equivalent to WSProxy or with the new Cloud Pages experience, some new Cloud Pages API capabilities. The holding pattern of iterative Einstein release, some long overdue Journey Builder feature and some compliance nice to haves are fine for now. But, these aren’t often game changing releases, they don’t level up the platform on the whole and they don’t scratch the itch that SFMC has historically been amazing at doing.

What do you want to see SFMC do in the next 6 months? What would be your game changer? Get in touch, we’d love to hear it.

Read more
Jason Cort
Published 01/15/2021
General
January 2021 - Release Notes Summary

It feels like only a couple of weeks ago that a group of trailblazers were looking at and reflecting on the year that was 2020 (Session 1 and Session 2) and bringing the SFMC year to a close, but it’s time for the first release of 2021!

One of the key characteristics of the releases throughout 2020 is that there was a focus on certain themes and bringing the overall platform into a better state. Permissions have been broken out of catch alls for Journey Builder, SFTP has been given a swift kick up the security upgrade path and content builder templates have been made more accessible. There’s been a strive to make SFMC more self supporting with the improved Setup window allowing users to toggle settings that previously required a support request and whilst the ambition was to make SSL certification self-serving in November, it’s making a rare second appearance in release notes this time around. The foundations of last year have meant that whilst the overall new capability increments were a little light on the ground, 2021 should be a big year for taking your Marketing automation efforts up a notch.

The majority of this you’ll find under the App and Setup heading in the release notes, but there’s some closely aligned elements in other categories so I wanted to bring them together.

As mentioned above, Self-Serve SSL certs are back in the release notes this time around. Currently you need to raise a support case to get SSL certification in place (If you’re on a private domain without an SSL, you’re probably getting sick of all the images not loading in Chrome by now!). But, unfortunately the “when” on this is essentially unconfirmed. So, hopefully this gets brought in time for this release, it would be a shame for it to make the cut 3 times in a row.

Some key _(no pun intended) _elements bringing themselves to the fore right now is encryption and the option to start using your own encryption keys as opposed to just the default ones available. The theme of bringing things up a level from a security perspective slipping into 2021 with the option of how you want to encrypt SFMC. Definitely something to look into if you’ve had worries that your data at rest is encrypted in the same way as other tenants on your stack.

The timer officially starts on the retirement of the SFMC Public Key and you will no longer be able to create new File Transfer activities using it after June 1st. So it’s great that BYOK is being rolled out to decrypting files received from other sources. The official advice from Salesforce is that you **stop **using the standard public key as soon as you can. There are some details for creating a key pair in the documentation here.

Social Studio is getting the Multi-Factor Authentication and Single Sign-On treatment in this release, so you’ll be able to enforce SSO and MFA once it is live.

As mentioned in previous release notes, the V2 Marketing Cloud connector is going away in less than 3 months now and the deep linking from Sales/Service Cloud in to SFMC is being removed in this release. If you’re using V2 and are on an edition of Salesforce CRM that supports it. If you are using a Salesforce Professional Edition, you will need to upgrade your Sales or Service cloud edition to Enterprise or Unlimited in order to access V5. If you’re not sure whether it makes sense for your business to do the upgrade, you can check out the difference between V2 and V5 here. If you’re not in a position to upgrade to V5, you may need to investigate other options for transferring data between your Marketing Cloud org and Salesforce.

It’s not all just good to go if you’re one of the multitude of users who is using the V5 app to integrate between SFMC and Sales/Service Cloud. You will need to make sure that your integration is using the connected app not the legacy integration to ensure connectivity between the two systems persists beyond January 30th. If you’re not sure whether you’re using the connected app or the legacy integration, jump over to the Salesforce Integration screen in Marketing Cloud & if you see a Tracking User Name and Tracking User Password field in the config settings - you’re using the legacy authentication method. Get that updated ASAP and you should be golden!

Possibly the single most updated element of SFMC in the past year, the capabilities for Distributed Marketing keep adding up! If you’re using Custom Personalization Interactions for segmented sends, you’ll now be able to use multiple tabs as opposed to one (recommendation is no more than 10 tabs with no more than 10 fields to keep it performant). This should be available now if you’re using version 230 and above of the DIstributed Marketing package.

Restrictions have been given an update with the option of using regular expressions and wildcards in the restricted words section. There’s also been an addition for the ability to restrict sending based on an active flag or a date range to make sure no content goes out that is no longer or not yet relevant. So check out Restricted Words and Restrict Sending in your Distributed Marketing package!

Farewell Discover Reports. You were helpful to have around, but the formal announcement of Discover being retired will be on February 1st 2021. But, it’s not going away without a replacement! Datorama reports will launch as part of this release and as the release rolls out, we’re expecting to see some more enablement details made available. We can’t add a whole lot of specific details here because they’re not currently available yet, but, having participated in some of the user experience research around it - this looks to be a big step forward. Whilst it may need some time to mature and may not cover all of your immediate analytical needs, it would be worthwhile considering what you can achieve with the new tool when it launches as opposed to creating new things that will sunset in 2022 as Discover is turned off for good.

MobileConnect is getting some new attributes in the Data Views. You’ll be able to add the JBDefinitionID and JBActivityID to your queries against the _SMSmessagetracking Data View. This should help you map your Journey Builder SMS activities more effectively. The actual Data View documentation hasn’t been updated yet with the full definition of these fields but it seems likely that JBActivityID will correspond to JourneyActivityObjectID the same way that TriggererSendDefinitionObjectID does to Email activity. So this will be the first time that there has been a simple way to match with an attribute between Mobile Connect and Journey Builder.

Also in the realms of tracking your SMS, you’ll be able to get the transient, bounced and delivery events for your SMS when sending them from the transactional API. When sending your SMS via the transactional API, just subscribe to the SMSTransient, SMSBounce and/or SMSDelivered events in the process.

Google Analytics gets a nice shot in the arm with the cost prohibitive requirement of GA360 going away and you’ll be able to start making use of the free elements of the analytical tool within Journey Builder. Don’t expect it to be a huge game changer, but it should hopefully simplify some of your campaign reporting if you’re a free Google Analytics user. Besides - it’s free… so why wouldn’t you?

There’s some general improvements coming through to Einstein in this release, including a learning portal which will help to understand the use cases for each Einstein capability. So if you’ve ever wondered what you’d use Copy Insights for, take a look and it should shine a light on what Salesforce suggests is a good use case for the tool. There’s also some improvements from an analytics perspective for Asset selection.

One thing to be aware of is Send Time Insight where Einstein can now use data from multiple customers to derive insight about the best time to send an email. Einstein will securely model opted-in customers (that’s us as users, not your customers/contacts) to improve prediction accuracy. The feature is going to have a gradual rollout but you’ll see a banner in the STO dashboard letting you know that the model determining when is best to send your email is using global data. If you do not wish to contribute to the global data, you won’t be able to benefit from this but to opt out, you’ll need to raise a support case.

Image tagging will now automatically tag things in non-english languages, but you’ll need to set that up. Any current tags in place won’t be updated until you update the image that the tag was for which may be a bit of a hassle if you’re using tagging extensively already but definitely a great one to have for anyone using multiple languages! Asset attributes will now also be able to have multiple values, eg: If you have an asset that works well for both football and basketball fans, you can set that as well as having a set of fixed valid values to use to prevent human error on data entry!

This is the big one for this release. After not providing too much in the way of new capabilities and primarily focusing on quality of life changes for Interactive Emails and similar functionality for the last 12 months, Salesforce have gone all in on some new messaging options in this release. Though, they won’t all hit your orgs on release day unfortunately.

However, the quality of life changes aren’t finished for Interactive Email forms. Currently if you have a hidden field, it may be visible to the form recipient, even though it doesn’t need to be. In this release, new or recently modified Interactive Email forms will experience the new behaviour of not passing the hidden field value through the form submission. A nice little addition to customer experience and keeping your interactive form elements neat.

Journey builder contact error details will get an improvement to make it easier to troubleshoot issues that have caused contacts to exit a journey prematurely due to an error. This won’t include every error type right now, but it will cover errors in email, updating contacts, Sales/Service Cloud activities as well as surfacing some errors from your custom activities. You’ll access it in the Health panel of a journey, search for a contact and if that contact got removed due to an error, you’ll get the details of the error so you can make the changes you need to prevent it happening in the future.

If (like me) you’ve been a big fan of the recently added Alert Manager, the default threshold for alerts being triggered will be set for 10 consecutive subscribers for your triggered send definitions to enter an errored state due to bad data or AMPscript failing to evaluate. One thing to note here is that notifications relating to cancelled triggered sends from Salesforce Support are being retired, so make sure you’re getting emails from Alert Manager to make sure your triggered sends don’t start erroring and not knowing about it.

Mailbox provider one-click unsubscribes will now be added to new or modified email headers in any commercial email send classification. One-click unsubscribe isn’t a bad thing and making your emails easy to unsubscribe from doesn’t have to be the end of the world, but having this in will make sure mailbox providers are treating your emails appropriately. But if you do see an increase in Unsubscribe activity after the new release, make sure you check to see if they are coming from your existing unsubscribe process or if they are coming from this. It’s one to keep an eye out for.

Stepping away from Emails, a nice little change is the ability to choose the duration of an SMS Conversation within Mobile Connect. These can be set up at a keyword level and you can now set your double opt-in and Next Keyword based conversations to listen for a response. If you’ve never used SMS conversations, take a look here to see how the conversation window could work better for you.

WhatsApp messaging comes to Journey Builder through a new partnership with Sinch who are an official WhatsApp business solution provider. You’ll need to link your account with Facebook Business Manager and subject to WhatsApp approving your account you could be using WhatsApp messaging within a few business days. You’ll need to create your message templates and create an audience, but the WhatsApp activity will appear towards the latter half of February once you’ve purchased the additional feature. So, whilst it may not be there for you and it may not be relevant for you - it’s something that up until now has required 3rd party integrations rather than native capabilities.

AMP for Email finally lands in SFMC in March. After being announced at AMPFest 2020, the AMP MIME-Type will become available in your business units. If you’ve not heard of AMP for Email (not to be confused with AMPscript in Email), it’s a framework to unlock greater content capabilities, real time status updates and even two way interaction directly from a customer inbox. Take a look at AMP.dev for some of the documentation to help you get started. But definitely don’t get confused with AMPscript and AMP scripts. If you build any AMP emails that you’d like to showcase, get in touch - it would be great to get some examples that showcase just how to push the capabilities of both SFMC and AMP for Email in tandem.

This is a pretty big release overall with some awesome new possibilities, but unlike the releases of the past it’s not a one and done. Things will change over a few weeks. The major new features with WhatsApp and AMP for Email are both expected to land after the final date of the release roll out. The core elements of the release are expected to finish landing on February 6th but WhatsApp and AMP won’t be available until 16th February and 1st March respectively. You’ve got the V2 connector being turned off at the end of March and the SSO certificate needing to be updated before 18th February. So, whilst there’s a lot going on it feels like there’s a lot of calendar management that needs to go into this to make sure you don’t miss any of these features being turned on or turned off for your business.

Keep an eye on the release notes for the items you have any specific interest in, the release edits are listed in the summary page and they do get updated after the original publish date. At time of writing, this summary covered the original publish date. If things have changed that are no longer reflective of the release, we’ll endeavour to keep updates on Twitter.

Read more
Jason Cort
Published 12/13/2020
SQL
12 Days of Scriptmas

Like many of you, the holiday season is a favorite for all of How2’s directors, admins and other community members! In that light, we are looking to share some of the ‘gifts of script’ we have accumulated over the years with you!

We will reveal a single script each day from Dec. 13th right up to Christmas Day (Dec. 25th) to provide you with the 12 DAYS OF SCRIPTMAS! We hope you enjoy all the ‘gifts’ we have been busy crafting these last few days to share with all you trailblazers and email-geeks as our gifts to you!

Happy Holidays and have a GREAT SCRIPTMAS!!


We are excited to share our first day with you all and for the beginning we are coming out strong with a debug process for SFMC SSJS:


<script runat='server'>
Platform.Load("core", "1.1.1");
/*
 @param { string } identify what you're writing
 @param { response } function/api response
 @param { debug } toggle if function should run or not


 debug = 1 will have output
 debug = 0 will hide output
*/
var debug = 1;

function deBug(string, response, debug) {
  if (response) {
    debug ? Write("<br><b>" + string + ":</b><br> " + Stringify(response) + "<br><br>") : null;
  } else {
    debug ? Write("<br><b>" + string + "</b><br> ") : null;
  }
} //End deBug

</script>

A special thanks to our own Tony Zupancic for the script.


The second day is here! Are you excited?! We are! For today’s script, we are sharing a quick SQL trick to help dedupe your results. Hope it helps you as much as it has helped us.


/* SQL dedupe using partition window and 
targeting first row match found */ 

SELECT x.SubscriberKey
, x.Email
FROM (SELECT a.SubscriberKey
  , a.Email
  , MAX(a.EventDate) AS lastOpenDate
  , row_number() Over(
  		Partition By 
  			a.SubscriberKey 

  			Order By 
  				a.Email
  		) row
  FROM DataExtension a
  WHERE ISNULL(a.EventDate,'') != ''
  Group By a.SubscriberKey
  , a.Email) x 
WHERE x.row = 1

A special thanks to our own Genna Matson for this script.


Day three! This is so exciting for us! Today, we are sharing a small AMPScript snippet that illustrates how to create dynamic variable creation/setting. The possibilities with this are near endless!


%%[
SET @deRows = LookupRows(...)

FOR @i = 1 TO RowCount(@deRows) DO
  SET @row = Row(@deRows,@i)
  SET @tempValue = Field(@Row,'Field')
  SET @FieldSet = TreatAsContent(CONCAT('%','%[SET @Field', @i, ' = @tempValue]%', '%'))
NEXT @i 
]%%
  

A special thanks to our own Greg (Gortonington) Gifford for this script.


We are a third of the way through! It is so exciting…yet also sad that there are only 8 more days left. Today is a trick to help you break up your SQL queries that could time out into multiple queries to get around the 30 minute limit.


/* Avoid time outs by breaking large volume DEs down
by targeting the _customObjectKey attribute on the DE.

This attribute indexes at 0. 

The below example breaks the DE in two. 
First half of the DE -- Query 1: */ 

SELECT a.EmailAddress
, a.SubscriberKey
, a.Status
FROM DataExtension a
WHERE _customObjectKey % 1 = 0


/* Second half of the DE -- Query 1: */ 

SELECT a.EmailAddress
, a.SubscriberKey
, a.Status
FROM DataExtension a
WHERE _customObjectKey % 1 = 1

A special thanks to our own Genna Matson for this script.


FIVE. GOLDEN. RIii…I mean hello and welcome to the fifth day of Scriptmas!! We are excited to provide this wondersful SSJS function that can be used to make sure to elegantly end your SSJS scripts rather than have them timeout and error. Hope it helps to save you some hair and keep that blood pressure low!


<script runat=server>
Platform.Load("Core","1.1.1");
​
var dev = 1;
​
var now = new Date();
var start = now.getTime();
var timeOut = 1500000; //25 minutes
//60000 milliseconds in a minute
​
​
if (dev) {
  timeOut = 10000;
}
​
​
​
do {
​

​//Your Code Here
​
} while((new Date().getTime() - start) < timeOut)
​
</script>

A special thanks to our own Greg (Gortonington) Gifford for this script.


OHHHHH…WE’RE HALFWAY THEERRREE!!! OHHHH OHH AMPSCRIPT CUZ WE CARE! Ahem. Sorry couldn’t resist some Bon Jovi. Today is our halfway point to the end! It has been a WONDERFUL journey and we want to thank everyone for coming along with us so far. Our elves have been cooking up quite a few wonderful things that we are excited to share over the next few days, so make sure you keep checking back every day!

<h1>
  Sendable Data Extension - Summary
</h1>
<table>
<thead>
  <tr>
    <th>DE Name</th>
    <th>Description</th>
    <th>Rowcount</th>
  </tr>
</thead>
%%[
SET @GetSendableDEs = CreateObject('RetrieveRequest')
SetObjectProperty(@GetSendableDEs, "ObjectType", "DataExtension")
SET @SimpleFilter = CreateObject("SimpleFilterPart")
SetObjectProperty(@SimpleFilter, "Property", "IsSendable")
SetObjectProperty(@SimpleFilter, "SimpleOperator", "Equals")
AddObjectArrayItem(@SimpleFilter, "Value", "True")
AddObjectArrayItem(@GetSendableDEs,"Properties","Name")
AddObjectArrayItem(@GetSendableDEs,"Properties","Description")
SetObjectProperty(@GetSendableDEs, "Filter", @SimpleFilter)
SET @DERows = InvokeRetrieve(@GetSendableDEs,@status,@reqID) 
SET @DECount = ROWCOUNT(@DERows)

IF @DECount > 0 THEN
FOR @Loop = 1 to @DECount DO

SET @DERow = ROW(@DERows,@Loop)
SET @DEName = FIELD(@DERow,'Name')
SET @DEKey = FIELD(@DERow,'CustomerKey')
SET @DEDesc = FIELD(@DERow,'Description')
SET @DERowcount = DataExtensionRowCount(@DEName)
]%%
  <tr>
    <td>%%=V(@DEName)=%%</td>
    <td>%%=V(@DEDesc)=%%</td>
    <td>%%=V(@DERowcount)=%%</td>
  </tr>
%%[ NEXT @Loop ELSE ENDIF ]%%
</table>

A special thanks to our own Jason Cort for this script.


We know how much console.log is important to JS developers and this script helps to fill that big hole that is left behind when running SSJS. We hope this helps to bring you a happy holiday season and a very merry Scriptmas!

<script runat=server>
var json = ["one","two","three"]
consoleLog(json);

function consoleLog(value) {
   if (typeof value === 'object' && value !== null) {
      value = Platform.Function.Stringify(value);
   }

   Platform.Response.Write('<script>console.log(' + value + ');</script>');
}
</script>

A special thanks to our own Shibu Abraham for this script.


Who else is excited for the holidays? We have had a ton of fun so far and are very excited for the next few days! We hope these scripts have been useful or at least helped in some way or another. This script helps tons with those that need to use Time Zones in SQL.

/* Get around some timezone challenges with the AT TIME ZONE 'function' within your SQL Queries */
select
eventdate AT TIME ZONE 'Central America Standard Time' AT TIME ZONE 'UTC' AS 'eventdate'
,SubscriberKey
,jobID
from
_Sent**

A special thanks to our own Jason Cort for this script.


Hello again and happy SCRIPTMas! Our elves cooked up something special today sharing examples on how to utilize the LookupRows and LookupOrderedRows AMPScript functions!

%%[ /* AMPscript Lookups */

/* Lookup Rows */
set @lookupVariable = 'rando'
set @lookupRows = LookupRows('DataExtension','Field',@lookupVariable)
set @countRows = RowCount(@lookupRows)
if @countRows > 0 then
  set @row = Row(@lookupRows,1)
  set @field = Field(@row,'Field')
endif


/* Lookup Ordered Rows -- returns 5 ordered rows */
set @lookupVariable = 'rando'
set @lookupOrderedRows = LookupOrderedRows('DataExtension',5,'Field asc','variable',@lookupVariable)
set @countOrderedRows = RowCount(@lookupOrderedRows)
if @countOrderedRows > 0 then
  set @row = Row(@lookupOrderedRows,1)
  set @field = Field(@row,'Field')
endif

]%%

A special thanks to our own Genna Matson for this script.


Holy Cow! Our own Mr. Tony Zups provides us with a powerhouse of a script - a SSJS function to run your API calls via a config file. These next few days are ramping up to be great ones!

<script runat='server'>
Platform.Load("core", "1.1.1");
var accessToken = getAccessToken();//get accessToken

var getJourneyConfig = {
	endpoint: "interaction/v1/interactions/"
	method: "GET"
}


function scriptUtil(config, accessToken) {

 var method = config.method;
 var url = "restBase" + config.endpoint;

 try {
  var req = new Script.Util.HttpRequest(url);
  req.emptyContentHandling = 0;
  req.retries = 2;
  req.continueOnError = true;
  req.contentType = "application/json"
  req.method = method;
  req.setHeader("Authorization", "Bearer " + accessToken);

  if (config.payload) {
   req.postData = Stringify(config.payload);
  }

  var resp = req.send();

  var resultStr = String(resp.content);
  var resultJSON = Platform.Function.ParseJSON(String(resp.content));

  return resultJSON;

 } catch (e) {

  Write("API (e)" + Stringify(e));
  return
 };
}
</script>

A special thanks to our own Tony Zupancic for the script.


One more day! Today we are sharing a wonderful script showing SQL subqueries and how to use them. They are instrumental in most complex SQL queries. With scripts like these last few…what could be the final one? Come back tomorrow and see!

/* Subqueries are a great way to extend your SQL capabilities and they come in many formats.

Eg: Using a Subquery to create a reference dataset from a single location to fFind every subscriber who has opened any email.*/

Select
Subscriberkey
from
_Subscribers
Where
SubscriberKey in (select Subscriberkey from _Open)

/* You can create Subqueries within your Subqueries to get a single output for a specific set of requirements.
Eg: Using a nested Subquery to finding all Subscribers who have Opened an email of a specific Email Name format  */

Select
Subscriberkey
from
_Subscribers
where
Subscriberkey in (select Subscriberkey from _Open
                  where
                  JobID in (Select JobId from _Job
                            where emailname like '%PromoCampaign%')
                  )
                  
/* You can use Subqueries if you need to calculate or derive something to use within your main query, it can save you needing to maintain an additional query and you can join your subquery in to your original query

Level 3 - Finding how many times a person has opened and the first time someone opened an email of the PromoCampaign type with one query rather than 3 */

select
sub.SubscriberKey
,tot.totalopens
,fir.firstopen
from
_subscribers sub
join (select subscriberkey, count(*) as 'TotalOpens' from _Open
      where
      JobID in (select JobId from _Job
                where emailname like '%PromoCampaign%')
      group by subscriberkey ) tot on sub.subscriberkey = tot.subscriberkey
join (select subscriberkey, min(eventdate) as 'FirstOpen' from _open
      where
      Jobid in (select JobId from _Job
                where emailname like '%PromoCampaign%')
     group by subscriberkey ) fir on sub.subscriberkey = fir.subscriberkey

A special thanks to our own Jason Cort for this script.

We did it! Today’s script is a SSJS script to output a list of all the field names inside of a data extension. This can be super helpful in multiple ways, from using in a DE inventory or using it to output fields for a SQL query.

<script runat=server>
Platform.Load("Core","1");  
var dataExtensionFields = DataExtension.Init('myDE').Fields.Retrieve()
if (dataExtensionFields.length > 0) {
    for (var i = 0; i < dataExtensionFields.length; i++) {
        Write('<br>'+dataExtensionFields[i].Name);
    }
}
</script>

A special thanks to our own Jacob Edwards for this script.

Thank you everyone for reading! If you would like to contribute or be a part of HowToSFMC, please reach out to us using <a href=“https://www.howtosfmc.com/contact/”>our contact form</a> or on our Slack channel.

Read more
HowToSFMC
Published 11/09/2020
SSJS
Survey Results - 2020

So, we asked for community feedback on resources that are used to help you to succeed, blaze your trail and become an overall more capable SFMC practitioner and the community has definitely delivered. The resources ranged in categories from websites run by Trailblazers, books, content from Third Parties and a staggering 18 unique individual Trailblazers were called out for the contributions they have made to your experience as your upskill, reskill or share skills within the Marketing Cloud.

One of the most interesting results from these were the number of unique resources that were suggested, where just one person has submitted a resource but some of them are gems that are new to us that need to be flagged. So, we’ll be running through the top 10, in no particular order, highlighting some of their highlights.

Whether you’ve got a question about using SQL, Content Builder, SSJS or pretty much anything you can think of with SFMC you’ve got some of the greatest minds in the community answering questions in their free time. You may have something unique to ask or you may find that there’s a number of people with the same query and you’ve got multiple solutions to your challenge.

With the tag based filtering, it’s quick and easy to get into the details around the questions you’ve got as well as a great way to start on your own quest to become a Salesforce Marketing Champion by getting stuck in. We cannot recommend SFSE enough and chances are if you type an SFMC question in your search engine of choice, SFSE will be one of the top results and start steering you down the right track to a resolution.

Created by Zuzanna Jarczynska (2020 Salesforce Marketing Champion and MVP) and featuring a wealth of resources, examples and great tools that you can get into the nitty gritty of how things work and how to make things work for you. The level of detail in the explanations of each of the examples is excellent and should definitely be a part of your regular reading. If you are ever in need of some inspiration for interesting projects or inspiration to push yourself further as a developer, bookmark sfmarketing.cloud today.

Highlight resources:

Data Views in Salesforce Marketing Cloud

Create a Cloudpages form with an image/file upload

Adam Spriggs (2020 Marketing Champion and MVP) is a name many will be familiar with, a prolific contributor to Salesforce Stack Exchange as well as many of the community webinar events. His blog covers not just SFMC but also just “interesting things” including memes, coffee and some top tier YouTube content. But, it is also one of the first places many people find when starting their journey in SFMC. The ever present requirement for a custom preference centre or for starting an automation via SSJS are 2 common tasks of SFMC developers and Adam has some great info on both of these topics.

Highlight Resources:

SFMC Known Issues Feed

AMPscript Lookup Examples

We’ve mentioned Adam Spriggs & Eliot Harper both in this article for their independent sites and SFMC resources, but when you bring 2 great minds together you can sometimes get something great. In this instance, we’ve got something arguably more useful than that with the AMPscript Guide. Whilst the guide itself isn’t free, the website contains ample code samples and all of the syntax details to enable you to up your AMPscript game.

Highlight Resources:

Marketing Cloud API Functions

AMPscript Language Overview

Greg “Gortonington” Gifford (2020 Marketing Champion and MVP) has set out to create a hub of interesting resources covering all things SFMC. Drawing on experiences from being client side as a jack of all trades through to today, Gortonington.com is definitely in the class of Master of SSJS with the resources that have been put together. Whether you know the difference between SOAP Objects and SQL Queries or not, there’s lots of practical and useful tips and guides to get you started as you level up your SFMC skills.

Highlight Resources:

SSJS Intro

SQL and the Three Valued Logic involving NULL

Markus Slabina (2020 Marketing Champion) is a Salesforce Consultant covering not just Marketing Cloud but also Cross Cloud projects and implementations. Markus.codes covers a broad range of topics with a strong focus on Journey Builder, Cross Cloud and making the most of what you have available. But it would be remiss to mention the blog without mentioning the open source Google Chrome Plugin, SFMC DevTools. Whilst this isn’t an official Salesforce product, there are some nice features that may help streamline your workflow.

Highlight Resources:

SFMC DevTools

Securing CloudPages with SSJS

DESelect is an AppExchange partner for Salesforce Marketing Cloud with the aim of making segmentation in SFMC a simple and easy task with less technical overheads. Whilst they have a lot to say about their product, they have well thought out content around data modelling within SFMC that is definitely worth checking out. The newest video series Heroes of Marketing Cloud features in depth video interviews with Trailblazers and is not one to miss.

Highlight Resources:

What data to store and how to integrate it

Heroes of Marketing Cloud with Kaelan Moss

AMPscript.xyz is the home of Ivan Razine, a web designer and developer and features how-to guides, tips and tricks for a range of the core SFMC technologies. With over 40 different articles at your disposal covering CloudPages, Data Extensions, AMPscript and more it’s a great source of solutions to some SFMC challenges. Even those SFMC challenges you didn’t realise you may have had to face!

Highlight Resources:

Create a Search and Replace app in SFMC

The 5 ways of adding and updating Data Extensions

Eliot Harper (2020 Marketing Champion and MVP) shares his tips across all aspects of SFMC in his short form video series mc.chat. Each video is accompanied by a well structured transcript to make this one of the most widely accessible video series on SFMC currently available. The topics are covered in the right amount of depth to either answer your question or to leave you with enough to go on to continue finding the solution to your specific challenge. Definitely taking a flick through the series.

Highlight Resources:

Email Address Update Behaviour

CloudPages Development

For those of you who haven’t heard of Guilda, she is a tour de Salesforce in bringing Trailblazer initiatives to life and showcasing the absolute best that the community has to offer. She’s spearheading the community driven events like Lookup(Answers) and Technical Marketers Meetings and you’ll find her regularly appearing at Trailblazer Community Events. If there is a single person at Salesforce to keep an eye out for to know what’s going on and when, it’s Guilda.

Highlight Resources:

Follow Guilda on Twitter

Written by Larry Rockoff, The Language of SQL takes a different approach to the syntax encyclopedia you sometimes find with SQL documentation. Taking readers through the simplest of SELECT statements through to the logic behind differing types of JOIN, Rockoff brings the fundamental concepts of SQL to life. Whilst not an SFMC specific resource, many SFMC developers will need to be well versed in this language - so if you’re looking to learn or improve you’d do well to grab a copy of this book.

Where to buy:

Amazon UK (No Affiliate Link)
Amazon US (No Affiliate Link)

Covering the complex world of email deliverability, WordToTheWise is a great resource for email marketers to understand how to make the most of their efforts from their email campaigns. It includes discussion pieces on SpamTraps,Delivery Improvement and Best Practices making it an incredibly valuable resource for SFMC practitioners.

Highlight Resources:

Gmail suddenly puts mail in the bulk folder

The key to improving deliverability

One of the most fully fleshed out API resources for those who use Postman. It includes REST and SOAP API activities ready to use in a well laid out collection. The collection is community maintained, is actively updated (as recently as 28th October 2020 to include new Transactional API endpoints) and covers a huge range of standard requirements from AUTH through to creating assets in Content Builder. Pair this up with Postman today.

The single most inspirational conclusion that can be drawn from this is that the resources the community are looking out for are diverse in terms of content, medium and function. We have resources that focus specifically on the development side of the spectrum as well as the Email Campaign Management. There are videos, books and code repositories & we’ll add the full set of resources shortly. If there’s any resources that you think have been missed, whilst the survey may be closed we would still be glad to hear of them.

Thank you again for all of your contributions to HowToSFMC.com and we would definitely recommend you check out any of the resources both above and below.

Read more
HowToSFMC
Published 11/01/2020
Web Studio
How to Create a Simple Authorization Endpoint Inside SFMC

Please note: There is no implied guarantee of security or any recommendations on security level or protection for your tools or websites in this article. This article is intended as educational only and all content contained is not intended as a ‘final product’ of any kind.

Have you ever run into a need for some added security for your web apps, dashboards, custom endpoints, etc. but don’t think you have the tools to build anything? Inside of SFMC there are a couple existing options such as the authenticated user via your SFMC account, using REST packages/components, etc. But each of these can get unweildy, expensive or potentially provide access/permissions outside the scope you want to provide. What if there were a different solution to provide a time-bomb guid for idetnification?

My solution is a custom built endpoint to pass back a token based on a username and password. This token would have an expiration period set to it in order to increase security due to requiring user/pass re-entry for extended use. Please do note that this is only a sample and is very simple in design to provide a baseline, and is not recommended to use ‘as is’. The idea is to provide a sample for you and your team to build on and make your own robust security endpoint.

First things first, lets lay the foundation for the endpoint to work. I would recommend the below for your endpoint:

  1. Create a BU that has limited access to only allow your admins or security team into
  2. A user/pass data extension to house your users
  3. Authorization Hash/GUID data extension to hold your tokens
  4. A code resource page to host your endpoint

I would highly recommend hosting this as well as all your other endpoints into a single separate Business Unit in your account. This will reduce the access that others will have (increasing security) and it helps to keep it in a centralized location. The second benefit is that this BU can host all your other custom endpoints as well. Since the majority will likely be utilizing REST API, you can host them all in the same enivornment since it would be the SFMC OAuth token that would place the context around the call - not the hosting environment.

It opens the gate for you to store confidential information like REST API ID/Secret codes, User/Pass, and more in a more secure location. That being said, you may want to have added security with Hashing, etc. to decrease the likelihood of someone being able to grab all the info here.

This data extension will be the repository of your users to allow for reference in creating the token. For the sake of this example, I am using it without any encryption or encoding, etc. Please feel free to adjust this to whatever security standards you desire.

Here is my example setup for this DE:

DE Name: Auth_UserPass

Name           |  Data Type  |  Length  |  Primary Key  |  Nullable  |  Default Value  |
UserName       |  Text       |   254    |  Y            |  N         |                 |
Password       |  Text       |   254    |  N            |  N         |                 |
LastLoginDate  |  Date       |          |  N            |  Y         |  Current Date   |
Status         |  Boolean    |          |  N            |  Y         |  False          |

UserName: This is the username of the user.

Password: This is the associated password of the user

LastLoginDate: This is used to show the last time a user logged in. I use this to check user activity and validate token matching, etc.

Status: This field allows you to toggle if a user is ‘Authorized’ or ‘Not Authorized’ without having to re-enter or delete the user from the Data Extension.

There are plenty of more fields and data points that you can store in here. I just added in my base recommendations above.

This data extension will hold the currently active hash tokens to be referenced for authentication. Again this is just a basic example and you can add/adjust to suit your needs.

Here is my example setup for this DE:

DE Name: Auth_HashToken

Name          |  Data Type  |  Length  |  Primary Key  |  Nullable  |  Default Value  |
HashAuth      |  Text       |   500    |  Y            |  N         |                 |
UserName      |  Text       |   254    |  N            |  N         |                 |
CreationDate  |  Date       |          |  N            |  Y         |  Current Date   |

HashAuth: The Token that was created inside of the auth endpoint

UserName The associated UserName to that token

CreationDate The date and time that the token was created.

As the data retention policy in SFMC only goes down to 1 day minimum, you cannot set these tokens to the hours/minutes that you may want. I am including CreationDate to allow for the easy set up of ‘time-bomb’ functionality. After whatever length of time you want, you can set it so that the token then no longer becomes viable and the user needs to create a new one. This also allows you to have flexibility on each endpoint to have a separate countdown rather than having it create an ‘ExpirationDate’ field that’hardcodes’ the expiration. That being said, I still recommend having a data retention on this DE to keep it clean, efficient and fast.

This is where you will build the actual endpoint itself. This page will use lookups and SSJS functions to create a JSON return with the token included. I would recommend making this a JSON code resource page inside of Cloud Pages.

There are a couple key things to note before I share the code:

GetPostData(): This handy dandy function is undocumented but powerful for SFMC SSJS. By writing Platform.Request.GetPostData() you are able to retrieve the data that was sent in via a POST to this page. As a note, this will take any JSON Arrays or Objects and turn them into Strings.

API Endpoint: This page is going to be an API Endpoint, so it will not be rendering or processing any HTML. So any attempts to make HTML in this page will result in the HTML being written as if it were text, not code. Also note that this page will require a POSTed Object to correctly function, so any views inside of a browser will likely result in either a 500 error or a returned error message from the page.

DEV Environment: I usually like to allow myself a ‘Dev’ (Development) or testing environment inside most of the endpoints, tools or programs I build to allow for easy programming and verification. To this extent I have allowed a flag at the top to turn on/off the development environment. Inside this final version, there is no need for it, but I like to include it for future usage. It is especially beneficial for ‘faking’ different requirements or conditions to allow for a streamline testing and to limit the variables in your development.

Debug Environment: This is probably as important if not more important than a 'Dev’environment. Having proper Debugging set up is instrumental to a good troubleshooting or QA process. Similar to my dev environment, I start this with a flag at the top to turn on/off. From there I usually write an inline conditional that if debug is true, then to Write() some data points for easy reference. E.g. debug ? Write('Debug is on!') : '';.

Below is the SSJS code to check if it is a valid user with the correct password, generate a token and then store it internally and pass it back:

<script runat=server>
Platform.Load("Core","1.1.1");

//---------------------------------------------------------//
//---------------------------------------------------------//

// CUSTOM API ENDPOINT TO AUTHORIZE CUSTOM ENDPOINT USAGE

// REQUIRES:
//  - Username
//  - Password
//  - Auth User/Pass DE
//  - Auth Hash/GUID DE

//

//---------------------------------------------------------//
//---------------------------------------------------------//

//set Dev environment for debugging and troubleshooting (1: ON | 0: OFF)
var dev = 0;
var debug = 0;


//Gathers the data POSTed to the endpoint and turns it into a JSON
var postStr  = Platform.Request.GetPostData();
var postJSON = Platform.Function.ParseJSON(postStr);

debug ? Write('postStr: ' + postStr + '\n') : '';
debug ? Write('postJSON: ' + Stringify(postJSON)+ '\n') : '';

if(postJSON) {
	//Gathers the values of the properties passed in payload
	var user = postJSON.userName,
	    pass = postJSON.password

	if(!user) { 
	  Write('{"ERROR": "Missing userName"}')
	  var fail = 1; 
	} else if(!pass) {
	  Write('{"ERROR": "Missing password"}') 
	  var fail = 1;
	} 

	// Error checking to output Error Object if missing
	// a required property from the payload


	if(!fail) {
		//grab authentication info from REST token DE    
		//this is a simple sample, may require different process/security precautions for you to implement
		var luDE = DataExtension.Init('Auth_UserPass');
		var lu = luDE.Rows.Lookup(['UserName','Password','Status'], [user,pass,1])

	    debug ? Write('lu: ' + Stringify(lu) + '\n') : '';
	    debug ? Write('lu.length: ' + lu.length + '\n') : '';
    
        if(lu.length > 0) {
        	var token = Platform.Function.TreatAsContent('%' + '%=GUID()=%' + '%');
        	debug ? Write('token: ' + token + '\n') : '';

            var hashDE = 'Auth_HashToken';
		    var insertToken = Platform.Function.InsertData(hashDE,["HashAuth","UserName"],[token,user]);
        } else {
            Write('{"ERROR": "Authorization is not available. Please add into the reference data"}')
        }
    }
    
} else {
  Write('{"ERROR": "Missing payload"}')
}
</script>

An example call to this endpoint would be something like:

POST /{{myAuthurl}}
Host: {{myHostDomain}}
Content-Type: application/json

Body:
{
    userName: 'myUserName',
    password: 'myPassword'
}

and an example return on success would be:

{
    "Token": c1d72dac-7986-4944-b8bd-a5395ce6be8c
}

and an example failure would be:

{
    "ERROR": "Authorization is not available. Please add into the reference data"
}

I hope this has helped to inspire you to create some awesome authentication endpoints of your own! I do want to make another final note here though that the above is in no way intended to be a final product nor is there any guaranteed or inherit security implied by utilization. It is provided as an example only and is not intended to be utilized as anything other then such. Please leave some comments or reach out with any thoughts, ideas, or questions!

Read more
Gortonington
Published 10/30/2020
General
Your first SOAP API Call - With AMPscript

When it comes to making your first API call within Salesforce Marketing Cloud, there’s a range of options and places to start. You could start with Postman and the library of API calls there, but then you have to worry about configuring an app etc. within SFMC. That’s a lot of effort when there’s actually a lot you can do with the SOAP API to get yourself started without going down the routes of authentication and similar.

In summary a SOAP API Invoke with AMPScript is formed of 3 key parts

  • Pick an object from the dropdown on the left here (In this case we’ll use DataExtension)
  • Check what SOAP API Operation you can do against it here (In this case we’ll use Create, so InvokeCreate)
  • Create your Invoke action in AMPScript. (This is the biggest of the 3 parts, sorry)

An InvokeCreate consists of a few key parts:

  • Creating the Object (DataExtension)
  • Defining the object properties (What attributes of the Object you want and how you want to define them)
  • Execution of the InvokeCreate

So in this example, we’re aiming to create a new Data Extension, which we will define with

Set @de = CreateObject(“DataExtension”)

From now on, any properties we want to set for this DataExtenstion object, we need to use the @de variable.

Examples would be for the Name, Description and IsSendable. The structure of the SetObjectProperty function has 3 arguments

SetObjectProperty(1,2,3)

  1. The API Object you are setting the property for (In this case, it’s @de)
  2. The API Property you’re looking to update (Eg: Name)
  3. The Value you want to set of the API Property in argument 2 (eg: HowToSFMCExample)

SetObjectProperty(@de,“Name”,“HowToSFMCExample”)

SetObjectProperty(@de,“Description”,“Data Extension Created via API - HowToSFMC Sample”)

SetObjectProperty(@de,“IsSendable”,“False”)

But - A data extension has fields, and DataExtensionField is a different object. But you can put an object within an object. So you need to configure a DataExtensionField object too. In this case, we’ll define this with

SET @deFields = CreateObject(“DataExtensionField”)

Much like the base DataExtension object, you need to set the properties for this one too. It works just the same here where you set the API Object you’re setting a property for, what the property is and what the value of that property is. For this example we’re going to give the field a Name, a FieldType, whether it IsRequired, IsPrimaryKey, IsNillable (It’s not for any of those) and a MaxLength of 100

SetObjectProperty(@deFields,“Name”,“MyFirstField”) SetObjectProperty(@deFields,“FieldType”,“text”) SetObjectProperty(@deFields,“IsRequired”,“False”) SetObjectProperty(@deFields,“IsPrimaryKey”,“False”) SetObjectProperty(@deFields,“IsNillable”,“False”) SetObjectProperty(@deFields,“MaxLength”,“100”)

Then you need to add this object to the parent object as an Array Item.

AddObjectArrayItem(@de,“Fields”,@deFields)

The AddObjectArrayItem has 3 arguments, these are:

AddObjectArrayItem(1,2,3)

  1. The object you are adding the array item to. In this case it is @de
  2. The element of the Object you are adding that array to. In this case we’re adding it to “Fields”
  3. The value of the array, which we defined as @deFields

Up next is to actually execute the InvokeCreate, you can do that using a SET function to capture the response from your call.

Set @CreateMyDE = InvokeCreate(@de, @Status, @Error)

The InvokeCreate function has 3 required arguments and an additional optional argument that isn’t necessary here.

InvokeCreate(1, 2,3)

  1. The API Object to be created. In this case, we defined the API Object as @de
  2. The AMPscript variable that will store the resulting status message. In the above example, this is set to store to @Status
  3. The AMPscript variable that will store the resulting error message. In the above example, this is set to store to @Error

Seeing as the InvokeCreate function does provide us with error messaging, we should display those somewhere. So in a lot of cases when setting up an API call these can be displayed on a Cloud Page or written to a Data Extension. For this example we’ll just display them on the a Cloud Page using %%=V(@Status)=%% and %%=V(@Error)=%% along with %%=V(@CreateMyDE)=%% that was used for the InvokeCreate.

Now if you take all of those snippets of broken down AMPscript and put them together, you should get this:

%%[ 
Set @de = CreateObject("DataExtension")
SetObjectProperty(@de,"Name","HowToSFMCExample")
SetObjectProperty(@de,"Description","Data Extension Created via API - HowToSFMC Sample")
SetObjectProperty(@de,"IsSendable","False")
Set @deFields = CreateObject("DataExtensionField")
SetObjectProperty(@deFields,"Name","MyFirstField")
SetObjectProperty(@deFields,"FieldType","text")
SetObjectProperty(@deFields,"IsRequired","False")
SetObjectProperty(@deFields,"IsPrimaryKey","False")
SetObjectProperty(@deFields,"IsNillable","False")
SetObjectProperty(@deFields,"MaxLength","100")
AddObjectArrayItem(@de,"Fields",@deFields)
Set @CreateMyDE = InvokeCreate(@de, @Status, @Error)
]%%
%%=V(@CreateMyDE)=%%<br> %%=V(@Status)=%% <br> %%=V(@Error)=%%

Now pop this snippet into a Cloud Page and click Schedule/Publish. You should get a nice preview pane with this in it:

Then head over to your Data Extensions root folder in the Business Unit that you’ve executed this in and you should see this: !How To SFMC Example Data Extension in your org

Finally if you click into it, you should see your one field configured the way you defined it to be, something like this:

!My First Field - Text - 100 length and Nullable

This should get you started with some SOAP API calls via AMPscript. If you’ve got any questions, keep an eye out for the Salesforce Marketing Cloud Technical Marketers Monthly calls and for the SFMC Lookup(Answers) sessions. These are the places where SFMC experts, MVPs, Marketing Champions and Salesforce Engineers themselves present cool things and answer questions from the community - live!

Read more
Jason Cort
Published 09/15/2020
How to Hack the Salesforce Marketing Cloud (SFMC): Where to go for help

Does this sound familiar? “I know you’re just learning this new tool "Salesforce Marketing Cloud", but can you launch an email tomorrow…stat?” The pressure is always on to show value quickly, but what if you’re just learning the platform? You don’t even know the difference between WYSIWYG or content builder…what’s a data extension (DE)? AMPscript – is that the name of a video game? Where do you go to become the expert that everyone thinks you are? Who can you vent to about all the nuances? Code overload – where are my like-minded email guru’s? Is there anyone out there going through the same pain points? I’m constantly being asked where to go for help, so I decided to pull together a couple of places for people to go to for help with Salesforce Marketing Cloud (SFMC).

I’m approaching my 13th year of using Salesforce Marketing Cloud “formerly ExactTarget email solutions”…and oh how times have changed. I remember how excited I was at first to learn about this new platform and its possibilities but worried about all the things to learn. 12 years ago, cloud communities, slack channels, LinkedIn, YouTube either didn’t exist or was just getting off the ground. Needless to say, support was your BFF or you faked it until you sent it. Today, there are great resources available, but you need to be a detective to stay on top of them. So, I’m gonna help you hack the cloud! I’m passionate about this, available and interested in your inputs/tips, and appreciate your readership! Please share 😃

  1. Join the Salesforce Marketing Cloud community: This group is dedicated to your success with the Salesforce Marketing Cloud ecosystem (Email Marketing, Social Media, Mobile Marketing, Web Marketing). Join the conversation here to ask questions, get answers, stay updated and share experiences. Surround yourself with like-minded savvy marketers who share the same passion around the platform. This is the best place to go when you have a question. Don’t be shy, and ask away! You won’t regret setting up your profile and diving right into the conversations.
  2. Bookmark the following pages:
    • Salesforce Marketing Cloud getting started: The Marketing Cloud training programs are designed to guide you through the key steps that are proven essential for a successful implementation. This is a great place to start when you are setting up your instance so that you can get the full advantages of this out-of-the-box solution.
    • Salesforce Marketing Cloud help page: This is the central location for all things Salesforce Marketing Cloud. You’ll find information on IP warm up, Account Configuration, Setting up content builder to Journey Builder best practices.
    • Salesforce Developer’s page: Here you’ll find information on the Marketing Cloud packages such as REST and SOAP APIs, SDKs, programmatic languages and other fun stuff.
    • Trust.marketingcloud.com: the Salesforce Marketing Cloud community’s home for real-time information on system performance and security.
  3. Become a member to one of the following groups. They exist both online and offline:
    • Salesforce Marketing Cloud User Group: Every month in towns and cities across most continents of the world, people come together to discuss all things Salesforce Marketing Cloud. The goal is to establish an engaged community of marketers. A culture of knowledge sharing and innovation, discussion of successes and missteps with the communal interest in growing and improving from our collective experiences with this platform. These aren’t Salesforce-organized events; these are events run by Trailblazers like yourself. Make this your one-stop resource for local Salesforce Marketing Cloud networking events, presentations and demonstrations.
    • Salesforce Marketing Cloud Meetup: These groups collaborate, network, exchange ideas and best practices around the Salesforce Marketing Cloud. The mission is to build an enthusiastic community committed to learning about the Marketing Cloud platform and achieving successful adoption of the platform.
    • Salesforce Marketing Cloud Developer Group: Useful resources for working and developing with Salesforce Marketing Cloud.
    • Marketing Cloud Developer Center: If you’re a developer, this is the place for you!
    • Salesforce Stack Exchange: Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between.
  4. [Register for Online Training](https://help.salesforce.com/htsctrainingcatalog
  5. Subscribe to the Salesforce Marketing Cloud YouTube Channel: The YouTube channel is a great place to go if you missed a conference, want to learn about a new product or need an overview of a product feature.
  6. Subscribe to the Marketing Cloudcast: Every episode offers key marketing campaign tactics, popular trends, interviews with marketing leaders, and relevant insights — such as social media stats and data management strategies in marketing today. Hosts Megan Collins and Tina Rozul will dive into topics such as performance on individual marketing channels, marketing career advice, the future of marketing, and beyond.
  7. Join a Slack channel: Yes, I said it. There are several email slack channels with a channel dedicated to Salesforce Marketing Cloud. Make new friends and share html codes, how-to’s, ask questions…Vent a little or vent a lot.
  8. Purchase the following books:
    • Salesforce Marketing Cloud For Dummies book by Chester Bullock and‎ Mark Pollard: Salesforce Marketing Cloud For Dummies guides you through the use of Salesforce’s exciting suite of cloud-based digital marketing solutions, which have the power to help you plan, personalize, and optimize your customers’ journey.
    • The AMPscript Guide by Eliot Harper and Adam Spriggs: AMPscript is a scripting language for Salesforce Marketing Cloud. This book extends the existing Salesforce documentation to provide an authoritative reference manual on AMPscript. This is the ultimate guide for anyone learning AMPscript or anyone who wants to freshen up their AMPscript knowledge.
    • The Data Handbook for Salesforce Marketing Cloud: This handbook explains the fundamental concepts for working with data in Salesforce Marketing Cloud, key tips for building a well-designed data model, how to utilize data across all of your marketing activities and much much more…
  9. Follow on Salesforce Marketing Cloud on Slideshare: This site doesn’t have the latest and greatest materials but it still has legacy platform information to help you understand the history of the Salesforce Marketing Cloud platform.

Please feel free to share where you go for help with the Salesforce Marketing Cloud!

Originally posted on LinkedIn by Guilda Hilaire

Read more
Guilda Hilaire
Published 08/08/2020
Email Studio
When you need to use WYSIWYG

Building dynamic content is always challenging for me to engage different customers based on their persona. When I looked at the challenge that was presented I was able to correlate with me and my team how we are challenged in the same way. l looked at the approach which will be reliable and easy to handle for me and my marketing team without any coding knowledge and the approach was towards WYSIWYG. WYSIWYG is an acronym for “what you see is what you get”. There are multiple ways to use WYSIWYG for building email content.

I talked about dynamic content in some of the line but what it is? What value it has? How I have used it? I will explain about it and will show how it was playing important role in my solution.

Dynamic content is content that displays in a content area according to the rules that you define based on the subscriber’s attributes or the targeted data extension column values.

A car rental firm wants to reengage with their existing customers .In this case reengaging with existing customer will be a challenge for marketing team because customer will be having different behaviour. Marketing team based on user behaviour (Like rental car duration or rental car model) can build different persona and assign particular content to user. Marketing team will create rule and assign particular content (Image/Email) to it and send for promotion. Dynamic content helps marketing team to engage/reengage with customers in personalize manner.

In this article I have focused on the approach which I have used to achieve dynamic content using WYSIWYG without HTML, AMPScript ,SQL query and CSS code. In this solution I have used data extension , dynamic content block and interactive email form .This approach works best for marketing teams instead of developers as I haven’t use any code for building dynamic content. I have elaborated the steps below which I followed to achieve this. Before creating email content I have done some of configuration which is described as well.

  • Sendable data extension is a table which stores subscribers/contact information which will be used for communication like email send.
  • For making sendable data extension Is Sendable checkbox should be checked.
  • Add atleast one email address for email send.
  • In sendable data extension send relationship field becomes mandatory where need to assign relation between data extension and subscriber.

Creation method: From the dropdown we can select any one option among three:

  1. Create from existing: Used existing data extension where only field will be referred not values
  2. Create from template: Use any existing template
  3. Create from new: Create new

I have selected Create from new option

  • Name: Enter any data extension name based on your convenient. I have given Name as Rental Car Customer Info.

  • External Key: Unique key for data extension(If we don’t enter value on this marketing cloud automatically assign some value for it). I kept this field blank while creating after saving the record saw external key automatically assigned to it by marketing cloud.

  • Description: Description related to data extension . I have enter description as “Storing Rental Firm Customer Info”

  • Location: Under which folder want to save DE. I have saved on one of my data extension folder.

  • There are 3 option to delete DE :

  • Individual records(Only single record will get deleted)

  • All Records and Data extensions(Data extension will get deleted with all records)

  • All records: (Only all records will get deleted)

  • Period: We can select specific time when want to delete data or data extension.

Firstly I have enabled retention setting by selecting ON flag and selected apply to option as all records and data extension so that data extension and all records should get deleted. Under period option I have specified specific day so that after particular day all records and data extension will get deleted.

!

There are multiple option which can be used to delete the records or data extension.

I have created multiple fields

  • Subscriber key(Primary key for customer)
  • Email address(Email Address Data Type)
  • Recent car hire start date(Date data type)
  • Recent car hire duration(Number data type)
  • Recent car hire model(text data type)
  • Recent car hire color(text data type)
  • Recent car hire price(decimal data type)
  • Recent customer name (text data type)

For all fields (except number and date field) I have assigned maximum length.

As subscriber key is a primary key for my sendable data extension so I have selected subscriber key field from relationship picklist to relates with subscriber info.

!

After creating data extension I have imported data on it using import wizard.

  • Clicked on create button. Email message, Email Template, Content Blocks, Upload option was visible.

  • Clicked on email template and there was two option available paste HTML and existing template.

  • I have selected existing template option but can be selected paste HTML option also based on your business use cases.

    !

  • Selected dynamic content and drag and drop to email content area where i want to include content.

  • Double clicked on the dynamic content block and clicked on browse button to upload or select existing content for default content.

    !

  • Clicked on create rule link + sign.

  • Selected the data source as data extension or audience option as I have stored customer info on data extension and clicked on OK button.

    !

In first rule the criteria I have defined based on the recent car hire duration field value and value I have assigned for this as greater than 15 days. According to me the users which fulfil this criteria they have shown trust on rental firm. Based on that created content for these users which will show them some good model of the car with compelling rent prices and unlimited mileage. Also, I have added some of the good deal in content to engage them for long term rental by comparing with their previous rental price and include some overall rental off.

In second rule the criteria I have defined based on the recent car hire duration field value and value I have assigned for this as less than equal to 7 days AND Recent car hire duration is greater than 1.

In third rule the criteria I have defined based on the recent car hire duration and **recent hire car model **fields. The values I have assigned for this as Recent car hire duration is equal to 1 day AND Recent car hire Model is the highest one which rental firm is providing.

In default criteria I have assigned all deals which rent firm can provide to their customers.

!

  • Clicked on interactive email icon from content block and drag and drop into email content .

  • Selected starting point as review and click on continue.

    !

I have previewed and tested email using data extension values and verify how email content looks like based on different criteria.

In summary WYSIWYG helps marketing team to build personalize and dynamic content which helps in reengaging or engaging customers. Using WYSIWYG without HTML , code and SQL query email content can be build.

So in this article we show what was my approach to achieve dynamic content using WYSIWYG without any code , query , HTML , AMPScript. How WYSIWYG editor helps in creating dynamic content based on the criteria which helps marketing team to engage with customers based on their behavior/persona. Also we show with point of click /no code approach we can solve some of the problems.

WYSIWYG provides capability for coding as well. If there is any business scenario where need some complex data in that case we can use HTML and AMPScript code to build the content. Also in my approach I have selected data extension for creating rules it’s just one approach we can select list as well. The rules in dynamic content can be created based on any logical operator like (AND /OR) also greater than, less than, equal to, not equal to and so on. Interactive email can be used for multiple option as it has lots of built in capabilities.

Read more
Shrishti Mishra
Published 07/16/2020
AMPScript
Baby Shark AMPScript Challenge Winning Solutions

First, thanks to all of our Baby Shark Challenge participants. There was an overwhelming response to this challenge; 120 submissions! After sifting through all of these great submissions, winners for each of the three categories were found. These winners claimed a signed copy of the AMPscript Guide book by Adam Spriggs and Eliot Harper.

The three categories were:

The concentration in this category was how easy it is to pick up and read the code. This includes adding comments, using more ‘basic’ and understandable functions as well as formatting it in a clear and readable way.

For my solution, my wish was to showcase that it is not always necessary to comment your code when your code itself reveals all about itself! I wanted to make it such that anyone who would take a look at it would be able to understand what is its purpose and how it is achieving it. It comes from some of my experiences that people who love coding do not necessarily like to comment on their code all the time, which in turn introduces gaps in understanding for other individuals.

Ivanjan Chattoraj


For innovation, the challenge was to come up with the most unique or ‘out of the box’ approach to output the Baby Shark lyrics in the specified format. However, you had to keep in mind that complexity alone does not equal innovation. Efficiency and elegance were also a consideration in this category.

I looked at the structure of the song and it made me realize it will look very nicely organized if we put it in an XML format. Then I used XSLT to easily format the song as I needed.

Pato Sapir


The efficiency category was based solely on character count. The challenge to the community was to output the full lyrics in a specific format with as few characters possible. ‘Hacks’ and abnormal structures that go against best practice were allowed and encouraged - the end result was all that mattered.

To get started, I had 2 ideas in mind, whether to use one FOR loop or 2. I started off my approach using a single FOR loop, modifying the AMPScript as I went through using functions like REPLACE to reduce the character count. After a while, I found that I reached a limitation towards the single FOR loop process, which is when I decided to switch to a 2-loop process, which drastically decreased the count. I did enjoy the competition, especially the Efficiency method having a clear goal to achieve, being based on just the minimum character count possible, and competing against fellow SFMC developers in the community posting their latest character count submission to motivate others into exploring other means. I was constantly looking for methods to reduce the script down as much as possible, understanding limitations within AMPScript and measures that normally would not be used, such as replacing whitespace with line breaks which did not count as valid characters on the character count site yet stills renders the AMPScript as valid, or inserting a single character into an IIF statement that does not render a value.

Nathan Stone


There were a few other amazing solutions other than our winners that we wanted to share. The hard work and great efforts they put into their amazing solutions deserves recognition. The two stand-outs are below:

A shining example that shows just how a thinking outside of the box can create a beautiful AMPscript output. This submission unfortunately, didn’t meet the printing lyrics requirements of the competition, therefore was disqualified from winning a book - but, it is a complete piece of art. It would be unfair to say just how long she spent on the code for this, but there were definitely some long days involved!

It’s stunning, it’s clever and it’s a submission that was completely unpredictable.

Akin to the example above where the code outputs the lyrics in the form of a shark this individual took the concept of printing out the Baby Shark lyrics and flipped it on its head. The code submitted to the innovation category in itself was written to look like a baby shark. Another stunning example of making code look awesome.

!alt


We hope you enjoyed this contest as much as we did. We were absolutely blown away by the sheer number of participants, the creativity and also in some ways, the commonality between some submissions. There were a few techniques that were used in multiple submissions that we’ll share some of our thoughts on in a future article. But we have to say a huge thank you to everybody who got involved. We have more tasks like this coming in the next few months, so keep an eye out for us on socials and on here. But until next time, thank you!

Read more
HowToSFMC
Published 07/14/2020
Web Studio
The 'Catches' of Try/Catch in SFMC SSJS

Many people lately have been putting out awesome articles on debugging SFMC SSJS, which is fantastic. Most have a section talking about utilizing try/catch, which is a great tool to help figure out specifically what is wrong in a code block. The issue is, most of these posts leave out some important considerations when using try/catch. I hope to help address those here.

For those who are unaware of what ‘try/catch‘ is, it is a statement that wraps around a block of code that you will ‘try’ and should any exception or error be thrown, it will abandon that block go to a ‘Catch’ block.

The main benefit here is that it will not default to likely throwing a 500 error, but instead you can gather the error thrown and then elegantly execute other actions or properly display/log this error as you see fit.

Technically, try/catch is not actually the complete form of this statement. There is an optional third clause that can be included – the ‘finally‘ clause. This will execute after the try/catch portion, regardless of which is executed.

So if the try attempt is made, but errors out so catch clause runs, it will then move to finally clause. The same is true if the try succeeds, and the catch is ignored, it will then move to the finally clause and execute there. The finally block will always execute regardless of try/catch success. Note too that by including a Finally clause, you actually make the catch clause optional.

My best recommendations when dealing with try/catch:.

  1. Put as little code inside of a try/catch as you can.
  2. Declare global variables outside of blocks, as all variables declared inside are ‘local‘ only.
  3. Use conditionals in your catch block to handle different exceptions/errors in different ways
  4. KISS (Keep it Simple, Stupid). The more complex you make it, the more likely you will need to debug your debugger

The reason I recommend putting as little inside a try/catch block as possible is for ease of reading, maintaining and potential scope issues. This also applies for the KISS recommendation, with the added benefit of the less nested try/catches or complex structures/scopes, the less likely something will go wrong or break. Taking it from a very specific use-case debugging tool to something that may require complex debugging itself.

Try/catch has little to no performance degradation or effect, so implementing it multiple times in different places is not just possible, but also can be recommended. This, of course, comes with the caveat that if the nature of the types of errors you are expecting/handling is singular and easily handled in a single block, then there is no reason to implement multiple try/catch statements as stated above.

As stated above, the try/catch/finally blocks are all local scope – so nothing declared in there will transfer outside of those individual blocks. This can certainly cause some major confusion if you do not take this into account.

With local scope, this means that something declared in try will not pass to catch or finally as it is ‘block specific values’. Now, there are ways to get around local scope….but most of them require a more recent version of JS, which we do not have.

The good news is that there is one that can help us get around this. By declaring the variable globally and then setting a value in the local scope, this will retain that value on the variable.

This example shows local scope not passing the value of the variable:

<script runat=server>
try {
	var a = 1;  //local scope only
	throw "Error";
} catch(e) {
	Platform.Response.Write(a); 
	//will write a null value to the page, as 'a' is undefined in this context
}

Platform.Response.Write(a); 
//will write a null value to the page, as 'a' is undefined in this context
</script>

Declaring the variable as a global variable, the value will be passed:

<script runat=server>
var a;

try {
	a = 1;  
	//cannot use 'var' here or it will be declared again,
	// making it local and not retaining the value
	throw "Error";
} catch(e) {
	Platform.Response.Write(a); 
	//will write 1 as a was set to 1 prior to the exception
}

Platform.Response.Write(a); 
//will write 1 as a was set to 1 inside the try/catch block
</script>

As noted in the comments of the sample scripts, the major thing to watch out for is do not ‘re-declare’ your variable in the blocks. This means, avoid using ‘var’ on your variables inside try/catch, only set the value (at least for your global variables). If you re-declare your variable with ‘var’ inside the block, this will make it be considered a local variable and will not retain the value.

This one comes straight from learnings inside of a [Salesforce Stack Exchange Question](https://salesforce.stackexchange.com/questions/311799/ssjs-platform-response-redirect-throws-unknown-error-error-in-application/311934?noredirect=1

Well! It appears this is a well documented .Net issue revolving around that Redirects will throw a ‘ThreadAbort’ exception, which would trigger the catch block to run instead. Now SFMC SSJS does not have all the great exception handling capabilities that you can see in languages like .Net, so what the heck do we do?

My recommended approach is to utilize an if statement inside the catch to watch for the ‘AMPScriptRedirectException’ (as it is called in SFMC error description) to be able to only have a redirect in the catch if it is appropriately needed. With this approach, the redirect will not be inside the catch statement when the RedirectException is thrown. Which means the try statement will correctly run and redirect accordingly.

Below is my recommended code:

<script runat="server">
    var redirect = 'https://google.com'

    try{
          Platform.Response.Redirect(redirect')
    } catch(e) {
        var desc = e.description; //Pulls the description from error object
        if(desc.indexOf("ExactTarget.OMM.AMPScriptRedirectException") > -1) {
          Platform.Response.Write(desc) //This is arbitrary as will not be run
        } else {
          redirect = 'https://yahoo.com'
          Platform.Response.Redirect(redirect)
        }
    }
</script>

The major benefit to this approach (other than that I submitted it as my answer) is that it allows you to differentiate how each type of exception/error is handled via the conditional. It allows you to create additional ‘else if’ statements to potentially have other Redirects or handling/logging options. It also is very readable and clean – allowing for easy maintenance and low processing requirements.

A second approach to this issue is to utilize a second try/catch inside of your original try/catch statement for the redirect. This was provided by the Original Poster (shd.lux) based on the accepted answer by Daniel Koch. Inside this example, there is a nested try/catch inside the original one. Because there is no ‘catch’ action in the nested try/catch, the ‘try’ redirect will function correctly. But if there are any exceptions in code surrounding this nested try/catch, it will be sent to the Redirect in the parent try/catch.

Code snippet example taken from the answer:

<script runat="server" language="javascript">
Platform.Load("core", "1.1.1");

try {

    // do stuff
    try{
        Redirect("http://www.google.com", false);    
    } catch(e) {}

} catch (e) {
    Redirect("http://www.yahoo.com", false);
}
</script>

I do see the value in this solution, especially in its simplicity, but I do worry about any risk that could come from attempting it. I worry if there is an actual error with the Redirect, your page will not toss any exceptions or anything as it will go to the empty catch - meaning it could potentially not redirect like intended, but instead stay on page.

The solution seems a bit to me like using duct tape to ‘fix’ a broken window. Sure, it will work and keep the water out of the car, but it is not pretty and can limit capabilities – like opening it or seeing through it. Depending on your comfort level and needs, this is a viable approach as long as you consider the approach and ensure it meets your needs and does not limit future requirements.

The final way (at least that I will cover here) is what was recommended by Daniel Koch. His recommended way is to declare your ‘try’ redirect var url above the try/catch as a global variable. Then if the ‘try’ throws an exception/error, you reassign a ‘catch’ value to it inside your ‘catch’ block. This value is then used in your Redirect function that is run below the try/catch block and will take you to the URL that was assigned or originally declared as appropriate.

Sample code taken from the answer:

<script runat="server">
    var redirect = 'http://www.google.com'
  
    try{
        //do your stuff here
    } catch(e) {
        redirect = 'http://www.yahoo.com'
    }
  
    Platform.Response.Redirect(redirect)
</script>

This is a very simple and elegant solution, but is also very specific to this particular issue. It doesn’t offer the same flexibility that I feel the conditional ‘catch’ block offers around handling errors. For instance, the redirect exists wholely outside the try/catch block, meaning if there is an error there, it will cause a 500 error on the page.

Now, that being said, I would definitely recommend this if your use-case is singular and you have no need for detailed exception handling or worries on the Redirect tossing any errors. This approach is easy to read, maintain and debug.

So, as you can see, there are a few things to keep in mind while using try/catch. The above is certainly not comprehensive and there are other things that can come up and cause ‘hiccups’ when using try/catch, but I feel the above examples should shed light on the majority of the ‘catches’ of try/catch. Hope this helps make debugging less painful for you!

Read more
Gortonington
Published 07/11/2020
Release Notes
July 2020 Release Notes - Deep dive & Summary

With the May 2020 release finally finishing it’s arrival in all of our orgs, we can now look to the new horizon of July 2020 landing in our orgs in a few weeks time. In summary, it’s another slim form factor release - but that doesn’t mean it’s a bad thing. Skip to the bottom if you’re not interested in the details and just want the abridged version.

The navigation is continuing down the form factor of bundling everything to do with Emails/SMS/Push/Cloud Pages under the banner of “Journeys and Messages”. Big improvement from a UX perspective is now not all elements of the release include “Month Year Release Notes”. A minor bug bear but it didn’t do much to help keep the release notes readable.

So a big thank you to the documentation team for making a tweak like this!

InfoSec professionals rejoice! Multi-Factor Authentication has arrived in SFMC. Whilst the actual implementation of it and what permissions you would need to manage the MFA settings (I would assume that there’s something in the Marketing Cloud Security Administrator user role or in the standard Marketing Cloud Administrator use role). Marketing Cloud Admin will be able to send temporary code to any users who lose their MFA device, but until we see it live we can’t say a whole lot about how it works. Fortunately, a range of MFA methods are available (including the Salesforce Authenticator app, so if you’re already using that then you at least don’t need something else!) Once we see this popping up we’ll add some details, as a security and access change it’s completely understandable that you may not want to make the change without some confidence that you won’t end up locked out! Fingers crossed support are ready for an influx of people who’ve seen it and thought “cool! We need this” without having thought of how it affects the users of their org.

Thank goodness. If you’re using SFMC and need to use Internet Explorer 11 - Show your IT team this post and let them know that the HowToSFMC team is officially against the use of Internet Explorer 11. If Microsoft themselves can embrace Chromium in Edge - so can you. It’s almost 7 years old. Just upgrade. Please.

New REST API search operators for those who use the /asset/v1/content/assets/query endpoint for retrieving the Name, CustomerKey, Content and Subject Line fields within a Content Builder Asset. Whilst it’s historically been more difficult than you’d want it to be if you don’t know the folder of an asset, it should become a little easier with these new operators:

  • MUSTCONTAIN
    • Results should have all the words included in the search term
  • EQUALS
    • Exact match of the search term
  • CONTAINS
    • Must have at least one of the words in the search term

Nice.

This is one of my favourite but also most frustrating elements of the release. Previously erroring for Journey Builder sending, Triggered sending or Job sending emails you wouldn’t necessarily find out straight away or event be notified at all. Now, however, you can input (this is the frustrating bit!) A SINGLE ADMINISTRATOR EMAIL ADDRESS to receive any and all alerts that are triggered around send alerts. It’s a cool feature and it’s one that I would have been grateful to have had in the past. I sincerely hope that we could include a variety of alerts for different reasons to different people & also different channels. Sometimes after a busy day at work (especially whilst we’re all on lockdown) I shock-horror turn off my emails for a few hours. There may be scenarios where an error is at the level of significance that an SMS would be more appropriate. But I’m still looking forward to trying it out and seeing how it works!

For those who have Interaction Studio, it appears to see that for all Interaction Studio customers will have another system user role created - Interaction Studio - Legacy & some new permissions to give users the appropriate permissions in the new version of Interaction Studio (previously known as Evergage). Make sure to take a look at these if you’re using it.

The second announcement about this for the A/B Testing via Marketing Cloud Connect. It relies on some Classic Content functionality (Which supposedly retired for creation of new assets just a couple of weeks ago) - So for Classic Content… This Ain’t Goodbye… Okay, well maybe it is - but thanks for your service. Long live Content Builder.

If you’re integrated with Salesforce CRM with Marketing Cloud Connect & you’re active in that side of the world - you’ll start to get some new banners appearing about the Business Units and Enterprise in the integration.

The release notes just say that they’ll “provide details” - Doesn’t specify what details. It might tell you how many people named Jason you have in your SFMC org or it might tell you something less useful than that. Maybe they’ll be customisable? Who knows, the release notes don’t in this case. Maybe there’ll be some screenshots added in the future to bring it to life (hint hint!)

If you’re in an integrated org, there’s a few more things to be aware of:

  • After a deadline date of July 31st in the May release, the July release now gives you until September 30th to migrate to the connected app. Please do it soon, it’s not too complicated and it means that Salesforce can switch it off and make everyone happy.
  • Distributed Marketing gets some enhancements to the workflow and send scheduling which is helpful. Send scheduling will allow you to schedule something that needs to be approved, but if the approval isn’t available in time the send gets cancelled. So make sure if you’re going to schedule something that whoever needs to approve it knows about it! Another new feature is that it’ll work on tablets (but still no robust phone support)
  • Integrate Interaction Studio with the Sales and Service Cloud with the managed package & the connector (but you need to have Interaction Studio premium in this instance)
  • Individual Link Details being moved to the Bulk API to help customers hitting their API limits due to tracking info sent over Marketing Cloud Connect
  • Synchronised objects with data type mismatches will have the individual field no longer sync, allowing the rest of the object to come through. It’s a shame this doesn’t come through from the Alert Manager rather than having to check the Synchronized Activity Log. But alas! At least it doesn’t all just fall over because someone changed a field type in Salesforce CRM.
  • New relationships get to retain the original relationships to keep things consistent. Not sure if that would be retained and added or retained and discarded. Release notes are a little vague in the fullness of 19 words.

We have the first casualty of the release notes and the release notes are only a couple of days old.

!alt

So if anyone managed to catch what the Modify Permissions for Marketing Cloud SFTP Folders was - let me know! I’m curious.

You can set this up in Contact Builder based on hourly, daily, weekly or monthly cadences. If you choose daily or weekly you get to pick an hour, monthly you get to choose which day of the month. So… Scheduling like we’re used to in other areas of the platform.

If you’re using Audience Builder, you may find your refresh process is taking a lot of time some days. If you happen to be a user who has lots of dimensions, but you only use some on a daily basis - the refresh procedure and data loaders will now automatically detect which ones aren’t being used and will not get that data processed. Just one to be aware of - as the process runs daily if you do need the dimension at short notice you may need to work out the best way to deliver that with Audience Builder. If you’re not sure how best to manage this, the system will only deactivate a dimension if it’s not used. So if you use a dimension in an Audience Filter Segment or Exclusion, the data will be refreshed every day. Not sure if this is a good update but if there are dimensions that you’ll never need you may as well not wait for them to process.

It’s definitely a light one for Developers this time around. You’ll be able to Link journeys to transactional message REST API Send Definitions. Anything you’ve already got in place, you’ll need to use the options.createJourney JSON parameter by PATCHing the /messaging/v1/email/definitions endpoint with your definition key.

With all of the details around SMS from Kevin Ryan from the Technical Marketers Bi-Monthly Meeting on 25th June - it’s great to see that the SMS Status Codes will be available via the REST API and not just the Data Views. The API will return the “highest” status code from the carrier. More details are available here.

Unsecured HTTP Callbacks for SOAP Async API are being turned off. Just switch to HTTPS and you should be good.

Everyone’s favourite cartoon genius (Sorry Dexter, your laboratory is cool and all - but I’ve never seen you ride a 3 person tandem bike with Astro who can’t reach the pedals and Codey who can’t reach the handlebars) is getting some new features!

!alt

  • Send Time Optimization is coming to Push. If you know STO for email & you know Push. Smash them together and you get Push STO.
  • Send Time Insights in Journeys, with STO you’ll start to get some more accurate details around the progression of contacts in the journey. You’ll get total counts for the last 30 days and see a breakdown of those who do and don’t have personalised send times modelled.
  • Some more info from the Einstein Messaging Insights Model card, which is awesome. Always a good sign to have more information made available to use rather than the insight being hidden away in a Salesforce box.
  • Content variety with a change in the Einstein Content Selection block - previously if you had more than one block in an email you may get 2 similar pieces of content selected. So at least this way you don’t send someone the same piece of content twice in one send! Just include the additional asset attribute you want to vary across in the Variety tab.
  • Einstein is going to play around in Interaction Studio a little bit more, using Einstein Personalization Decisions for next best actions and similar experiences.

Journey Builder is getting some great new features and considerations in the new release including:

  • Dedicated Journey Builder permissions. Administrators will be able to dedicate roles around the design and configuration of a Journey but not necessarily enable the same user to activate it. If you have a marketing team who have dispatch in their remit but not orchestration it may not be best practice for them to have permission to edit a journey. This way you can also create a safe way for newcomers to Journey Builder to get hands on without risking sending an email to your full customer base!
  • Transactional sending from Journey Builder and including near real time operational metrics is nifty, takes it out of the hands of just developers via the Transactional API and it does include the same out of the box features as Multi-Step and Single Send journeys.
  • Single send SMS is here. We’ve had Single Send Email & Single Send Push already so now it’s time for Single Send SMS. If you’ve got Journey Builder & you’ve got Mobile Connect - now you can do all your sending in one place with Journey Builder. It’s good to have consistency. Single Send Line next release?
  • Find and remove a contact from a journey in Journey Builder. FINALLY. Many of us have had an instance where we’ve needed to get someone out of a journey for whatever reason and we’ve all got our own ways of dealing with the situation. Some use Goal/Exit criteria and go change some data about the contact, some use decision splits to force people down a specific exit path and some use the API. None of which are ideal, usually when you need to hoof someone out of a journey it’s because something serious happened and they need to go now. If you’ve never done the API ejection or you haven’t got the Goal/Exit criteria - up until this goes live, you would have been up a creek with no paddle. Thanks Salesforce for finally adding this feature!
  • Abandoned Browse, Abandoned Basket and Abandoned Wishlist emails now possible with the collect tracking code within Journey Builder using the Behavioural Trigger data extension as an entry source to the Journey. Lots of us have custom solutions for this situation, but hopefully this will streamline it for future iterations!

For Mobile Push - Update your Auth Keys for iOS. You have until September 14th. Release notes for the 2020 Push SDK are available for iOS and Android

For SMS - There’s now a loop prevention process in place for those times where you accidentally send to a system that automatically responds to you. So now, if you receive more 35 messages from a single number in a 5 minute period, outbound messages will stop being deployed.

You’ll be able to update the SSL selection after a page has been published, which should be nice and help those who have any legacy pages prior to purchasing an SSL cert. Rather than fully redeploying a landing page (especially a preference centre) that has been active for months just because of an SSL cert.

CloudPages did have their base template updated to remove an insecure version of jQuery. So if you have any jQuery on a landing page I thoroughly recommend you go and check whether you need to add it yourself manually now!

Have you been trying out the new CloudPages Experience PREVIEW? Any thoughts on it? Let us know!

Email feels a bit like the last kid picked at school this release. The only change at all is the addition of Interactive Email being able to accept a pre-populated field. So if you’re providing a contact with a form including a name or any other details - you could surface this information for them to confirm or reject it directly from the email.

Pretty neat, don’t think it’s massively a game changer but any improvement for Interactive Email is massively welcome.

Overall, whilst this is a pretty slimline release, there are some great call outs to be made but most of them aren’t on the super fun stuff we all love to play with. It’s definitely a release of practicality and it almost feels like SFMC is growing up and becoming more responsible for itself. MFA is a great addition for a security perspective, the Alert Manager is massively welcomed to any solo admin of a wide org & the journey removal process now being in the UI to make things a little easier. On the whole, this is a sensible release and fingers crossed it all lands before the release notes for the October release hit the internet!

Read more
Jason Cort
Published 07/08/2020
Release Notes
Journey Pausing - A quick summary and considerations

After being announced as part of the May release, delayed until the end of June, organisations will have started to see the Journey Pause capability roll out at the beginning of the second week of July (We’ve waited a while for this anyway, what’s a couple more weeks?). We’ve now started to see it in the wild so wanted to share some insight about HowTo Journey Pause and what implications there are for some of the situations where you’d need to pause a Journey.

To start, we’ll look at how to configure Journey Pause.

When your SFMC instance has received the update to enable Journey Pause, you’ll see this in the top right of an active Journey.

When I read the original release notes, I was under the impression that it would be an all or nothing pause as opposed to a pause that could be applied at a journey version level. Which does add some additional flexibility to applying a journey pause to the marketing cloud users. Eg: If you’ve got a requirement to apply a pause and are currently finishing a previous version of a journey - you could leave that to finish regardless to close it out or you can pause it too.

The only configuration beyond selecting which version of a journey gets paused, is the duration and the Wait by Duration extension option.

!alt

!alt

In the instance below we have a fixed wait by duration Pre-Birthday email campaign. With the way it is configured, anything longer than a 7 day pause would definitely cause a problem for all contacts involved in the flow of the journey. But, if you were using relative wait steps (Wait Until Date / Wait By Attribute) you would have no option but to have everyone flow through immediately when the journey becomes unpaused. So it’s something to consider when orchestrating the flow controls of your journey if you are taking into account the functionality of Journey Pausing.

!alt

The other option available is the post-pause-period action. If you set a journey to pause for 14 days, what behaviour should SFMC take with the journey if it hasn’t been resumed? Much like we referred to in the original release notes write up that this isn’t a true/permanent pause. It is the opportunity to pause a journey and make a decision about what to do in a period of crisis without just pulling the plug. But, in order to deploy a pause you do need to decide what happens if you do not come to a conclusion and either resume or stop the journey manually.

One such scenario would be that if your journey has to pause due to an inventory bug on an eCommerce store showing all products out of stock, it might make sense to set a 3 day pause and then the automatic response be to reactivate. However, I suspect that isn’t necessarily the best way to go about it. If the bug gets fixed in 2 days you wouldn’t wait for day 3 and if it takes longer than 3 days to fix you’ve kind of backed yourself into a corner with the pause expiring.

!alt

My suggestion would be that if you need to pause a journey, unless you have a specific reason to need SFMC to make the future decision for you at a shorter time frame - Just go for a 14 day pause. You can go back and resume or you can go back and stop a journey whilst it is paused. You cannot currently extend a shorter pause without resuming the journey first.

!alt

So in summary - Journey Pausing is a great new tool to have in your arsenal. It goes part of the way towards supporting us with some of the issues that can crop up, whether it’s a stock control issue, a data quality issue or a global pandemic. But, be aware that when you’re using it - it will only do what you tell it to do and it will only hold your journey in for as long as you tell it to (for up to 14 days).

Good luck & I hope you never need to use this feature in a crisis (because that means something went wrong and nobody likes that!)

Read more
Jason Cort
Published 06/06/2020
Winning Solutions
James Lamb - Subscriber Preferences Management

Everywhere I’ve worked, Subscriber Management has been a complex beast. You’ve got multiple backend systems that know about a person, you want to make sure a person is empowered to control their subscription status (not just unsubscribe), you’ve got legal issues around compliance and you’ve got the technical challenges of bounces.

At each place I’ve worked, we’ve approached that challenge from a different angle with varying levels of success, but recently I had a chance to design a system from scratch. My solution is for our particular business objectives and based on U.S. law which is far more lax than some other countries. In the case of law compliance and procedures, always make sure to check with your company’s legal team. I wish I could share more detail (because I’m pretty proud of it), but there are proprietary/confidential details that I can’t share. So I’ll cover a simplified version of this:

One automation, 20 data extensions (less if you left off development and qa), 42 queries (again, less w/o dev and qa), 1 Data Extract, 1 File Transfer and 1 Import.

  • Honor all unsubscribe requests in 10 business days or less. Hopefully much, much less.

  • Allow subscribers to unsubscribe without authentication (existing landing page allows them to enter their email address and hit submit to unsubscribe. This is also linked at the bottom of all of our transactional emails and used by our Customer Service team when a person asks to be unsubscribed during a call or chat)

  • Allow subscribers to subscribe or unsubscribe from within their authenticated Account Management portion of our website (applies only to their current, primary email address)

  • Allow use of the native ESP (SFMC) unsubscribe method (click or reply “unsubscribe” in email)

  • Get most recent instruction if they use multiple methods to give conflicting instructions (for instance, use the Account Management to subscribe and then use an email to unsubscribe)

  • Include a Development flow (the right half of the flowchart) and a QA process. I won’t talk much about the Development flow (it mimics production as best as it can). The QA process allows you to get before and after snapshots for comparison. This is useful in troubleshooting. Together with the Development flow, the QA process will be instrumental in any future changes to the process.

  • An email address that’s not unsubscribed is eligible to receive emails. It might or might not actually be sent any emails.

  • An email address that is unsubscribed should not receive any marketing emails.

  • Any new subscribers should get our current blog email (their address should be passed over to that automation to be handled)

  • Unique Key: Email Address. Each account must have a unique active, current email address, each email address can belong to one and only one account. (An email can’t exist on more than one account.)

  • Marketing Election: The user’s indicated choice to be subscribed or not subscribed. It is an attribute of Email Address. It has a timedate stamp.

  • Never use one complicated query when several simpler queries will do

  • This assumes SFMC doesn’t start charging by

  • The entire extended automation takes about 35 minutes end-to-end. With more data, additional optimizations may be necessary.

  • Explicit Opt-In - they have told us they wanted to receive email from us. By U.S. law, this also covers people who have a financial relationship with us (i.e., they’ve made a purchase).

  • Explicit Opt-Out - they have told us they do not want to receive email from us. (They have unsubscribed.)

  • Implicit Opt-In - they have not told us one way or another. Because our website predates our ESP (and the one before it), it’s possible that there are some customers (long-term paying subscribers) whose email address has never been passed to the ESP.

Salesforce Marketing Cloud receives inputs in the following manner:

  1. Subscribe. We offer opportunities on our website to subscribe to an email. Accepted email addresses are passed from the form to SFMC via API (rowset) to an “Opt-In” Data Extension. This stores the Email Address, the timestamp and the form.
  2. Unsubscribe
    1. Native - SFMC offers several native methods for unsubscribe via Profile Center, Subscriber Center or by replying “unsubscribe” in an otherwise blank email. These update the “_Unsubscribe” Data View.
    2. Form - we offer a CloudPage where you can enter an email address to be unsubscribed. It is stored in an “Unsubscribe Request” Data Extension.
    3. API - there are some cases where our website determines an email address should be unsubscribed from marketing emails. These are passed by API (rowset). This does not prevent them from being able to re-subscribe if they so choose.
  3. Subscribe/Unsubscribe - From our Authenticated User Preferences section on our website, there is a checkbox for “Receive Marketing Emails”
    1. When someone first visits the page, an API call is made to SFMC to determine their “Marketing Election”
      1. SFMC responds “Subscribed” - the checkbox is displayed as checked
      2. SFMC responds “Unsubscribed” - the checkbox is display as unchecked
      3. SFMC says it doesn’t know - the checkbox is displayed as checked, another API call (rowset) is made to mark their election as “Subscribed”

Runs hourly

  1. Query - get any new records in the past 24 hours from the _Unsubscribe Data Extension (target SuppressionMaster, update) where the record isn’t also in MarketingEmailElection in a subscribed state with a later timestamp.

    SELECT
     
        u.SubscriberKey as EmailAddress,
        MAX(u.EventDate) as dteOptOut,
        '_Unsubscribe' as strSource
     
    FROM
     
        [_Unsubscribe] u
         
    WHERE
     
        u.EventDate > DATEADD(HOUR, -25, GETDATE())
     
        AND
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = u.SubscriberKey
    AND u.EventDate < mee.dteUpdate and mee.blnOptIn = 1)
          
    GROUP BY
     
         u.SubscriberKey
    
  2. Query - get any new records that were passed via the unsubscribe API (target: SuppressionMaster, update)

    SELECT
     
        s.Email as EmailAddress,
        s.[Last Modified] as dteOptOut,
        'suppression_prod' as strSource
     
    FROM
     
        [WebSuppression] s
         
    WHERE
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = s.Email
    AND s.[Last Modified] < mee.dteUpdate
    and mee.blnOptIn = 1)
    
  3. Query - clear WebSuppression (could explore using SSJS to remove all rows) (target: WebSuppression, OVERWRITE)

    SELECT
     
        EmailAddress as Email
     
    FROM
     
        [SuppressionMaster]
         
    WHERE
     
        EmailAddress = 'intentionally, this will never match anything@hello.com'
    
  4. Query - get all the opt-outs from the opt-out form (target SuppressionMaster, update)

    SELECT
     
        eco.EmailAddress,
        eco.[dteSubmitted] as dteOptOut,
        'ExternalCloudpageOptout' as strSource
     
    FROM
     
        [ExternalCloudpageOptout] eco
     
    WHERE
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = eco.EmailAddress AND eco.[dteSubmitted] < mee.dteUpdate
    and mee.blnOptIn = 1)
    
  5. Query - Clear optout form. (similar to query #3)

  6. Query - get any new unsubscribed records from _Subscribers (All Subs). Target SuppressionMaster, update

    SELECT
     
        s.EmailAddress,
        s.[DateUnsubscribed] as dteOptOut,
        '_Subscribers' as strSource
     
    FROM
     
        [_Subscribers] s
         
    WHERE
     
        s.status = 'unsubscribed'
     
        AND
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = s.EmailAddress AND s.[DateUnsubscribed] < mee.dteUpdate
    and mee.blnOptIn = 1)
    
  7. Query - get all the unsubscribes from MarketingEmailElection - target SuppressionMaster, update

    SELECT
     
        EmailAddress,
        dteUpdate as dteOptOut
     
    FROM
     
        [marketing_email_election_prod]
         
    WHERE
     
        blnOptin = 0
    

    Ok, at this stage “SuppressionMaster” now has all of the unsubscribes from all the different places you can unsubscribe. Now, we’re going to handle all of the new “subscribe” (yes) elections.

  8. Query - get all the “yes” entries from the past 72 hours from MarketingEmailElection and store in MeeYes1, OVERWRITE. (At this point, you’ve probably guessed that I’m changing the DE names from what I use in my instance.)

    SELECT
    	EmailAddress,
    	blnOptin,
    	dteUpdate,
    	strSource =
    		CASE
    		WHEN strSource is NULL THEN 'AcctPage'
    		ELSE strSource
    		END
    FROM [MarketingEmailElection] mee
    WHEREblnOptin = 1
      
     AND
      
     DATEDIFF(hour,mee.dteUpdate,GETDATE())<72
    
  9. Query - now, get only the “yes” requests that are newer than the “no” (unsubscribe) requests in SuppressionMaster. Target MeeYes2, OVERWRITE.

    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        sm.dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
         
    INNER JOIN
         
        [SuppressionMaster] sm
            ON mee1.EmailAddress = sm.EmailAddress
             
    WHERE
     
        mee1.dteUpdate > sm.dteModified
         
        AND
         
        mee1.blnOptin = 1
             
    UNION
     
    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        NULL as dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
     
    WHERE
     
        NOT EXISTS (select 1 from [SuppressionMaster] sm
    where sm.EmailAddress = mee1.EmailAddress )
    
  10. Query - now get all the records that are in SuppressionMaster unless they’re in MeeYes1. Target SuppressMasterTransit, OVERWRITE. We need to do this because we need to remove the new “yes” entries from SuppressionMaster.

    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        sm.dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
         
    INNER JOIN
         
        [SuppressionMaster] sm
            ON mee1.EmailAddress = sm.EmailAddress
             
    WHERE
     
        mee1.dteUpdate > sm.dteModified
         
        AND
         
        mee1.blnOptin = 1
             
    UNION
     
    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        NULL as dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
     
    WHERE
     
        NOT EXISTS (select 1 from [SuppressionMaster] sm
    where sm.EmailAddress = mee1.EmailAddress )
    
  11. Query - now we write them back to SuppressMaster, OVERWRITE. At some point, I’d love to replace this with an SSJS SQL “DELETE” query instead.

    SELECT
     
        EmailAddress,
        dteOptOut,
        dteModified
         
    FROM
     
        [SuppressionMasterTransit]
    
  12. Query - now we need to make a list of the records that need to be updated in _Subscribers. Target SubscribersUpdate, OVERWRITE

    SELECT
     
        y2.EmailAddress as [Email Address],
        y2.EmailAddress as [Subscriber Key],
        'active' as status,
        y2.blnOptin,
        y2.dteUpdate,
        s.DateUnsubscribed
         
    FROM
     
        [MeeYes2] y2
         
    INNER JOIN
     
        [_Subscribers] s
        ON s.EmailAddress = y2.EmailAddress
    
  13. Now that we’ve got that file, we need to Export it to our SFMC SFTP Storehouse.

  14. And then transfer it from the Storehouse to the SFTP Import Directory.

  15. And then import it into _Subscribers, update.

  16. Get everyone from Yes1 in the past 24 hours and place into Yes3, OVERWRITE.

    SELECT
     
        yes1.EmailAddress,
        yes1.dteUpdate,
        yes1.strSource
         
    FROM
     
        [MeeYes1] yes1
         
    WHERE
     
        yes1.dteUpdate > dateadd("d",-1,GETDATE())
         
        AND
         
        yes1.blnOptin = 1
    
  17. Place everyone from Yes3 into the “optin” data extension. A different process will read from this Data Extension and send the latest blog email to all new opt-ins. It’s designed to only send them the email once, even if they appear in here more than once.

    SELECT
     
        yes1.EmailAddress,
        yes1.dteUpdate,
        yes1.strSource
         
    FROM
     
        [marketing_email_election_yes1_prod] yes1
         
    WHERE
     
        yes1.dteUpdate > dateadd("d",-1,GETDATE())
         
        AND
         
        yes1.blnOptin = 1
    

!Schema table

  • mee = MarketingEmailElection, MeeYes1, MeeYes3
  • mee2 = MeeYes2
  • ms = not explained in this simplified version for HowToSFMC
  • qa1, qa2, qa3 = used in the QA process, also not explained here

SuppressionMaster and SuppressionMasterTransit

!

!Extract Configuration

!File transfer setup

!Import configuration

Read more
James Lamb
Published 06/06/2020
Winning Solutions
Matt Brocious - Subscriber Preferences Management

For the “preferences management” solution, I wanted an option that was user-friendly (both internal and external users), that was scalable for a business of most any size, and that played to the strengths of both the Marketing Cloud tool and the AMPscript coding language.

As a quick overview, this solution uses a CloudPage to capture user’s email content preferences and if they want to unsubscribe from your emails or not. Once submitted, the preferences and whether or not the user is mailable (unsubscribed or active) is stored in a data extension which can be used to segment customers or can be used to populate a field in your customer master. Additionally, we use a data extension that holds your preferences to populate the form on the CloudPage. This will allow you to easily modify your preferences without making major updates to the form.

Before I begin describing the setup in more detail, there are a few things you’ll want to make sure are setup within your account:

  • You’ll need to make sure you have access to CloudPages within your account.
  • If desired, ensure you have personalized URLs setup with SSL certs for your CloudPages.
  • Review your business unit rules to ensure they are setup accordingly. For example, if unsubscribes happen on a business unit level or if statuses are shared.

This setup also makes a few assumptions, so if they don’t apply to your business then you might need to make some adjustments:

  • Your business rules allow for users to be opted-in to all preferences by default.
  • You are not allowing users to update their email address in the preference center.
  • Your business rules allow for users to re-activate themselves within the preference center after the unsubscribe themselves.
  • You are only allowing users to access the page via a link in an email.

So, if you have everything setup and are okay with how this solution works, let’s start building.

For this setup, you will be creating two data extensions. One to house the user’s preferences and one to house all of the preferences we are listing out on the page.

First, you’ll want to build the preference center data extension. For this setup, we are creating the data extension within the Shared Data Extensions folder via the parent business unit. Below is the data extension we are using for this setup but make any necessary adjustments for your use case.

  • Name: Master_PreferenceCenter_CustomerPreferences
  • Description: Master data extension for user’s preferences and unsubscribes
  • External Key: Master_PreferenceCenter_CustomerPreferences
  • Data Setup:

Next, you’ll want to set up a data extension that will be populated by the preferences you want listed out in your preference center page. This will allow you to easily add additional preferences as your marketing efforts grow more complex. The “priority” field will be used to denote the order in which the preferences should be displayed. The “key” field will be the same for all preferences and will be used in the AMPscript within the CloudPage.

  • Name: Master_PreferenceCenter_PreferencesList
  • Description: List of all email preferences
  • External Key: Master_PreferenceCenter_PreferencesList
  • Data Setup:

!Data Setup

Finally, if necessary, add any additional fields to your customer master you’d like to use in conjunction the prewithference center. For example, a “Preferences” field.

Now that the data extensions are setup, we’ll move onto the CloudPages. For our setup we’ll need one CloudPage that will house the form and process the user’s data and one page to be used as an error page.

So, in the CloudPages section of Marketing Cloud, let’s create a new collection named “Email Preference Center”.

Next, create a new page named “Error”. This page doesn’t need much but feel free to stylize and needed for your use case. For this example, we’re simply going to have some text on the page that reads “An error has occurred. Try your request again or contact your system administrator.”.

Once that is done, we’ll move on to creating our main preferences page. Let’s create a new landing page named “Email Preference Center”. You can design the form as you wish, but you will need the elements listed below on your page for this setup. For an example page - see document sfmc_prefcenter.html.

The below AMPscript code should be added to the top of your page and is used to update the user’s preferences and status on the master preference center data extension and, if the user unsubscribes, within the All Subscribers. It also sets a confirmation message which is displayed on the page after a user takes an action. If the page is accessed and there is no valid email address provided, the page will redirect to the error page we created in the previous step. There are notes in the code to help show which lines are performing which actions.

And, if you aren’t familiar with some of the AMPscript functions being used, it might be helpful to read up on them. Here are some of the functions being used:

  • IsEmailAddress – https://ampscript.guide/isemailaddress/

  • RequestParameter – https://ampscript.guide/requestparameter/

  • Row – https://ampscript.guide/Row/

  • Field - https://ampscript.guide/field/

  • Create Object - https://ampscript.guide/CreateObject

  • SetObjectProperty - https://ampscript.guide/SetObjectProperty

  • AddObjectArrayItem - https://ampscript.guide/AddObjectArrayItem

  • InvokeExecute - https://ampscript.guide/invokeexecute

  • Empty - https://ampscript.guide/empty/

     %%[
     
     /* Set the variables used in this page */
    
     var @email, @jobid, @submitted, @unsubscribe, @preference, @mod_date, @url
    
     set @email = emailaddr
     set @jobid = jobid
     set @submitted = requestparameter("submitted")
     set @unsubscribe = requestparameter("unsubscribe")
     set @preference = requestparameter("preference")
     set @mod_date = Now()
    
     /* If the @email variable is empty but the @submitted or @unsubscribe value are populated with the correct values, populate the @email variable with the email parameter. */
    
     if empty(@email) and (@submitted == "Yes" or @unsubscribe == "Yes") then 
       set @email = requestparameter("email") 
     else endif 
     
     /* If there is no valid email address provided, then redirect the user to the error page */
     
     if isemailaddress(@email) == "false" then
       set @url = "errorpagelinkhere.com"
       redirect(@url)
     else endif
    
    
     /* If the user submitted their preferences, then upsert the record on the master preference center data extension and update their status to active */
    
     if @submitted == "Yes" then 
    
       UpsertDE("Master_PreferenceCenter_CustomerPreferences",1,"Email_Address",@email,"Mailable","Y","Preferences",@preference,"Last_Modified_Date",@mod_date)
    
      SET @sub = CreateObject("Subscriber")
       SetObjectProperty(@sub,"EmailAddress", @email)
       SetObjectProperty(@sub,"SubscriberKey", @email)
    
       SetObjectProperty(@sub,"Status","Active")
       Set @options = CreateObject("UpdateOptions")
       Set @save = CreateObject("SaveOption")
       SetObjectProperty(@save,"SaveAction","UpdateAdd")
       SetObjectProperty(@save,"PropertyName","*")
       AddObjectArrayItem(@options,"SaveOptions", @save)
       /* Here is where we actually update the Subscriber object */
       Set @update_sub = InvokeUpdate(@sub, @update_sub_status, @update_sub_errorcode, @options)
    
       /* Set the confirmation message */
    
       set @message = "Preferences have been updated<br><br>"
    
    
     /* Else, if the user unsubscribed, upsert their record accordingly (Clear the Preferences field and flag them as Mailable = "N") and log the unsubscribe */
    
       elseif @unsubscribe == "Yes" then
    
       UpsertDE("Master_PreferenceCenter_CustomerPreferences",@email,"Mailable","Y","Preferences",@preference,"Preferences","","Last_Modified_Date",@mod_date)
    
       /* set the reason for the unsubscribe */
       set @reason = "Custom Unsubscribe"
    
       /* initiate the LogUnsubEvent request */
       set @lue = CreateObject("ExecuteRequest")
       SetObjectProperty(@lue, "Name", "LogUnsubEvent")
    
       /* configure the properties of the API object */
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "SubscriberKey")
       SetObjectProperty(@lue_prop, "Value", @email)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "EmailAddress")
       SetObjectProperty(@lue_prop, "Value", @email)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "JobID")
       SetObjectProperty(@lue_prop, "Value", @jobid)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "Reason")
       SetObjectProperty(@lue_prop, "Value", @reason)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       /* this is where the unsubscribe is performed */
       set @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId)
    
       /* check status/errors */
       set @Response = Row(@lue_statusCode, 1)
       set @UStatus = Field(@Response, "StatusMessage")
       set @Error = Field(@Response, "ErrorCode")
    
       /* Set the confirmation message */
    
      set @message = "You have been unsubscribed.<br>To resubscribe, click the submit button below.<br><br>"
    
     else endif
    

For the form on the page, you can style it as desired, but within the <form> tags, the below code will need to be included. This code uses an AMPscript loop to display all of the preferences defined in the “Master_PreferenceCenter_PreferencesList” reference data extension. Within the loop, we also utilize a lookup to define whether or not the checkbox should be checked based on the user’s previously defined preferences (if available).

Again, it might be helpful to lookup some of the functions being used in the below script if you aren’t familiar with them quite yet:

  • Process Loops - https://ampscript.guide/process-loops/

  • Lookup - https://ampscript.guide/lookup/

  • LookupRows - https://ampscript.guide/lookuprows/

  • LookupOrderedRows - https://ampscript.guide/lookuporderedrows/

  • Output - https://ampscript.guide/output/

     %%[
    var @preferences, @preference_name, @header, @i, @rows, @row, @checked
    
    /* The @preferences variable pulls in the value of the user's "Preferences" field in the master preference center data extension, if available. The @userlookupcount looks on the master preference center data extension to see if the record exists and sets the @existinguser variable accordingly. */
    
    set @preferences = Lookup("Master_PreferenceCenter_CustomerPreferences","Preferences","Email_Address",@email)
    set @userlookupcount = LookupRows("Master_PreferenceCenter_CustomerPreferences","Email_Address",@email)
    
    if @userlookupcount > 0 then
    set @existinguser = "Y"
    else
    set @existinguser = "N"
    else endif
    
    /* This sets up the loop to bring in all preferences listed in the preferences reference data extension. The @header field defines what the text above the preferences checkbox will be. Set this to whatever you'd like. */
    
    set @rows = LookupOrderedRows("Master_PreferenceCenter_PreferencesList",0, "Priority asc, Preference_Name","Key","X")
    set @header = "<b>Send me emails about:</b><br><br>"
    
    for @i = 1 TO RowCount(@rows) DO
    
    /* If this is the first row pulled, place the @header variable above the checkboxes */
    
     if @i == 1 then 
       output(v(@header))
     else endif
    
     /* Pulls in the preference_name field and applies it to the @preference_name variable */
    
     set @row = Row(@rows,@i)
     set @preference_name = Field(@row, "Preference_Name")
    
    
     /* Sets the checkbox as "checked" on the page if the user has previously opted-in to the preference or if the user has not yet defined their preferences */
    
     if IndexOf(@preferences,@preference_name) > 0 OR @existinguser == "N" then
       set @checked = "checked"
     else 
       set @checked = ""
     endif
    
     /* Sets the checkbox input field that will be displayed. All of the checkboxes will have the same name and will be submitted as one comma-delimited value */
    
     output(concat("<input type='checkbox' name='preference' ",@checked," value='",@preference_name,"'>",@preference_name,"<br>"))
     
     next @i
     ]%%
    

Now your preference center data extensions are created and your page is setup and (hopefully) looking good and functioning as expected. But, how do users access this? Like we mentioned above, we’ve set this up so that users can only access via a link in one of your emails. So, for this, we’ll be using the CloudPagesURL function in a link in our email. When users click on the link, they’ll be taken to our preferences page and they’ll have all of the necessary information (email address, job id) with them. Here is an example link for your emails:

<a href="%%=CloudPagesURL(123)=%%">Update Preferences</a>

The next, and final step is optional and depends on your data setup within Salesforce Marketing Cloud. If you have a customer master data extension and would like to keep this data within your customer master data extension, then simply setup an automation to run hourly. Within the automation have a SQL query that updates your customer master with the values from the master preference center data extension.

And then that’s it, you’re done. Your customers are now able to easily update their preferences, you can easily segment them based on those preferences, and you can even somewhat easily update the preferences you offer customers.

Read more
Matt Brocious
Published 05/22/2020
SSJS
Debugging Server-Side JavaScript

This article assumes some knowledge of Server-Side JavaScript. If you are new to SSJS feel free to dive into Greg Giffords articles that serve as an introduction to the topic.

SFMC Server-Side JavaScript 1: Intro
SFMC Server-Side JavaScript 1.5


If you’ve ever found yourself multiple days into writing Server-Side JavaScript (SSJS), with a few hundred lines of code, and a script that should work… But doesn’t… Just know that we’ve all been there.

I tend to live in the area between total coding elation and utter frustration. In fact… I had arrived at Camp Frustration (that’s right, it’s an actual place), set up a tent, and camped out for the better part of two weeks. And while it was frustrating, it was also extremely rewarding and I learned so much in the process!

I had written a script that aimed at taking a very long and manual process and automating it. Exporting tracking extracts and moving them to the SFTP server for a bulk period of time (one full year at a time).

This script, at every step, interacts with the SFMC APIs to create the activity definitions needed, execute them, then delete them (because they no longer ‘spark joy’ and are no longer needed).

During the process there were moments where I felt like an API super hero, but there were also moments that made me want to toss my computer out of my office window and give up (don’t worry, I didn’t). The most glaring issue was that no matter how many iterations my loop ran, it would only create the first tracking extract; but there were no errors and everything else ran perfect!

I quickly learned the value of being able to debug and troubleshoot things (not just API calls) in SFMC.

Testing is typically done on a cloud page or a code resource page. I’ve done most of my testing from the classic > code view cloud page editor.

My first tip is something I picked up recently from a fellow EmailGeek as I was getting frustrated that the cloud page was running my SSJS code as it rendered the preview:

%%[
if indexof(requestparameter('pageurl'),"CloudPages/Preview") < 1 then
]%%
                                                                
//Your Code                                                                
%%[
endif
]%%

This script captures the page URL and checks if the context of it is a preview. Using some standard AMPscript logic, it tells the page to only load the code you are running on a non-preview page.

When you’re working with the SFMC APIs, you not only need to instruct it what to do, you you also need to tell it to provide you with the details of what it just did. But why do you need to know what it just did? We trust SFMC, right? Well… Kinda… But this serves a few purposes. These Callbacks tell us what the values of the variables we are passing into the API calls are before they are used and what the response from the API call is.

So how do we get the callback and where should we put it?

With SSJS we don’t have the console to display our debugging efforts since everything is run server-side, so we use the Write function in it’s place.

In order to make your callback accessible, your API call needs to be set as a function expression; which means calling your function as variable. This stores the response in that variable so you can display or parse through it.

var createExtract = ...

Once your response is captured by the variable, you can use the Stringify function along with Write to display your response.

Something I’ve learned in this process is to leave yourself context with the details you are displaying.

Write(Stringify(createExtract)); will provide you with the callback but Write('Create extract definition: ' + Stringify(createExtract) + '<br><br>'); will give you what the response of for and some extra space so it doesn’t run into your next callback.

Where the callback details are generated from was something that I didn’t think of until very late in the game; and it would have saved me a lot of time.

When writing SSJS, I like to use functions so my code is reusable. At the onset, I was putting my callbacks directly after I initiated my function

var createExtract = createExtract (...);
Write(Stringify(createExtract));

While this was giving me some insights into what was going on… It wasn’t the best place for it. I was not getting the details closest to where the actual call was being made.

What’s a payload? The payload is simply the data you pass through your API call. It contains all of your information to create/interact with the API object.

This was my Ah Ha! Moment.

I was checking everything after the HTTP.Post call was being made, and I thought that because I was displaying my variables before I initiated my createExtract function I was verifying the correct data was being passed (I was… And I wasn’t).

To truly verify what was being passed into my function I went back to the trusty Stringify/write combination and displayed it before my HTTP.Post in the function.

What did this tell me? By adding this response here, this showed me that after my first loop iteration, the createExtract function was either not initiating or not injecting my variables into the payload (which because it’s a function, should be the same as the first iteration).

The try/catch function is not something native to SSJS but it’s one of the functionality in Vanilla JavaScript that works with it.

As with a lot of other instances, the error messages provided by SFMC tend to leave a lot to be desired. With the try/catch block, you are able to display a more detailed error message for that specific part of your code.

Try {

//Your code

} catch(e) {
   Write(Stringify(e));
};

My initial thought was that it’s not running the function after the first iteration, but it’s holding out the error. So I wrapped my function and settled in to find out what my error was.

Try {

var createExtract = createExtract (...);

} catch(e) {
   Write(Stringify(e));
};

Much to my surprise, the try/catch had a very welcomed but unexpected effect… It fixed the issue! The current theory behind why this worked is that try/catch is local scope only (visit here for more information on scope), so nothing can be cached for the second run. In the event that we are able to get more insight into this issue and why the try/catch worked, I will update this article.


The next time you find yourself waist high in code with no idea why it doesn’t work and you’re ready to throw in the towel (and the laptop too!) remember there are some very useful methods you can use to help troubleshoot and debug your code.

Keep in mind, this is not an exhaustive list of debugging techniques. There are other methods and best practices to keep in mind as you work through writing and troubleshooting your code.

Read more
Tony Zupancic
Published 05/16/2020
Release Notes
May 2020 Release Notes - Summary & thoughts

It’s that time again - we’ve got an upcoming release and as of today (16th May) there’s some interesting elements being included or introduced as part of this bundle of joy. After a couple of the more recent releases that have felt somewhat underwhelming and some that added simple but needed functionality, there’s some promising new features due to hit our instances when the release lands.

New training modules are coming/have arrived and been given some prime real estate in the release notes. There’s 3 new modules and an official Trailmix for someone getting started in SFMC. As with all things SFMC on trailhead, you don’t get a developer environment to play with for these features and it doesn’t look like there’s any simulations for these just yet either, but it’s a worthwhile read and some points if you go hunting for those.

I do have one bug bear with the content of the Marketing Cloud Account Optimization content for auditing your account.

Is there somewhere else besides a Data Extension that I don’t know about for storing data within SFMC? If there is, I’d really like to know because if it’s such a high threat - everyone should probably be working that one out!

Either way - I did the badge because of this article. There’s some useful content there if you’ve never tried to audit or optimize an account. No new audit tools unfortunately. Will I be adding any of these modules to the standard Trailmixes that colleagues who I work with to show them around SFMC? Probably. But probably only the interactive email modules that have been released. Not because I believe Interactive Email in its current guise is going to add value to marketing comms. But, I do believe it is a powerful module to help people understand more of the art of the possible in Email Marketing.

!alt

The new Journeys section appears to be an amalgamation of all of the individual studios in one place. There’s content for Web Studio, Content Builder, Push, LINE, Classic Content and basically everything that used to have it’s own dedicated part of navigation. So if you’re looking in the documentation at the release notes and not finding what you’re hoping for - it’s probably under Journeys.

!alt

  • Selecting Image URLs from Content Builder no longer needs a visit to the properties view so that should save some excess clicks
  • Push notifications built in line within Journey Building bringing some parity between Push and other channels like Email and SMS
  • Push notifications now being available for Single Step Journeys (No, I don’t entirely know why a single step journey is called a Journey. It’s just a batch send with humorously, more steps involved) but it definitely starts to bring Push support more mainstream in SFMC. So fingers crossed the development kits and capabilities are made more accessible and simpler to use for some basic implementations. (If anyone reading this is an avid user of Mobile Push and would be interested in writing an article about it from their perspective - do reach out to us!)

The new features cover a wide spectrum of channels and levels of technical experience required, but in summary it looks like there’s a lot of things happening - but I believe there is less in this release but it appears to be a lot more useful new features.

There is a new Cloud Pages experience that users will be able to trial if they currently have Cloud Pages available. You will be able to swap between the two because the underlying changes are happening to the same place. Definitely worth having an investigation, especially with no real indication about what’s changing. Except for FINALLY having the ability to copy pages like you can with classic content. But even that is a footnote in the release details. Fingers crossed it’s as useful as it sounds for those who have multiple similar cloud pages to deploy or a template to use.

You can now preview Interactive Email Blocks & if you’ve got a lot of data in a complex file structure - you’re no longer limited to 31 folders worth when trying to test an email. So a couple of useful new things to streamline some QA steps, one would hope!

I really don’t know what took so long, but we can finally pause a Journey rather than just have to stop and design a way of bringing people into a stopped journey in the right place or just treat them as half-complete and hope for the best. That said, it’s not a permanent pause it is only for 14 days. So if there’s something happening in a specific instance that means you need to suspend for longer than 14 days, it may still be better to stop and determine a longer term contact strategy. But, 14 days is a long time to be able to work that up so the pause is definitely a welcome addition!

Probably one of the two biggest elements of this release from a Marketing Cloud capabilities perspective is the new Path Optimizer in Journey Builder. A technology requirement that features heavily in a lot of marketing functions is the ability to A/B Test & Winner Pick. But, until this release, Journey Builder hasn’t been able to do that - despite the Sales team trying to convince us otherwise. But, if Path Optimizer works well, this has the potential to go a long way.

Things to be conscious of when using it - Journey Builder has limitations for how many steps to include for it to be performant (150-200 is listed in the documentation). So, if you’re looking to optimise at a segment level - this could lead to a lot of bloat in your journeys impacting performance. Once it’s live and GA, we’ll test it and hope to share some of our experience (if you use it after it goes live and would be interested in sharing your thoughts, it would be great to get some additional views on this! Let us know here)

It’s still being retired. This has been one of the longest retirement tours ever, but it is still being retired.

This is the second biggest announcement from my experience. Given that Contact Count is a billable volume in SFMC, it’s been ludicrous that there has been no straightforward method to manage this or clean out data that has no value. There have been workarounds historically here where the general situation was “if you didn’t pay for Mobile Studio when you signed up, you’re outta luck”. So it’s definitely a step in the right direction. Hopefully the complex days of contact management go out the window and this can supersede the hacky workarounds.

Einstein has been a common theme in the last few releases with a range of new features being added, moved around, renamed etc. but one thing has stayed consistent - Einstein won’t tell you how it works. Which for some data scientists makes it a no go, not understanding how the data is being used or processed and just hoping it works isn’t a part of the scientific method. However, the wording on the Send TIme Optimization release note does add some hope for some visibility _“Marketing Cloud’s Einstein Send Time Optimization model card provides insight into how our artificial intelligence (AI) model works.” _If Einstein will start to tell me how it works, then I think it’ll be an easier recommendation.

The inclusion of the Random Sending Path for STO is a welcome addition, previously an audience would need to be split prior to a Send Time Optimization node making a journey a little clunkier. Looking forward to testing and validating this one!

Important date to keep in mind (31st July 2020) if you’re using the MC Connector - make sure you update to the Connected App authentication as opposed to the legacy authentication. You’ve not got long before your integration will stop working if you don’t do it. You’ll need to block out a couple of hours to get it sorted, but it’s a couple of hours you can choose rather than an integration failure that’ll come bite you on the rear!

This looks to be a promising release with some great new features being included for end users, marketers and developers alike. Top 2 callouts for this set of features is the Journey Path Optimizer and the Contact Cleaning tools being made available. But I’m keen to try the new Cloud Page experience and if you’ve ever wondered how to audit a Marketing Cloud instance - there’s a module to take a look through on Trailhead that covers that in some high level detail.

There are some elements not covered here that I’ll call out specifically as references:

  • AMPScript AttachFile function getting some additional features including: 4 new mime-parts & an optional extra to determine if the default function is to inline or attach the file.
  • Deleting contacts via the API now only needs a ContactKey as opposed to a ContactID and a ContactTypeID so you can get remove contacts in one delete request instead of for each ContactTypeID
  • Removing content from the Content Builder CDN using the API rather than just overwriting it as a workaround
  • Go To Marketing Cloud links removed from the Connected App in Salesforce core platforms for security purposes. You’ll now have to navigate to SFMC manually
  • With the sunsetting of Classic Content - The ability to A/B test from Sales & Service cloud is no longer available
Read more
Jason Cort
Published 05/11/2020
Winning Solutions
Frequency Filtering for Email Subscribers

How often does your marketing team send out communications to a subscriber? Are the subscribers in your database getting too many emails in a week? Does this lead to many unsubscribes and deliverability problems? Are you able to control the emails that are sent to a subscriber for a specific duration? We can help resolve this through the technique of Frequency Filtering.

There are multiple ways you can achieve Frequency Filtering – and the solutions can vary for each organization depending on their processes. In this article, we will look at a simple approach on how to implement Frequency Filtering in your Salesforce Marketing Cloud account using the following steps:

  • Pre-Configuration: Using Profile Attributes
  • Send Data Filtering: Send Log and Filter Activity OR Data Views and Query Activity
  • Subscriber Exclusion: Using Exclusion Script

Now, let’s look at the detailed steps on how to set this up in your instance. For this solution, we will assume that you wish to restrict emails being sent more than once per day to a subscriber – i.e.

  • Frequency Duration = Daily
  • Max email sends allowed for a subscriber for the Frequency Duration = 1

You can tweak these values on the solution as needed – for e.g. some organizations may need the Frequency Duration to be Weekly – i.e. subscribers should receive an email only once a week.

Pre-Configuration

The Pre-Configuration step involves creation of a Profile Attribute to capture the number of maximum Email Sends per duration that should be allowed for a subscriber. You may keep this attribute as hidden if your marketing team would like to control the send frequency. However, if you wish to give the subscribers the option to control the frequency of with which they may receive emails, then this field can be exposed on the Profile Center.

<ol> <li>Go to Email Studio > Email > Subscribers > Profile Management</li> <li>Click on “Create” button to add a new Profile Attribute with the following properties:</li> <ol> <li>General Tab</li> <ol> <li> <b>Name</b>: Max Daily Email Send Count</li>
<li> <b>Hidden</b>: Checked (if to be controlled internally, else can leave it unchecked)</li> </ol>
<li>Data Tab</li> <ol> <li><b>Data Type</b>: Numeric</li> <li><b>Default Value</b>: 1 (you may change this default value as decided by your marketing team’s process – if you leave it at 1, then this means the send to this subscriber will be restricted to once per frequency duration)</li> <li><b>Max Length</b>: 3 (you may even keep this as 1 if you are sure you won’t need to send more than 9 times to a subscriber during a frequency duration)</li> </ol> </ol> </ol>

!screenshot of second tab on modal

Once the MaxDailyEmailSendCnt profile attribute has been saved, we can move to the next step – which is where we filter the Email Send data based on the desired Frequency Duration.

Send Data Filtering

Option 1: SendLog and Filter Activity

In the case that your organization is using Send Logging, then you may use the SendLog Data Extension to retrieve the Email Send information.

<ol start=“1”> <li>If you do not have <b>Send Logging</b> enabled, then go ahead and create the <b>SendLog Data Extension</b>.</li> <ol> <li>Go to “Contact Builder” > “Data Extensions”, create a new “Standard Data Extension”</li> <li>Select “Create” from Template option – and choose the SendLog template as shown below</li> <li>Save the Data Extension – it will show several pre-populated fields as part of the template</li> </ol>

<img src=“https://lh5.googleusercontent.com/lb-SxCMBlk1wTtl69TK04KkHosXY03jV6gUwgnX3_USIXSvBDroF6xLQK-poZtP7ghMxIfyX8P2L5vDt5u3sVjRiwxqbkOYYqoHjZ-NR1RjxzO01gDZRV2hUw8i-YOWiOJJqIO5ZhsTfG0y0mg” width=“100%” class=“py-5”/>

<li>Now add 2 more custom fields to the SendLog DE (if you have an existing SendLog DE, do check and add if these fields do not exist):</li> <ol> <li>Email Address – (type = EmailAddress) – this should match with the Profile Attribute “Email Address” on the Subscriber so that the email id of the subscriber gets populated in the SendLog DE during send time.</li> <li>Send Date – (type = Date, Default Value = Current Date) – this will get populated with the date on which the send happens for each record being entered into the SendLog</li> </ol> </ol> <img src=“https://res.cloudinary.com/howtosfmc/image/upload/v1588385953/shibu_de_insert_wnz0hs.png” alttext=“DE Insert screenshot” width=“100%” class=“py-5”/>

The SendLog DE would now store all Email Send details for the Account / Business Unit (depends on how it has been previously configured). We can filter this Email Send Data for a specific duration and then use the same for the final Exclusion step.

<ol start=“3”> <li>Create a Filter to segment subscribers from the SendLog DE with email id who were sent one or more emails for current day </li> <ol> <li>Go to Email Studio > Subscribers > Data Filters </li> <li>Create a new Filter with following properties:</li> <ol> <li><b>Name</b>: SubscribersSentEmailToday</li> <li><b>Source</b>: SendLog Data Extension</li> <li><b>Filter Criteria</b>: SendDate is equal to Today</li> </ol> </ol> </ol>

This Filter would segment and provide all subscribers in the SendLog DE that has a SendDate value of the current date – which means, all subscribers who were sent emails today.

!

Now if you want a different Frequency Duration – let’s say 7 days instead of daily, then all you need to do is change the Filter criteria in this step accordingly. For e.g. you can check for the “SendDate to be after Today – 7 Days”.

Sometimes, you may have a business scenario where you do not want Triggered Sends to be counted towards the Frequency Filtering as it may be used for sending transactional emails. In this case, you can add the Filter criteria to include only the records that have an empty TriggeredSendID. The resulting records in the Filtered Data Extension would not have any subscribers that were the recipients of a Triggered Send – however if they also did receive a non-Triggered Send email, then their record would populate in the data extension.

!

<ol start=“4”> <li>The third step would be to create a Filter Activity and schedule this through an Automation Workflow</li> <ol> <li>Go to “Journey Builder” > then go to “Automation Studio” > Activities > Filter</li> <li>Create a new “Filter Activity” with the following properties:</li> <ol> <li><b>Name</b>: Refresh Todays Email Sends Info</li> <li><b>Filter Definition</b>: SubscribersSentEmailToday (select this Filter that we created in step 3)</li> <li><b>Target Data Extension Name</b>: TodaysSendDE</li> </ol> <li>Save the Filter Activity</li> <li>Go to the Overview Tab and click on New Automation</li> <li>For Starting Source, pull in Schedule</li> <ol> <li>Configure the Schedule based on how often you wish to run this Filter Activity Refresh</li> </ol> <li>Under Step 1 of the workflow – pull in Filter Activity – and select “Refresh Todays Email Sends Info” that we created in 4b</li> <li>Save the Automation Workflow – you may run this manually using the Run Once option or schedule it to run every few hours or daily depending on your requirement.</li> </ol> </ol>

!

Please do note that a Filter Activity overwrites the data in the target Data Extension (in our case TodaysSendDE) – so as long as the Filter criteria is met, the data will be populated in the target DE. You will need to choose the appropriate times when this Activity should run daily (probably in between multiple Email Sends that you have) in order to have the updated data available for Exclusion.

<ol> <li>Once the Automation has run, go to Contact Builder > Data Extensions</li> <li>Select the TodaysSendDE – verify that the appropriate Send records have been populated based on the criteria in our Filter</li> </ol>

Option 2: Data views and Query Activity

In case your organization is not using Send Logging, or you are unable to get all Email Send info in the SendLog DE, then we have another option to retrieve the Subscriber Send information. This time, we will use the System DataViews and a Query Activity.

<ol> <li>As a first step, we need to create the target Data Extension that we would be using for the Exclusion step</li> <ol> <li>Go to Contact Builder > Data Extensions</li> <li>Create a new Data Extension</li> <ol> <li>Name:TodaysSendDE</li> <li>Data Retention: Keep an appropriate retention policy (we will use the Overwrite option from the Query Activity so that old records will be removed automatically during the Query update)</li> <li>Leave Sendable as un-checked as we will not be using this Data Extension for Email Sends</li> <li>Create the fields in the DE as shown below and save</li> </ol> </ol> </ol>

!

<ol start=“2”> <li>Once the Data Extension is ready, let’s build the Query Activity to fetch the Email Send Data.</li> <ol>
<li>Go to Journey Builder > Automation Studio > Activities</li> <li>Create a new Query Activity with following properties:</li> <ol> <li>Name:Retrieve Todays Email Send Data</li> <li>Target Data Extension:TodaysSendDE (that we created in Step 1)</li> <li>SQL Query: As shown in the code snippet below</li> </ol> </ol> </ol>

!

!

<ol start=“3”> <li>Save the Query Activity and now let’s use this in an Automation Workflow.</li> <ol> <li>Go to the “Overview Tab” and click on “New Automation”.</li> <li>For “Starting Source”, pull in “Schedule”</li> <li>Configure the Schedule based on how often you wish to run this Query Activity</li> <li>Under Step 1 of the workflow – pull in <b>Query Activity</b> – and select “Retrieve Todays Email Send Data” that we created in 2b.</li> <li>Save the <b>Automation Workflow</b> – you may run this manually using the Run Once option or schedule it to run every few hours or daily depending on your requirement.</li> </ol> </ol>

!

<ol start=“4”> <li>Once the Automation has run, go to Contact Builder > Data Extensions</li> <ol> <li>Select the TodaysSendDE – verify that the appropriate Send records have been populated based on the criteria in our Filter</li> </ol> </ol>

Subscriber Exclusion

Now that we have the Email Send information for the desired Frequency Duration in the TodaysSendDE Data Extension, let’s see how we can use this for excluding subscribers from being sent emails beyond the limit that we have set in the Profile Attributes (as part of Pre-Configuration).

  1. For excluding Subscribers during each Email Send, we can use an Exclusion Script that checks the number of records a Subscriber has in the TodaysSendDE and then compares it with the value of that Subscriber’s profile attributeMaxDailyEmailSendCnt.

In the desired Email Send definition(s), under Target Audience, include the Exclusion Script as shown below:

!Row Count and subscriber exclusion.

This Exclusion Script uses AMPscript functions – if the condition in an Exclusion Script evaluates to True, then the Subscriber is excluded from the current Send. The script is evaluated for each individual Subscriber that is in the Target Data Extension of the Send.

Script Demystified

The Exclusion Script uses the following functions:

  1. LookupRows – This AMPscript function returns a set of rows from a Data Extension based on a lookup Column and lookup Value. In the above solution, we use the Subscribers emailaddr value (which is a system attribute – hence not in double quotes) and check it with all the values in the “Email Address” column of the TodaysSendDE Data Extension – and then return the rows that have a match.
  2. RowCount – This AMPscript function returns the count of rows of a Rowset – when used with the LookupRows function, we can identify the number of rows returned from the Data Extension that satisfies the lookup condition. In our solution, this should give us the total number of records the current Subscriber has in the TodaysSendDE.
  3. AttributeValue – This AMPscript function returns the value of an attribute based on the context of the Subscriber. In our solution, we use this function to return the value of the MaxDailyEmailSendCntprofile attribute for the current Subscriber.

In summary, the Exclusion Script checks for the number of rows a Subscriber has in the TodaysSendDE (which has the list of all subscribers that was sent emails in the desired Frequency Duration – e.g. Daily, Weekly, etc.) and if this is greater than the value that was set in the Profile AttributeMaxDailyEmailSendCntto control Send Frequency, then the condition will return true and the Subscriber will be excluded from the Send.

Let’s say we have a Subscriber A that hasMaxDailyEmailSendCnt = 1and was already sent an email today. If we are going with a Daily Frequency Duration, then we can run the automation workflow and have his data added to the TodaysSendDE. Subsequently, if the Exclusion Script is added for the next Send today, then the condition would return TRUE as (1 >= 1) for this Subscriber A – and hence he would be excluded for this and all future sends today.


Conclusion

We have now seen how we can set up a Frequency Filtering mechanism in our Salesforce Marketing Cloud account. In order to change the limit for the Frequency Duration, teams can just change the profile attribute value. In the above solution, we have used Email Address for the lookup condition – this means that if we have multiple subscribers with the same email id (for e.g. say members of a household), then all of them would be restricted from the subsequent sends based on Frequency Filtering.

In case you wish to use the Subscriber Key instead of the Email Address, then you can use the _subscriberkey system attribute in the Exclusion Script as shown below:

!Attribute Value with Row Count visualization of code.

Overall, a marketing team or the ones formulating the strategy whether they are internal resources or agency partners must take into account the number of records in the SendLog or the DataView Query before implementing this solution.

In scenarios where the size of the audience is too large and it may not be feasible to run Refresh and query activities multiple times, then this solution may not be feasible. This is because the effort involved for larger projects may be too complex at times that leaves it open to human error without sufficient time for quality assurance to occur.

Read more
Shibu Abraham
Published 05/05/2020
Tutorials
SFMC Server-Side Javascript 1.5

As a warning, this article assumes certain knowledge. _ This link _ should provide you with any background or refresher information if needed. This is the second article (but only half step forward) in an ongoing series, please visit _ here _ to read the first article. This article is intended to give some background on what Server-side JavaScript is and basics to help make the next article make more sense.


JavaScript is a cross-platform, object-oriented scripting language. An Object Oriented Programming (OOP) language is defined if it can provide the following four basic capabilities to developers: Encapsulation, Aggregation, Inheritance and Polymorphism. JavaScript is designed for embedding in other products and applications – such as web browsers. It is heavily intertwined with HTML and CSS, but focuses mostly on creating dynamic and interactive elements. JavaScript contains a core set of objects:

These objects are the base objects for JavaScript to operate and interact with the environment. There are other ‘built-in’ objects that are available, but there are too many to effectively list out here. Here is a good link to check out a list. Please note that some of these are not available sever-side. JavaScript allows you to implement complex features utilizing:

  • Storing information in variables
  • Operations and evaluations
  • Event based scripts
  • Application Programming Interfaces (APIs) JavaScript contains two different contexts – server-side and client-side. Vanilla JS is shared between the two contexts, but most have their own libraries/environments specific to the context. Client-side code is code that is run on the user’s computer — when a web page is viewed, the page’s client-side code is downloaded, then run and displayed by the browser. It is mostly for displaying and manipulation of HTML in a windowed environment. This environment is run solely in the browser or ‘local user’ environment and is dependent on the browser/software that the user has. This means that all of your JavaScript is then fully exposed for the user to see via source code. Any information you use in your scripts, such as user/pass, will be able to be viewed by any visitor of the site – opening up security risks. Server-side code in contrast, is designed to work with resources outside the windowed environment – utilizing the ‘home’ server environment. It is able to interact with relational databases, API calls to other systems, or manipulate objects on the server prior to response rendering. Generally, server-side Javascript is run in a specific environment (like Node.js), which is run differently than vanilla JavaScript. The server script is not passed along to the requester, only the results of this scripting. So you can utilize ‘sensitive’ information in here (with appropriate security measures) without worrying that the end user will be able to see it.

Server-side JavaScript contains the same core language as the client-side version, but it does not include DOM (Document Object Model) or BOM (Browser Object Model) capabilities.

Overview of Runtime processing: (for example, a web page)

  1. User accesses the URL via Browser. The browser sends request to server for page
  2. The server finds the appropriate file and begins processing
  3. The runtime engine constructs the HTML page that will be sent over
    1. It first begins with those that are set to run at server
    2. It then compiles the results of this along with the remaining HTML/CSS/JS to be shared
  4. The runtime engine sends the new, compiled HTML page to the client
  5. The web browser then interprets all client-side JS that was in the compiled page, formats the HTML and then displays the page As you can see from above, the SSJS is run before the HTML page is compiled fully and sent to the browser. Essentially what it does is scan the page from top to bottom and each time it sees a runat=server it will then process and output that script instead of moving it across as content.

It then continues on with the scan until it reaches the end of the file. At this point it will have the ‘final’ rendered page that it sends back to the browser where then all the client-side scripting is run and processed.

Hopefully soon I will release the next full step forward to the basics of utilizing SFMC Server-side JavaScript.

Read more
Greg Gifford
Published 05/03/2020
Tutorials
Introduction to making API calls to SFMC from POSTman

API calls are one of the most powerful tools available to an SFMC user. It essentially allows you to make changes, perform actions, retrieve data and more all programatically via an outside resource.

Below I will give a quick overview on setting up an API integration package and then utilizing it in POSTman. This includes, creating the package, the component and gathering the required information from Salesforce Marketing Cloud down to creating the environment and setting up an authentication call and test calls inside of POSTman.

First you will need to go into the ‘Setup’ section which is under your user name in the top right of the screen. You will need to hover over it and the dropdown should have one of the first options being ‘Setup’.

From there you will want to navigate on the left sidebar to ‘Platform Tools’ and then expand ‘Apps’ and select ‘Installed Packages’.

!SFMC Packages

Click on ‘New’ in the top right. This will open a popup asking for name/description of your new package.

!SFMC Package Details

You then want to hit save to create your package.

Next part is the creation of the component inside the package to allow utilization of API integration between your external system and SFMC.

After building the package, this will then open up a screen with your new Package overview. The part we want to concentrate on is the bottom that involves ‘Components’. You will want to click on ‘Add Component’.

!

This will open a new popup to select the component type. For the purposes of this article, you will want to select ‘API Integration’. Then Click Next at the bottom.

!SFMC Component

Next you need to select your integration type. For this article, I am going to choose ‘Server-to-Server’ as this is only going to be used via POSTman or similar services. For more information on Component Types, see official documentation here.

!SFMC Integration Type

You will then need to assign the scope of your package (see official documents on scope here. For this article, I am going to allow full permissions on my package – basically allowing full access to the whole platform. After testing, you should make sure to go back and assign scope on your component as appropriate for your intended usage.

!SFMC Component Scope

You then hit save and the new component will be added to your package.

!SFMC Completed Component

You then will want to grab the following fields for use in your API call:

  1. Client Id
  2. Client Secret
  3. Authentication Base URI
  4. REST Base URI
  5. SOAP Base URI
    (make sure to append /Services.asmx to the returned endpoint)

These will be used inside your POSTman API calls.

EDIT I wanted to give a quick thank you to Vishal Kumar C V and Renato Oliveira for bringing up that I did not include the append needed to be added to the SOAP Base URI (/Services.asmx) for the SOAP API to work. See more details inside of this Stack Exchange post.

Next we will work on setting up an Environment in POSTman to best utilize your new Package in SFMC.

In POSTman you will want to navigate to the top right corner and select the gear icon to ‘Manage Environments’

!POSTman Environment Setup

You will then want to click the ‘Add’ button at the bottom to open the following window:

!POSTman add environment

This is where you will add in the endpoints and id/secret information to your environment. What I would also recommend is adding a field named ‘accessToken’ that you can use to hold your returned Token from your Auth call. You do not need to insert a value into this field

Sample completed Environment below:

!POSTman completed environment

You will then want to click the ‘Add’ button at the bottom. This will then add your environment to your POSTman.

Now that you have your environment set up, you need to aim at retrieveing your token so that you can do your REST/SOAP API calls. First things first, verify that your environment is selected in the dropdown in the top right of your POSTman.

Below is a quick overview of the call, that you can use if you have familiarity with POSTman to set up your call:

POST /v2/token 
Host: {{authEndpoint}}
Content-Type: application/json

{
"grant_type": "client_credentials",
"client_id": "{{clientId}}",
"client_secret": "{{clientSecret}}",
"account_id": "{{mid}}"
}

For the visual learners, use the below images to help figure out where to put this information:

!POSTman Auth Call Headers

!POSTman Auth Call Body

You will notice in my call, I have included a parameter account_id with a replacement named {{mid}}. This can be used to switch between BUs by assigning an MID to your environment and filling it in here. Please note that this can be included with mid being empty or null and it will still function as expected.

Another quick note to help auto-store your authentication is to utilize a script that can be added in the ‘Tests’ section in your Auth call. See below image and snippet:

!POSTman Auth Call JS Script

var data = JSON.parse(responseBody);
postman.setEnvironmentVariable("accessToken", data.access_token);

By adding this JS in, on each call of this, it will update the ‘accessToken’ var in your environment with the returned token in your Response.

After setting all of this up, you will want to click the ‘Send’ button in the top right, near where you set your environment.

Example Response:

!POSTman Auth Call Response

To test and make sure your environment and access token are set up properly, I am going to provide a very simple test API call. This call is basically a discovery call to gather all endpoints under a specific section. For this sample, we will use the ‘contacts’ section.

See below extract and image for setting up this call:

GET /contacts/v1/rest
Host: {{yourSubdomain}}.rest.marketingcloudapis.com
Authorization: Bearer {{yourToken}}
Content-Type: application/json

!POSTman Discovery Call

You then want to hit send in the top right. This should then return something like the below in the Response section:

!POSTman Discovery call response

Congratulations! You have successfully completed a REST API call to your SFMC BU.

For this sample, I am going to do a quick example Retrieve on a Subscriber via SOAP. There are certain differences that need to be noted in SOAP that are not in REST.

Here is a down and dirty list:

  1. Method will always be POST
  2. Requires a header key of ‘SoapAction’ – value will mimic the request type. E.g. RetrieveRequest will be SoapAction = Retrieve
  3. Authentication is inside of the payload (body) of the call, not in the header.
  4. SOAP utilizes XML for the payload, unlike REST which uses JSON
  5. The endpoint that SOAP aims at is universal for all calls, the payload/body determines content and action
  6. SOAP has Methods (declared inside of payload/body) as well as Objects that are to be utilized to determine action and result.
  7. SOAP requires explicit declaration of Properties in retrieve calls

Below is an extract of the SOAP call on retrieving a subscriber:

POST /Service.asmx 
Host: {{mySubDomain}}.soap.marketingcloudapis.com
Content-Type: text/xml
SoapAction: Retrieve

<?xml version="1.0" encoding="UTF-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <soapenv:Header>
    <fueloauth>{{authToken}}</fueloauth>
  </soapenv:Header>
   <soapenv:Body>
      <RetrieveRequestMsg xmlns="http://exacttarget.com/wsdl/partnerAPI">
         <RetrieveRequest>
            <ObjectType>Subscriber</ObjectType>
            <Properties>SubscriberKey</Properties>
            <Properties>EmailAddress</Properties>
            <Properties>Status</Properties>
            <Filter xmlns:q1="http://exacttarget.com/wsdl/partnerAPI" xsi:type="q1:SimpleFilterPart">
               <q1:Property>SubscriberKey</q1:Property>
               <q1:SimpleOperator>equals</q1:SimpleOperator>
               <q1:Value>{{mySubkey}}</q1:Value>
            </Filter>
         </RetrieveRequest>
      </RetrieveRequestMsg>
   </soapenv:Body>
</soapenv:Envelope>

Below are a few Screenshots to help provide further context:

!POSTman SOAP Headers

!POSTman SOAP Body

This should then return a SOAP envelope with the requested fields shown above inside of it. See below image showing a response to the above call:

!POSTman SOAP Response

Congratulations! You have now successfully created, implemented and performed an API call to SFMC!

For future API calls, you can check out this POSTman library of some sample SOAP and REST API calls to get you started. I believe they utilize hostEndPoint instead of endPoint in their environment variables. You may consider adjusting your Environment to fit those parameters if you utilize that library.

Read more
Greg Gifford
Published 04/29/2020
Tutorials
SFMC Server-Side JavaScript 1: Intro

Marketing Cloud Server-side JavaScript is one of those topics that most people do not like talking about. Most people seem to acknowledge it exists, and then move on to another topic as fast as possible. Why? It is a very powerful language and has a plethora of uses, why does it seem to be swept under the rug?

My theory is that the stigma on SSJS is the same as the one around Email HTML. It is not regular HTML, it instead is almost its own little language that looks like HTML. It has different rules, requirements and guidelines/best practices than web HTML and has no standards or regulations.

No one liked talking about Email HTML until enough industry experts ‘blazed the trail’ and produced ‘how tos’ and resources to get people up to speed and comfortable with it. (HUGE shout-out to all those Email-Geek heroes that made email marketing what it is today)

This is where I think SSJS for Marketing Cloud needs help. No one wants to talk about it because no one wants to accidentally show ignorance or get embarrassed by passing incorrect information. I think this stems from there being no real definitive reference or tutorial, like AMPscript.guide for AMPscript. Plus, there are a lot of people that incorrectly snub SSJS saying that AMPscript is better in all areas and it makes SSJS obsolete. These people need to learn how wrong they are – each language is very powerful and shines in different situations or use-cases.

Sure there are the official documents inside of SFMC help docs, which do contain a ton of awesome information and tips and tricks. But they are not the easiest thing to read and honestly have an expectation of knowledge in certain areas that the majority of people do not have – creating confusion. Sadly, there are also some samples or references that simply are not true, incomplete, are missing reference information or just do not work like explained, which causes even more confusion.

This means that a lot of people may no longer use those docs as reference because all it does is make things ‘worse’ for them. They instead will go to places like SF Stack Exchange, Email-Geeks channels, blogs or the Success Community/Groups to research or ask their questions on specific tasks or needs.

A lot of these places and resources are super helpful, but they also provide only specific information on a specific use case. Getting the answer does not mean that you have learned the solution. This means that it is not a good reference place to learn the language or to further your knowledge on it as there will be gaps and misunderstandings.

Learning vanilla Client-side Javascript is certainly helpful, but same as learning HTML 5 in order to learn Email HTML – it can lead to confusion as certain functions/capabilities and best practices for Client-side are simply not possible in SSJS. Of course, to make things worse, there are also things in SSJS that are not possible in client JS as well.

So how the heck DO we learn SSJS for Marketing cloud? Honestly, right now its just through a ton of trial and error. You need to research piecemeal in a bunch of places, scouring documentations to try and find options that ‘may work’. Then pray you can implement or adjust it accordingly to work in Marketing Cloud. Basically, in order to advance in SSJS right now, you need to constantly mess up and fail. Which can be a great learning method (e.g. ‘Trial by fire’), but it is very inefficient, frustrating and time consuming.

Far from optimal for sure. My goal here is to put forth a couple articles to help begin this conversation and in the future put SSJS on near equal footing to AMPscript conversations.

In a nutshell, SSJS is an older version of vanilla JS that is processed on the server instead of on the client-side computer. Due to it processing on Marketing Clouds server instead of client-side, it does not work with the DOM nor does it function with external libraries.

SSJS is mostly used inside of landing pages/Cloudpages, but it does have uses inside of messaging as well as in Automation Studio Script Activities – which, without some ‘fancy footwork’, cannot use AMPscript at all.

Most of the functions/Capabilities that are native to JS (and not browser/DOM dependent) are allowed inside SSJS. Things such as:

SSJS is separated into two ‘sections’. You have the Platform class and the Core Library. I know we just stated that external libraries do not work with SSJS, but Core is a library that is created by SF and hosted on the SF servers, so it is an exception to this rule. The rule is intended to apply to ‘user-defined’ external libraries.

In a future article, I will go over Platform and Core in more detail, but below is a quick synopsis on them:

Platform SSJS: Platform is very closely related to AMPscript, containing many of the same functions and uses. There are a few differences in syntax and capabilities (Cough Cough WSProxy Cough) that can help decide which language is better for your use-case, but that will need to be in the next article.

Core SSJS: Core is, in a nutshell, SSJS functions that are built to do SOAP API calls. Core mostly can only be used inside of a Cloudpage or landing page environments and not in messaging. Core opens the gate to be able to interact with SOAP objects ‘behind the scenes’ inside Marketing cloud through a simpler and more familiar method (to those that already have background in JS).

Hopefully this was a helpful introduction into SSJS and helps pave the way for my future articles. My next one will be a deeper dive into the ‘basics’ of SFMC SSJS and syntax. Then my next focus will be on Platform and Core and their usages (including where WSProxy fits in). Let me know anything you are interested in around SSJS and I will be happy to try and work it in.

Until next time, keep safe and healthy!

Read more
Greg Gifford
Published 04/10/2020
Winning Solutions
Building Reusable Email Templates

Emails need to look good and email developers need to make sure they look good on all devices and browsers, however creating a custom email layout for every campaign send can be an incredibly resource consuming task. Copying emails or reusing elements from previous sends can save some time, however it’s still a laborious task.

Designing email templates to be modular and reusable is an effective way to increase speed to market without compromising on email design choices.

Disassembling an Email - Template, Layouts and Content Modules

!Modular Layout

When building a reusable email template, it helps to visualize the design areas as layers that stack on top of each other - like a cake!

Our email cake will have 3 layers:

  • Template: The foundation layer that holds the key HTML & CSS structure to support the other 2 layers.
  • Layouts: The middle layer of the email that uses style classes built in the Template to change how the email looks on different devices.
  • Modules: The top layer and final touches of content, containing pre-formatted HTML, text and images; examples such as a Header, Call-Out, Product Banner, etc.

Just like a real cake, your Template “base” will rarely change, however you may have a few different sizes or styles to suit different use cases. The Layout “middle layer” is where the email/cake begins to take shape. The Module layer is the icing on top, here you have full creative control and can make the final product look exactly as needed.

Now that we’ve covered the basics, let’s look more closely at each of these layers.

Template - The Email Base Plate

!Desktop Squish Stack Hide for Email Templates

The template is the bottom layer of your email and contains all the key HTML tags to support your email. This includes the <head>, <style> and <body> tags.

The Stylesheet in your template needs to accommodate anything your email end-users could want to build, so a comprehensive list of style classes is recommended to ensure no further customization is needed. Make sure these styles are designed inline with your practical use cases, as redundant style classes (stylesheet bloat) can have an impact on your viewable email length in some email clients.

Layout Framework - Stack, Squish and Hide

Looking critically at the most common email designs, there are only a handful of unique layout configurations that affect how a responsive email displays. Here are the 3 layout methods that I use to fulfill my design needs:

  • Squish: Elements remain side-by-side and get narrower on mobile.
  • Stack: Elements can move from side-by-side on desktop to “stacked” on mobile.
  • Hide: Elements can toggle hide/show between desktop and mobile.

Here is a sample StyleSheet that can achieve these functions:

<style name="responsive" type="text/css">
.responsive-show { 
	display: none !important; 
}

@media screen and (max-width: 500px) {
    .responsive-50 {
        width: 50% !important; 
        height: auto !important;
    }
    .responsive-100 {
        width: 100% !important; 
        height: auto !important;
    }
    .responsive-100-block {
        width: 100% !important; 
        height: auto !important; 
        display: block !important; 
        float: left !important;
    }
    .responsive-hide { 
        display: none !important; 
    }
    .responsive-show { 
            display: block !important; 
    }
}
</style>

Code Explanation

The most important element in this stylesheet is the @media declaration. This element is stating that the styles contained within will only activate when the user’s screen is under 500px wide.

Within the @media statement there are a series of styles that produce different responsive layouts. To achieve the “squish” layout, we can use the styles contained in“.responsive-50” and “.responsive-100”. These styles force their element to become 50% or 100% width of the available space, allowing elements to share the same row.

The “stack” layout is achieved with the “.responsive-100-block” style, as it contains instructions to push any following elements below it, denying elements from sharing the same row.
The “hide” layout can be achieved using “.responsive-hide”. This style uses the display attribute to hide elements in mobile view. The reverse can be achieved with “.responsive-show”, which in the stylesheet above will only make an element visible when viewed on a screen width less than 500px.

Take a look at the attached HTML file for an example of how these layouts change between desktop and mobile screen resolutions.

These 3 responsive behaviors can be applied to single and multi-column layouts to create numerous configurations. You can have a 4 column layout on desktop that squish and stack into a 2x2 configuration on mobile, or a 3 column layout on desktop that stacks and hides the middle column when viewed on mobile.

Understanding the kinetic possibilities of a responsive email template is an important step in creating a framework that allows for high reusability. Learning how these styles work can open up new opportunities for unique email features and designs; such as interactive menus, image carousels and embedded email forms!

Responsive Content Modules - Designing for Layouts

With the structure of the email covered, we can now look at the content blocks or modules. Looking at most marketing emails, it’s common practice for businesses to use the same product or promotion layouts week after week; after all there’s a limited number of ways to display a product in a clear and concise manner. Sometimes this “product module” will be in a single column format 600px wide; other times it might be in a 3-column format at 200px wide each. In both examples above the content is the same, it’s just scaled differently based on the layout it sits within.

When designing content modules, it’s important to think about how they could reasonably be used by the email team, and ensure the CSS and table-containers allow for every use case. Creating a small library of pre-formatted content blocks that are suitable in 1|2|3-column layouts give your email team the flexibility to dynamically create unique emails on the fly.

For example, a brief may require 5 key products be shown in the middle of the email. The end user can drag in the 1x2-coland 1x3-collayouts, one after the other, and use the “Image-Header-Price” module for all 5 products. The standardized approach also means the artworks team doesn’t need to resize product images uniquely for every email - they can keep to a standard set of tile sizes.

This design process also extends to headers and footers too - creating a standardized yet customizable asset pool to choose from. Other supporting elements such as Call-to-Action (CTA) buttons, graphical page dividers or full-width promo banners can also be designed to add more variety to the module library.

How to build your own reusable library

Below is a copy of a reusable email template with some sample layouts to show how the CSS classes work. You can use this as a starting framework to develop your own suite of responsive modules.

GitHub Link

Read more
Cam Robert
Published 02/26/2020
Career
Appreciate Everything. Sweat Profusely. Regret Nothing.

Have you ever taken a second in your busy work day to look in the mirror, take a nice deep breath, and then with great conviction say…”I’ve made a huge mistake.”?

All of you? Yes? Even the biggest, baddest, most perfect person you can think of has wanted to vomit (or more likely HAS vomited) from nerves at some point in their life. This could be from an unplanned project getting dropped on their lap or even having to dump everything they just did and start from scratch. This is a part of learning and growing. You need to ‘embrace the suck’ and push yourself beyond your comfort zone.

Is this scary? Absolutely! But, it also is the best way for you to better yourself, to become successful and to make it seem less scary the next time. We at HowToSFMC thought that, appropriately (as many of us have vomited many times while making this website), we would make our first article about pushing through these terrible feelings and doing what you should, not what makes you feel safe.


Below are a few words of wisdom from each of our founders:

Genna - The old cliche is true, there really are no dumb questions. Seriously, everyone says it, but it’s true. There are no stupid questions, just people too scared to ask because others might think less of them. Most people are more concerned about themselves (possibly also having panic attacks, while writing… ) and aren’t paying attention to you. So don’t worry about losing face, ask.

It’s through mistakes that we learn, see new perspectives and innovate. Being human means feeling dumb – a lot! It’s how you rebound from those experiences that really matter. Experiences that are difficult often have the best outcomes so take challenges when you are presented with them, even if they terrify you. And never ever be afraid to ask for help, or to join a team or community. No one can do it all so you have to look for those who have complementary skills and similar values to build your community with.

It’s okay to talk about your accomplishments, even if you think it sounds braggy. Being humble can be detrimental when taken to the extreme. So, self-promotion is not a bad thing when done in moderation. You have to learn to promote yourself at the right time in order to get ahead in life.

You never know, you may suffer from impostor syndrome at work and then have a complete belief that you are the greatest home brewer ever while introducing your taste-testing friends to Captain Trips. No one wins them all. And if you’re a plant lover like myself, then no one keeps them all alive either. You’re going to kill something at some point, accept it, move forward.

Jason - For every one question someone thinks could be dumb, there’s likely an actually dumb piece of documentation to make the question need asking. Which unfortunately is only something you find when you need to know an answer quickly. (Looking at you, User Permissions documentation).

Let’s face it, in the early days of most of our learning experiences, documentation and guides are the primary sources of knowledge. Which is great if you want to follow a path laid out for you like Waze and you know what your A to B is. But, the more useful skill to learn is how to drive the car. Then it doesn’t matter if you know where A and B is or if you need to stop off on the way and do something else - You can still get there.

The number 1 question that I wish I’d asked on day 1 was “I created a filtered Data Extension, how do I make it refresh/update automatically?”. What I should have done is asked that question before I’d made about 40 of them that didn’t refresh… I then asked it later and found that I’d done them all wrong. But alas, I learned the most useful lesson that day - Ask the question first before spending a few days possibly doing it wrong.

The second most useful lesson I’ve learned is “Someone already invented the wheel, unless you believe you’ve got a better concept - why reinvent it?” There are some fantastic people in the world of SFMC and it would be remiss to not reference the Isaac Newton quote “If I have seen further it is by standing on the shoulders of Giants.” The best thing about the Giants in SFMC, is that lots of them make their shoulders available for you to stand on.

If there’s anything to take away from this - Ask a question. As children we’re often taught to politely ask questions, we’re raised to be inquisitive. Let that childish inquisition come back. Make mistakes, but only make the same mistake once, then ask how to avoid it if your inquisitive experiment didn’t go according to plan.

Greg - Sometimes the people that are viewed as the ‘stupidest’ are actually the smartest ones in the room. They realize that how others perceive them is not the real definition of their intelligence. By suppressing your question to ‘save face’ you limit the knowledge you are able to gain. Is embarrassment worth the gaining of knowledge? HECK YEAH. In 5 minutes that embarrassment disappears, but that knowledge stays with you forever.

Don’t be afraid to fail. Sometimes you aren’t the best. It really sucks when it happens, but unfortunately you are not an omnipotent God. Failing is important though as you then know exactly what NOT to do next time. As good ol’ T. Edison used to say “I have not failed. I’ve just found 10,000 ways that won’t work.”

Even though failing sucks, you should always try new things and explore. That being said though, you need to do this smartly. Don’t dive into a project that you literally have never done before and promise the moon. That is setting up yourself and your team for failure. But, if you are open and honest and put forth the extra effort to fill in all your knowledge gaps, then you are much more likely to succeed regardless of skill level.

Tony - Every time I ask a peer in the email industry about their background I’ve heard very similar stories: “I kinda just fell into it…” “They needed someone to run their email program so that’s what I did…”

My story is the same; I started out in print design and then magically transitioned to email design. When I started, I knew basic HTML, a little CSS and not much else. So how do you learn? I quickly realized, the only way to learn is to ask as many questions as you can.

However, learning isn’t just about asking the question, it requires asking about the why, the how, and the theory behind the answer - not just blindly accepting it. There is no doubt that jumping into a new community and asking questions is daunting and overwhelming, but the benefits far outweigh any embarrassment.

So you know that question you haven’t asked yet? Ask it!

Aysha - You know that problem you’ve been working on all day? That one question you want to ask, but think if you do you’ll look like a chump because it’s a ‘stupid’ question? Just ask it already! It’s important to realize that looking stupid for a minute is the only way you can learn and continue to get smarter.

We, in the email industry, are quite lucky. We are fortunate to have a fairly substantial community of peers. With so many knowledgeable, innovative, boundary pushing professionals, you may ask yourself “how in the world can I… a beginner… contribute?!” Simple: Ask questions. Answer Questions. Talk with people. But, most importantly: get involved!

Stuck on some AMPScript? Need to troubleshoot Content Builder? Just ask. Chances are there is someone else with the same question that will also benefit.

When I first joined an Email community, it was very one-sided. Lots of questions were asked about how to do basic tasks, but I couldn’t give anything back. After a while, I noticed a significant shift. New community members joined, similar questions were asked…but this time I could answer them…and DID! I quickly realized that even though I was still learning I could contribute by answering the questions that I already knew.

You don’t have to be the most knowledgeable about a topic or even know every detail. The best thing about contributions from users starting out… perspective. Sometimes a fresh perspective can yield some very interesting results!

!Do I regret it? Yes. Would I do it again. Probably.


Are you still sweating? Hopefully we’ve put your mind at ease a bit. Our goal is to show people that there is no ‘best way’ to do things. That there is no right and wrong way, just different solutions. Are we saying that these nerves and agita will go away? Nope. BUT we are saying that you will become more confident and learn with better ways to deal with the ‘rattlers in your gut’ so no one would ever know how close to vomiting you really are.

Hope you enjoyed it. Please continue contributing to our new community here and hopefully we will be able to provide a safe space for everyone to share ideas and collaborate to bring us all to the ‘next level’.

Authors (in order of appearance):
Genna (gemliza) Matson
Jason Cort
Greg (Gortonington) Gifford
Tony (Tony Zups) Zupancic
Aysha (BlackenedHonesty) Zouain

Read more
HowToSFMC
Published 01/18/2020
General
HowToSFMC Mission Statement

HowToSFMC is a group of people that aim to provide valued information and learnings to the Salesforce Marketing Cloud (SFMC) community across the various skill levels of its users. This assemblage will require community involvement and participation in order to flourish. Essentially creating a “For the Community, From the Community” environment.

HowToSFMC will essentially display community provided issues/common tasks/roadblocks and then ask for the community to submit their solutions for these tasks. The goal is to show that tasks/issues do not have only a single solution, but instead multiple angles and approaches to solve it as the ‘best solution’ may not actually be the best choice for everyone.

We will be opening this up to the community to provide us with the ‘problems’ that they face on a daily basis. We will then identify the most relevant ones to present for the monthly vote. These problems can be anything and everything. Although the more specific the problem is, the less relevance it may have toward the community as a whole.

Once selected, the problem will then be posted to the website for the community to review. We then will ask community members to submit proposed solutions to this problem. These solutions will need to be proven and explained, we cannot accept half answers or just code blocks.

After we receive the solutions, we will gather them, rank them, and display them (anonymized so the same person doesn’t win every time) for the community to vote on. We ask the community to vote based on the best overall solution. This includes efficiency, versatility, elegance, future-proof and results.

After the ‘winning’ solution has been identified, we will remove the anonymization so people can proudly share their work and be recognized for it. We will also have the winner (or a proxy if the winner is unable to) create a quick video explaining their solution and how to implement it. The other top solutions will be listed as honorable mentions/alternate methods. This will not only help illustrate the different ways tasks can be handled, but also provide an opportunity for community discussion.

Although it would be greatly appreciated, we do want to stress that you do not need to participate in each and every step of the above to be a valuable contributor of HowToSFMC. Even if you choose to only participate as a voter once every so often, you have provided a great contribution and we appreciate it. We simply ask you to do what you can, when you can to help our community grow and learn.

Read more
HowToSFMC
All Rights Reserved
Made with by your fellow SFMC users.