Search Content
Hero Image

All HowToSFMC Articles

Published 10/25/2024
Community
HowToScream 2024 - Community SFMC Horror Stories

With Winter '25s bleak enhancements sat in our recent memory, as the pumpkin spices our lattes and spooky Salesforce Agents threaten the very fabric of our reality, it’s time to look back at some of the scary moments members of our community have had. If you’re going to jump at the sight of an Appy apparition, a scary SaaSy or a Zombie Zig the Zebra, turn away now and save yourself the dry cleaning bill.

For everyone else, steel your nerve and take a deep breath for HowToScream 2024.

I had set up the perfect SSO for Salesforce Marketing Cloud, confident that all was secured—until someone accidentally deleted the non-SSO backup user account. The next day, SSO failed, locking out everyone, and the realization hit me: there was no way back in.

We all know that SSO is great for keeping your SFMC org secure. Not managing more credentials, multiple passwords and all of the important good that it does. But, nobody dares warn you about what happens when SSO goes so very wrong. On that day you wake up and SSO is down or SSO is broken and there is no way back in to your org. You may keep a key under the flower pot for those days and have a backup user without SSO. But what happens when someone takes that key? When someone deletes the account? What do you do when you’re trapped outside the org. All of those scheduled jobs happening, even if you need them to stop. Standing helpless on the outside, wishing that SSO hadn’t failed you.

This horror face by Aman is one that sits in the back of every SFMC user, or if it didn’t before; maybe it does now.

Aman has faced more than the one horror and shares the tale of when an request for a single email resulted in 10,000 responses. Be careful with your CC’s & BCC’s on sends. You don’t want to be in the storm of multiple Super Messages, wondering why your Gmail inbox isn’t accepting emails anymore or even just your corporate mailbox filter thinking you’re getting a few suspicious messages. But, it’s not just Aman who has suffered this fate. Poor Lesley has recalled a story of a journey sharing 50,000 messages through a BCC gone wrong. If you’re personalising your subject lines, that’s a lot of ghastly messages to clean up!

A member of our community has reached out and shared some of their in platform nightmares. Chandler has shared some of those instances where the gremlins are working within us rather than just against us.

Make sure you read the map and read it twice before you follow the path. Chandler picked up a haunted map and it took him down the wrong path, with the wrong Data Extension field mapped to the SubscriberKey for the sending relationship. This SubscriberKey slip up is scary enough if you’re just worrying about duplicate rows in your billable Contacts. It’s even worse when the value in that field is in use by another Subscriber! Not just duplicate records but experimental merging of data in unintended and unexpected ways. Dr Frankenstein would be pleased if this came to life!

When you feel the need to give peace a chance and turn the triggers off, there’s often times where you need to reload and fight back against the hordes of customers who are due to pay or overdue to pay their next installment. If you forget to reactivate your triggers, who’s going to notice when things stop bouncing back? Keep an eye on those triggers everyone, you never know when you’ll need to be armed and ready to go!

An anonymous community member shared some terrifying number based horror stories that will send shivers down everyones spines. We all know one of the best ways to keep your SFMC org working smoothly is to rid it of data so old it’s got cobwebs, spiders and is probably haunted. Contact deletion is a great way to do it, but you have to make sure that you are deleting the correct old spooky data. Using the right > or < when it comes to taking the data out of your org is critical or you may wake up to the scarier nightmare than old data - No data at all. Remember, check twice and delete once.

The number based spooky tales don’t end after this though.

A bounce rate made up of ghost bounces. 600% of sends bounced. Bounced from what? Is there spectral beings in your SFMC org?

Be careful out there!

It’s not too controversial to feel that a little bit of teamwork can help overcoming these horrifying moments. People you can rely on, people who you can trust to always have your back in the face of adversity. But, then there is the beast of Internal IT. When all you need is a friend or a helping hand, sometimes all you get is a slap on the wrist and a service management runbook instead. Keeping a clients Internal IT on side is one way to make your life easier, but if you can’t do that then you may end up dealing with a Freddy Krueger style nightmare!

Sometimes your teammates just don’t think things through. They just start plugging things in and unplugging things without thinking about the onward impact. The moment of dread when you notice that someone has switched their Marketing Cloud Connector to a different Sales Cloud instance. Pulling the plug on all of your journeys, all of your data and all of your automations and wondering to yourself how you’re going to rebuild this whole thing.

Poor Duc having to face all of this!

It is far too easy to have to face the demons, ghouls and skeletons of SFMC. But, if you’d like to be around a team that is a little less like the teams community member Duc has mentioned and a little more supportive, come and join us on HowToSFMC Slack. Come and share your horror stories or ask the community to help you avoid your own entry for next years HowToScream!

Read more
HowToSFMC Community
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 10/01/2023
Winter '24 Release Bonus Feature

Fresh off of the heals of the latest Release, word on the trail is that there is a bonus feature making it’s way to Salesforce Marketing Cloud Engagement very soon - and it’s a good one!

If you’ve done anything in SFMC Engagement, you are very much familiar with SubscriberKeys and the role they play within your Marketing Cloud Instance. Pretty much everything on the backend that relates to your subscribers is bound to this identifier. So much in-fact that there have been many different best practices that go along with choosing exactly what this value should be from the inception of your contract. You need to make sure that the decision you make here is one that will stand the test of time - or at least the duration of your SFMC contract.

Now, it’s no surprise since SF introduced the concept of Customer360 that there have been many efforts to enable you to bring together a complete view of your customers data. This means providing you with the tools to ensure you can successfully identify, analyze, personify your customers across your database. We all know that doing so across platforms with different identifiers that this could be pretty tricky.

One of the cornerstones of what makes SFMC Engagement so great is that you have the tools right within the platform to do so many powerful tasks. With this bonus feature, we’re given one more awesome ability - the ability to change your customers Subscriber Key whenever you want!

Rumor has it that Salesforce is adding some much requested functionality to the Subscriber Update Server-Side JavaScript function that will allow you to alter the SubscriberKey of your Subscriber records after the record has been created, making subscriber key migrations easier than ever before. <br /><br /> Now, very little details have been officially released and there’s been no talk on when it’s officially rolling out, but we can speculate a little bit about how this might be setup.

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

var originalSubscriberKey = "066a8346-171b-477d-9a70-65cdc4922594"
var updatedSubscriber = {
    "Attributes": {"SubscriberKey": "whatdayisit@howtosfmc.com"}
}

var subObj = Subscriber.Init("originalSubscriberKey");
var status = subObj.Update(updatedSubscriber);

// Expected Status Output
// April Fools!
</script>
Read more
HowToSFMC
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:

/*!
 * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
 * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
 */@font-face{font-family:"FontAwesome";font-display:block;src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.eot);src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.eot?#iefix) format("embedded-opentype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.woff2) format("woff2"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.woff) format("woff"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.ttf) format("truetype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.svg#fontawesome) format("svg")}@font-face{font-family:"FontAwesome";font-display:block;src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.eot);src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.eot?#iefix) format("embedded-opentype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.woff2) format("woff2"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.woff) format("woff"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.ttf) format("truetype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.svg#fontawesome) format("svg")}@font-face{font-family:"FontAwesome";font-display:block;src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-regular-400.eot);src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-regular-400.eot?#iefix) format("embedded-opentype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-regular-400.woff2) format("woff2"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-regular-400.woff) format("woff"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-regular-400.ttf) format("truetype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-regular-400.svg#fontawesome) format("svg");unicode-range:U+f004-f005,U+f007,U+f017,U+f022,U+f024,U+f02e,U+f03e,U+f044,U+f057-f059,U+f06e,U+f070,U+f075,U+f07b-f07c,U+f080,U+f086,U+f089,U+f094,U+f09d,U+f0a0,U+f0a4-f0a7,U+f0c5,U+f0c7-f0c8,U+f0e0,U+f0eb,U+f0f3,U+f0f8,U+f0fe,U+f111,U+f118-f11a,U+f11c,U+f133,U+f144,U+f146,U+f14a,U+f14d-f14e,U+f150-f152,U+f15b-f15c,U+f164-f165,U+f185-f186,U+f191-f192,U+f1ad,U+f1c1-f1c9,U+f1cd,U+f1d8,U+f1e3,U+f1ea,U+f1f6,U+f1f9,U+f20a,U+f247-f249,U+f24d,U+f254-f25b,U+f25d,U+f271-f274,U+f279,U+f28b,U+f28d,U+f2b5-f2b6,U+f2b9,U+f2bb,U+f2bd,U+f2c1-f2c2,U+f2d0,U+f2d2,U+f2dc,U+f2ed,U+f3a5,U+f3d1,U+f410}@font-face{font-family:"FontAwesome";font-display:block;src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-v4deprecations.eot);src:url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-v4deprecations.eot?#iefix) format("embedded-opentype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-v4deprecations.woff2) format("woff2"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-v4deprecations.woff) format("woff"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-v4deprecations.ttf) format("truetype"),url(https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-v4deprecations.svg#fontawesome) format("svg");unicode-range:U+f003,U+f006,U+f014,U+f016,U+f01a-f01b,U+f01d,U+f040,U+f045-f047,U+f05c-f05d,U+f07d-f07e,U+f087-f088,U+f08a-f08b,U+f08e,U+f090,U+f096-f097,U+f0a2,U+f0e4-f0e6,U+f0ec-f0ee,U+f0f5-f0f7,U+f10c,U+f112,U+f114-f115,U+f11d,U+f123,U+f132,U+f145,U+f147-f149,U+f14c,U+f166,U+f16a,U+f172,U+f175-f178,U+f18e,U+f190,U+f196,U+f1b1,U+f1d9,U+f1db,U+f1f7,U+f20c,U+f219,U+f230,U+f24a,U+f250,U+f278,U+f27b,U+f283,U+f28c,U+f28e,U+f29b-f29c,U+f2b7,U+f2ba,U+f2bc,U+f2be,U+f2c0,U+f2c3,U+f2d3-f2d4}