Search Content
Hero Image

All HowToSFMC Articles

Published 01/15/2021
General
January 2021 - Release Notes Summary

It feels like only a couple of weeks ago that a group of trailblazers were looking at and reflecting on the year that was 2020 (Session 1 and Session 2) and bringing the SFMC year to a close, but it’s time for the first release of 2021!

One of the key characteristics of the releases throughout 2020 is that there was a focus on certain themes and bringing the overall platform into a better state. Permissions have been broken out of catch alls for Journey Builder, SFTP has been given a swift kick up the security upgrade path and content builder templates have been made more accessible. There’s been a strive to make SFMC more self supporting with the improved Setup window allowing users to toggle settings that previously required a support request and whilst the ambition was to make SSL certification self-serving in November, it’s making a rare second appearance in release notes this time around. The foundations of last year have meant that whilst the overall new capability increments were a little light on the ground, 2021 should be a big year for taking your Marketing automation efforts up a notch.

The majority of this you’ll find under the App and Setup heading in the release notes, but there’s some closely aligned elements in other categories so I wanted to bring them together.

As mentioned above, Self-Serve SSL certs are back in the release notes this time around. Currently you need to raise a support case to get SSL certification in place (If you’re on a private domain without an SSL, you’re probably getting sick of all the images not loading in Chrome by now!). But, unfortunately the “when” on this is essentially unconfirmed. So, hopefully this gets brought in time for this release, it would be a shame for it to make the cut 3 times in a row.

Some key _(no pun intended) _elements bringing themselves to the fore right now is encryption and the option to start using your own encryption keys as opposed to just the default ones available. The theme of bringing things up a level from a security perspective slipping into 2021 with the option of how you want to encrypt SFMC. Definitely something to look into if you’ve had worries that your data at rest is encrypted in the same way as other tenants on your stack.

The timer officially starts on the retirement of the SFMC Public Key and you will no longer be able to create new File Transfer activities using it after June 1st. So it’s great that BYOK is being rolled out to decrypting files received from other sources. The official advice from Salesforce is that you **stop **using the standard public key as soon as you can. There are some details for creating a key pair in the documentation here.

Social Studio is getting the Multi-Factor Authentication and Single Sign-On treatment in this release, so you’ll be able to enforce SSO and MFA once it is live.

As mentioned in previous release notes, the V2 Marketing Cloud connector is going away in less than 3 months now and the deep linking from Sales/Service Cloud in to SFMC is being removed in this release. If you’re using V2 and are on an edition of Salesforce CRM that supports it. If you are using a Salesforce Professional Edition, you will need to upgrade your Sales or Service cloud edition to Enterprise or Unlimited in order to access V5. If you’re not sure whether it makes sense for your business to do the upgrade, you can check out the difference between V2 and V5 here. If you’re not in a position to upgrade to V5, you may need to investigate other options for transferring data between your Marketing Cloud org and Salesforce.

It’s not all just good to go if you’re one of the multitude of users who is using the V5 app to integrate between SFMC and Sales/Service Cloud. You will need to make sure that your integration is using the connected app not the legacy integration to ensure connectivity between the two systems persists beyond January 30th. If you’re not sure whether you’re using the connected app or the legacy integration, jump over to the Salesforce Integration screen in Marketing Cloud & if you see a Tracking User Name and Tracking User Password field in the config settings - you’re using the legacy authentication method. Get that updated ASAP and you should be golden!

Possibly the single most updated element of SFMC in the past year, the capabilities for Distributed Marketing keep adding up! If you’re using Custom Personalization Interactions for segmented sends, you’ll now be able to use multiple tabs as opposed to one (recommendation is no more than 10 tabs with no more than 10 fields to keep it performant). This should be available now if you’re using version 230 and above of the DIstributed Marketing package.

Restrictions have been given an update with the option of using regular expressions and wildcards in the restricted words section. There’s also been an addition for the ability to restrict sending based on an active flag or a date range to make sure no content goes out that is no longer or not yet relevant. So check out Restricted Words and Restrict Sending in your Distributed Marketing package!

Farewell Discover Reports. You were helpful to have around, but the formal announcement of Discover being retired will be on February 1st 2021. But, it’s not going away without a replacement! Datorama reports will launch as part of this release and as the release rolls out, we’re expecting to see some more enablement details made available. We can’t add a whole lot of specific details here because they’re not currently available yet, but, having participated in some of the user experience research around it - this looks to be a big step forward. Whilst it may need some time to mature and may not cover all of your immediate analytical needs, it would be worthwhile considering what you can achieve with the new tool when it launches as opposed to creating new things that will sunset in 2022 as Discover is turned off for good.

MobileConnect is getting some new attributes in the Data Views. You’ll be able to add the JBDefinitionID and JBActivityID to your queries against the _SMSmessagetracking Data View. This should help you map your Journey Builder SMS activities more effectively. The actual Data View documentation hasn’t been updated yet with the full definition of these fields but it seems likely that JBActivityID will correspond to JourneyActivityObjectID the same way that TriggererSendDefinitionObjectID does to Email activity. So this will be the first time that there has been a simple way to match with an attribute between Mobile Connect and Journey Builder.

Also in the realms of tracking your SMS, you’ll be able to get the transient, bounced and delivery events for your SMS when sending them from the transactional API. When sending your SMS via the transactional API, just subscribe to the SMSTransient, SMSBounce and/or SMSDelivered events in the process.

Google Analytics gets a nice shot in the arm with the cost prohibitive requirement of GA360 going away and you’ll be able to start making use of the free elements of the analytical tool within Journey Builder. Don’t expect it to be a huge game changer, but it should hopefully simplify some of your campaign reporting if you’re a free Google Analytics user. Besides - it’s free… so why wouldn’t you?

There’s some general improvements coming through to Einstein in this release, including a learning portal which will help to understand the use cases for each Einstein capability. So if you’ve ever wondered what you’d use Copy Insights for, take a look and it should shine a light on what Salesforce suggests is a good use case for the tool. There’s also some improvements from an analytics perspective for Asset selection.

One thing to be aware of is Send Time Insight where Einstein can now use data from multiple customers to derive insight about the best time to send an email. Einstein will securely model opted-in customers (that’s us as users, not your customers/contacts) to improve prediction accuracy. The feature is going to have a gradual rollout but you’ll see a banner in the STO dashboard letting you know that the model determining when is best to send your email is using global data. If you do not wish to contribute to the global data, you won’t be able to benefit from this but to opt out, you’ll need to raise a support case.

Image tagging will now automatically tag things in non-english languages, but you’ll need to set that up. Any current tags in place won’t be updated until you update the image that the tag was for which may be a bit of a hassle if you’re using tagging extensively already but definitely a great one to have for anyone using multiple languages! Asset attributes will now also be able to have multiple values, eg: If you have an asset that works well for both football and basketball fans, you can set that as well as having a set of fixed valid values to use to prevent human error on data entry!

This is the big one for this release. After not providing too much in the way of new capabilities and primarily focusing on quality of life changes for Interactive Emails and similar functionality for the last 12 months, Salesforce have gone all in on some new messaging options in this release. Though, they won’t all hit your orgs on release day unfortunately.

However, the quality of life changes aren’t finished for Interactive Email forms. Currently if you have a hidden field, it may be visible to the form recipient, even though it doesn’t need to be. In this release, new or recently modified Interactive Email forms will experience the new behaviour of not passing the hidden field value through the form submission. A nice little addition to customer experience and keeping your interactive form elements neat.

Journey builder contact error details will get an improvement to make it easier to troubleshoot issues that have caused contacts to exit a journey prematurely due to an error. This won’t include every error type right now, but it will cover errors in email, updating contacts, Sales/Service Cloud activities as well as surfacing some errors from your custom activities. You’ll access it in the Health panel of a journey, search for a contact and if that contact got removed due to an error, you’ll get the details of the error so you can make the changes you need to prevent it happening in the future.

If (like me) you’ve been a big fan of the recently added Alert Manager, the default threshold for alerts being triggered will be set for 10 consecutive subscribers for your triggered send definitions to enter an errored state due to bad data or AMPscript failing to evaluate. One thing to note here is that notifications relating to cancelled triggered sends from Salesforce Support are being retired, so make sure you’re getting emails from Alert Manager to make sure your triggered sends don’t start erroring and not knowing about it.

Mailbox provider one-click unsubscribes will now be added to new or modified email headers in any commercial email send classification. One-click unsubscribe isn’t a bad thing and making your emails easy to unsubscribe from doesn’t have to be the end of the world, but having this in will make sure mailbox providers are treating your emails appropriately. But if you do see an increase in Unsubscribe activity after the new release, make sure you check to see if they are coming from your existing unsubscribe process or if they are coming from this. It’s one to keep an eye out for.

Stepping away from Emails, a nice little change is the ability to choose the duration of an SMS Conversation within Mobile Connect. These can be set up at a keyword level and you can now set your double opt-in and Next Keyword based conversations to listen for a response. If you’ve never used SMS conversations, take a look here to see how the conversation window could work better for you.

WhatsApp messaging comes to Journey Builder through a new partnership with Sinch who are an official WhatsApp business solution provider. You’ll need to link your account with Facebook Business Manager and subject to WhatsApp approving your account you could be using WhatsApp messaging within a few business days. You’ll need to create your message templates and create an audience, but the WhatsApp activity will appear towards the latter half of February once you’ve purchased the additional feature. So, whilst it may not be there for you and it may not be relevant for you - it’s something that up until now has required 3rd party integrations rather than native capabilities.

AMP for Email finally lands in SFMC in March. After being announced at AMPFest 2020, the AMP MIME-Type will become available in your business units. If you’ve not heard of AMP for Email (not to be confused with AMPscript in Email), it’s a framework to unlock greater content capabilities, real time status updates and even two way interaction directly from a customer inbox. Take a look at AMP.dev for some of the documentation to help you get started. But definitely don’t get confused with AMPscript and AMP scripts. If you build any AMP emails that you’d like to showcase, get in touch - it would be great to get some examples that showcase just how to push the capabilities of both SFMC and AMP for Email in tandem.

This is a pretty big release overall with some awesome new possibilities, but unlike the releases of the past it’s not a one and done. Things will change over a few weeks. The major new features with WhatsApp and AMP for Email are both expected to land after the final date of the release roll out. The core elements of the release are expected to finish landing on February 6th but WhatsApp and AMP won’t be available until 16th February and 1st March respectively. You’ve got the V2 connector being turned off at the end of March and the SSO certificate needing to be updated before 18th February. So, whilst there’s a lot going on it feels like there’s a lot of calendar management that needs to go into this to make sure you don’t miss any of these features being turned on or turned off for your business.

Keep an eye on the release notes for the items you have any specific interest in, the release edits are listed in the summary page and they do get updated after the original publish date. At time of writing, this summary covered the original publish date. If things have changed that are no longer reflective of the release, we’ll endeavour to keep updates on Twitter.

Read more
Jason Cort
Published 12/13/2020
SQL
12 Days of Scriptmas

Like many of you, the holiday season is a favorite for all of How2’s directors, admins and other community members! In that light, we are looking to share some of the ‘gifts of script’ we have accumulated over the years with you!

We will reveal a single script each day from Dec. 13th right up to Christmas Day (Dec. 25th) to provide you with the 12 DAYS OF SCRIPTMAS! We hope you enjoy all the ‘gifts’ we have been busy crafting these last few days to share with all you trailblazers and email-geeks as our gifts to you!

Happy Holidays and have a GREAT SCRIPTMAS!!


We are excited to share our first day with you all and for the beginning we are coming out strong with a debug process for SFMC SSJS:


<script runat='server'>
Platform.Load("core", "1.1.1");
/*
 @param { string } identify what you're writing
 @param { response } function/api response
 @param { debug } toggle if function should run or not


 debug = 1 will have output
 debug = 0 will hide output
*/
var debug = 1;

function deBug(string, response, debug) {
  if (response) {
    debug ? Write("<br><b>" + string + ":</b><br> " + Stringify(response) + "<br><br>") : null;
  } else {
    debug ? Write("<br><b>" + string + "</b><br> ") : null;
  }
} //End deBug

</script>

A special thanks to our own Tony Zupancic for the script.


The second day is here! Are you excited?! We are! For today’s script, we are sharing a quick SQL trick to help dedupe your results. Hope it helps you as much as it has helped us.


/* SQL dedupe using partition window and 
targeting first row match found */ 

SELECT x.SubscriberKey
, x.Email
FROM (SELECT a.SubscriberKey
  , a.Email
  , MAX(a.EventDate) AS lastOpenDate
  , row_number() Over(
  		Partition By 
  			a.SubscriberKey 

  			Order By 
  				a.Email
  		) row
  FROM DataExtension a
  WHERE ISNULL(a.EventDate,'') != ''
  Group By a.SubscriberKey
  , a.Email) x 
WHERE x.row = 1

A special thanks to our own Genna Matson for this script.


Day three! This is so exciting for us! Today, we are sharing a small AMPScript snippet that illustrates how to create dynamic variable creation/setting. The possibilities with this are near endless!


%%[
SET @deRows = LookupRows(...)

FOR @i = 1 TO RowCount(@deRows) DO
  SET @row = Row(@deRows,@i)
  SET @tempValue = Field(@Row,'Field')
  SET @FieldSet = TreatAsContent(CONCAT('%','%[SET @Field', @i, ' = @tempValue]%', '%'))
NEXT @i 
]%%
  

A special thanks to our own Greg (Gortonington) Gifford for this script.


We are a third of the way through! It is so exciting…yet also sad that there are only 8 more days left. Today is a trick to help you break up your SQL queries that could time out into multiple queries to get around the 30 minute limit.


/* Avoid time outs by breaking large volume DEs down
by targeting the _customObjectKey attribute on the DE.

This attribute indexes at 0. 

The below example breaks the DE in two. 
First half of the DE -- Query 1: */ 

SELECT a.EmailAddress
, a.SubscriberKey
, a.Status
FROM DataExtension a
WHERE _customObjectKey % 1 = 0


/* Second half of the DE -- Query 1: */ 

SELECT a.EmailAddress
, a.SubscriberKey
, a.Status
FROM DataExtension a
WHERE _customObjectKey % 1 = 1

A special thanks to our own Genna Matson for this script.


FIVE. GOLDEN. RIii…I mean hello and welcome to the fifth day of Scriptmas!! We are excited to provide this wondersful SSJS function that can be used to make sure to elegantly end your SSJS scripts rather than have them timeout and error. Hope it helps to save you some hair and keep that blood pressure low!


<script runat=server>
Platform.Load("Core","1.1.1");
​
var dev = 1;
​
var now = new Date();
var start = now.getTime();
var timeOut = 1500000; //25 minutes
//60000 milliseconds in a minute
​
​
if (dev) {
  timeOut = 10000;
}
​
​
​
do {
​

​//Your Code Here
​
} while((new Date().getTime() - start) < timeOut)
​
</script>

A special thanks to our own Greg (Gortonington) Gifford for this script.


OHHHHH…WE’RE HALFWAY THEERRREE!!! OHHHH OHH AMPSCRIPT CUZ WE CARE! Ahem. Sorry couldn’t resist some Bon Jovi. Today is our halfway point to the end! It has been a WONDERFUL journey and we want to thank everyone for coming along with us so far. Our elves have been cooking up quite a few wonderful things that we are excited to share over the next few days, so make sure you keep checking back every day!

<h1>
  Sendable Data Extension - Summary
</h1>
<table>
<thead>
  <tr>
    <th>DE Name</th>
    <th>Description</th>
    <th>Rowcount</th>
  </tr>
</thead>
%%[
SET @GetSendableDEs = CreateObject('RetrieveRequest')
SetObjectProperty(@GetSendableDEs, "ObjectType", "DataExtension")
SET @SimpleFilter = CreateObject("SimpleFilterPart")
SetObjectProperty(@SimpleFilter, "Property", "IsSendable")
SetObjectProperty(@SimpleFilter, "SimpleOperator", "Equals")
AddObjectArrayItem(@SimpleFilter, "Value", "True")
AddObjectArrayItem(@GetSendableDEs,"Properties","Name")
AddObjectArrayItem(@GetSendableDEs,"Properties","Description")
SetObjectProperty(@GetSendableDEs, "Filter", @SimpleFilter)
SET @DERows = InvokeRetrieve(@GetSendableDEs,@status,@reqID) 
SET @DECount = ROWCOUNT(@DERows)

IF @DECount > 0 THEN
FOR @Loop = 1 to @DECount DO

SET @DERow = ROW(@DERows,@Loop)
SET @DEName = FIELD(@DERow,'Name')
SET @DEKey = FIELD(@DERow,'CustomerKey')
SET @DEDesc = FIELD(@DERow,'Description')
SET @DERowcount = DataExtensionRowCount(@DEName)
]%%
  <tr>
    <td>%%=V(@DEName)=%%</td>
    <td>%%=V(@DEDesc)=%%</td>
    <td>%%=V(@DERowcount)=%%</td>
  </tr>
%%[ NEXT @Loop ELSE ENDIF ]%%
</table>

A special thanks to our own Jason Cort for this script.


We know how much console.log is important to JS developers and this script helps to fill that big hole that is left behind when running SSJS. We hope this helps to bring you a happy holiday season and a very merry Scriptmas!

<script runat=server>
var json = ["one","two","three"]
consoleLog(json);

function consoleLog(value) {
   if (typeof value === 'object' && value !== null) {
      value = Platform.Function.Stringify(value);
   }

   Platform.Response.Write('<script>console.log(' + value + ');</script>');
}
</script>

A special thanks to our own Shibu Abraham for this script.


Who else is excited for the holidays? We have had a ton of fun so far and are very excited for the next few days! We hope these scripts have been useful or at least helped in some way or another. This script helps tons with those that need to use Time Zones in SQL.

/* Get around some timezone challenges with the AT TIME ZONE 'function' within your SQL Queries */
select
eventdate AT TIME ZONE 'Central America Standard Time' AT TIME ZONE 'UTC' AS 'eventdate'
,SubscriberKey
,jobID
from
_Sent**

A special thanks to our own Jason Cort for this script.


Hello again and happy SCRIPTMas! Our elves cooked up something special today sharing examples on how to utilize the LookupRows and LookupOrderedRows AMPScript functions!

%%[ /* AMPscript Lookups */

/* Lookup Rows */
set @lookupVariable = 'rando'
set @lookupRows = LookupRows('DataExtension','Field',@lookupVariable)
set @countRows = RowCount(@lookupRows)
if @countRows > 0 then
  set @row = Row(@lookupRows,1)
  set @field = Field(@row,'Field')
endif


/* Lookup Ordered Rows -- returns 5 ordered rows */
set @lookupVariable = 'rando'
set @lookupOrderedRows = LookupOrderedRows('DataExtension',5,'Field asc','variable',@lookupVariable)
set @countOrderedRows = RowCount(@lookupOrderedRows)
if @countOrderedRows > 0 then
  set @row = Row(@lookupOrderedRows,1)
  set @field = Field(@row,'Field')
endif

]%%

A special thanks to our own Genna Matson for this script.


Holy Cow! Our own Mr. Tony Zups provides us with a powerhouse of a script - a SSJS function to run your API calls via a config file. These next few days are ramping up to be great ones!

<script runat='server'>
Platform.Load("core", "1.1.1");
var accessToken = getAccessToken();//get accessToken

var getJourneyConfig = {
	endpoint: "interaction/v1/interactions/"
	method: "GET"
}


function scriptUtil(config, accessToken) {

 var method = config.method;
 var url = "restBase" + config.endpoint;

 try {
  var req = new Script.Util.HttpRequest(url);
  req.emptyContentHandling = 0;
  req.retries = 2;
  req.continueOnError = true;
  req.contentType = "application/json"
  req.method = method;
  req.setHeader("Authorization", "Bearer " + accessToken);

  if (config.payload) {
   req.postData = Stringify(config.payload);
  }

  var resp = req.send();

  var resultStr = String(resp.content);
  var resultJSON = Platform.Function.ParseJSON(String(resp.content));

  return resultJSON;

 } catch (e) {

  Write("API (e)" + Stringify(e));
  return
 };
}
</script>

A special thanks to our own Tony Zupancic for the script.


One more day! Today we are sharing a wonderful script showing SQL subqueries and how to use them. They are instrumental in most complex SQL queries. With scripts like these last few…what could be the final one? Come back tomorrow and see!

/* Subqueries are a great way to extend your SQL capabilities and they come in many formats.

Eg: Using a Subquery to create a reference dataset from a single location to fFind every subscriber who has opened any email.*/

Select
Subscriberkey
from
_Subscribers
Where
SubscriberKey in (select Subscriberkey from _Open)

/* You can create Subqueries within your Subqueries to get a single output for a specific set of requirements.
Eg: Using a nested Subquery to finding all Subscribers who have Opened an email of a specific Email Name format  */

Select
Subscriberkey
from
_Subscribers
where
Subscriberkey in (select Subscriberkey from _Open
                  where
                  JobID in (Select JobId from _Job
                            where emailname like '%PromoCampaign%')
                  )
                  
/* You can use Subqueries if you need to calculate or derive something to use within your main query, it can save you needing to maintain an additional query and you can join your subquery in to your original query

Level 3 - Finding how many times a person has opened and the first time someone opened an email of the PromoCampaign type with one query rather than 3 */

select
sub.SubscriberKey
,tot.totalopens
,fir.firstopen
from
_subscribers sub
join (select subscriberkey, count(*) as 'TotalOpens' from _Open
      where
      JobID in (select JobId from _Job
                where emailname like '%PromoCampaign%')
      group by subscriberkey ) tot on sub.subscriberkey = tot.subscriberkey
join (select subscriberkey, min(eventdate) as 'FirstOpen' from _open
      where
      Jobid in (select JobId from _Job
                where emailname like '%PromoCampaign%')
     group by subscriberkey ) fir on sub.subscriberkey = fir.subscriberkey

A special thanks to our own Jason Cort for this script.

We did it! Today’s script is a SSJS script to output a list of all the field names inside of a data extension. This can be super helpful in multiple ways, from using in a DE inventory or using it to output fields for a SQL query.

<script runat=server>
Platform.Load("Core","1");  
var dataExtensionFields = DataExtension.Init('myDE').Fields.Retrieve()
if (dataExtensionFields.length > 0) {
    for (var i = 0; i < dataExtensionFields.length; i++) {
        Write('<br>'+dataExtensionFields[i].Name);
    }
}
</script>

A special thanks to our own Jacob Edwards for this script.

Thank you everyone for reading! If you would like to contribute or be a part of HowToSFMC, please reach out to us using <a href=“https://www.howtosfmc.com/contact/”>our contact form</a> or on our Slack channel.

Read more
HowToSFMC
Published 11/09/2020
SSJS
Survey Results - 2020

So, we asked for community feedback on resources that are used to help you to succeed, blaze your trail and become an overall more capable SFMC practitioner and the community has definitely delivered. The resources ranged in categories from websites run by Trailblazers, books, content from Third Parties and a staggering 18 unique individual Trailblazers were called out for the contributions they have made to your experience as your upskill, reskill or share skills within the Marketing Cloud.

One of the most interesting results from these were the number of unique resources that were suggested, where just one person has submitted a resource but some of them are gems that are new to us that need to be flagged. So, we’ll be running through the top 10, in no particular order, highlighting some of their highlights.

Whether you’ve got a question about using SQL, Content Builder, SSJS or pretty much anything you can think of with SFMC you’ve got some of the greatest minds in the community answering questions in their free time. You may have something unique to ask or you may find that there’s a number of people with the same query and you’ve got multiple solutions to your challenge.

With the tag based filtering, it’s quick and easy to get into the details around the questions you’ve got as well as a great way to start on your own quest to become a Salesforce Marketing Champion by getting stuck in. We cannot recommend SFSE enough and chances are if you type an SFMC question in your search engine of choice, SFSE will be one of the top results and start steering you down the right track to a resolution.

Created by Zuzanna Jarczynska (2020 Salesforce Marketing Champion and MVP) and featuring a wealth of resources, examples and great tools that you can get into the nitty gritty of how things work and how to make things work for you. The level of detail in the explanations of each of the examples is excellent and should definitely be a part of your regular reading. If you are ever in need of some inspiration for interesting projects or inspiration to push yourself further as a developer, bookmark sfmarketing.cloud today.

Highlight resources:

Data Views in Salesforce Marketing Cloud

Create a Cloudpages form with an image/file upload

Adam Spriggs (2020 Marketing Champion and MVP) is a name many will be familiar with, a prolific contributor to Salesforce Stack Exchange as well as many of the community webinar events. His blog covers not just SFMC but also just “interesting things” including memes, coffee and some top tier YouTube content. But, it is also one of the first places many people find when starting their journey in SFMC. The ever present requirement for a custom preference centre or for starting an automation via SSJS are 2 common tasks of SFMC developers and Adam has some great info on both of these topics.

Highlight Resources:

SFMC Known Issues Feed

AMPscript Lookup Examples

We’ve mentioned Adam Spriggs & Eliot Harper both in this article for their independent sites and SFMC resources, but when you bring 2 great minds together you can sometimes get something great. In this instance, we’ve got something arguably more useful than that with the AMPscript Guide. Whilst the guide itself isn’t free, the website contains ample code samples and all of the syntax details to enable you to up your AMPscript game.

Highlight Resources:

Marketing Cloud API Functions

AMPscript Language Overview

Greg “Gortonington” Gifford (2020 Marketing Champion and MVP) has set out to create a hub of interesting resources covering all things SFMC. Drawing on experiences from being client side as a jack of all trades through to today, Gortonington.com is definitely in the class of Master of SSJS with the resources that have been put together. Whether you know the difference between SOAP Objects and SQL Queries or not, there’s lots of practical and useful tips and guides to get you started as you level up your SFMC skills.

Highlight Resources:

SSJS Intro

SQL and the Three Valued Logic involving NULL

Markus Slabina (2020 Marketing Champion) is a Salesforce Consultant covering not just Marketing Cloud but also Cross Cloud projects and implementations. Markus.codes covers a broad range of topics with a strong focus on Journey Builder, Cross Cloud and making the most of what you have available. But it would be remiss to mention the blog without mentioning the open source Google Chrome Plugin, SFMC DevTools. Whilst this isn’t an official Salesforce product, there are some nice features that may help streamline your workflow.

Highlight Resources:

SFMC DevTools

Securing CloudPages with SSJS

DESelect is an AppExchange partner for Salesforce Marketing Cloud with the aim of making segmentation in SFMC a simple and easy task with less technical overheads. Whilst they have a lot to say about their product, they have well thought out content around data modelling within SFMC that is definitely worth checking out. The newest video series Heroes of Marketing Cloud features in depth video interviews with Trailblazers and is not one to miss.

Highlight Resources:

What data to store and how to integrate it

Heroes of Marketing Cloud with Kaelan Moss

AMPscript.xyz is the home of Ivan Razine, a web designer and developer and features how-to guides, tips and tricks for a range of the core SFMC technologies. With over 40 different articles at your disposal covering CloudPages, Data Extensions, AMPscript and more it’s a great source of solutions to some SFMC challenges. Even those SFMC challenges you didn’t realise you may have had to face!

Highlight Resources:

Create a Search and Replace app in SFMC

The 5 ways of adding and updating Data Extensions

Eliot Harper (2020 Marketing Champion and MVP) shares his tips across all aspects of SFMC in his short form video series mc.chat. Each video is accompanied by a well structured transcript to make this one of the most widely accessible video series on SFMC currently available. The topics are covered in the right amount of depth to either answer your question or to leave you with enough to go on to continue finding the solution to your specific challenge. Definitely taking a flick through the series.

Highlight Resources:

Email Address Update Behaviour

CloudPages Development

For those of you who haven’t heard of Guilda, she is a tour de Salesforce in bringing Trailblazer initiatives to life and showcasing the absolute best that the community has to offer. She’s spearheading the community driven events like Lookup(Answers) and Technical Marketers Meetings and you’ll find her regularly appearing at Trailblazer Community Events. If there is a single person at Salesforce to keep an eye out for to know what’s going on and when, it’s Guilda.

Highlight Resources:

Follow Guilda on Twitter

Written by Larry Rockoff, The Language of SQL takes a different approach to the syntax encyclopedia you sometimes find with SQL documentation. Taking readers through the simplest of SELECT statements through to the logic behind differing types of JOIN, Rockoff brings the fundamental concepts of SQL to life. Whilst not an SFMC specific resource, many SFMC developers will need to be well versed in this language - so if you’re looking to learn or improve you’d do well to grab a copy of this book.

Where to buy:

Amazon UK (No Affiliate Link)
Amazon US (No Affiliate Link)

Covering the complex world of email deliverability, WordToTheWise is a great resource for email marketers to understand how to make the most of their efforts from their email campaigns. It includes discussion pieces on SpamTraps,Delivery Improvement and Best Practices making it an incredibly valuable resource for SFMC practitioners.

Highlight Resources:

Gmail suddenly puts mail in the bulk folder

The key to improving deliverability

One of the most fully fleshed out API resources for those who use Postman. It includes REST and SOAP API activities ready to use in a well laid out collection. The collection is community maintained, is actively updated (as recently as 28th October 2020 to include new Transactional API endpoints) and covers a huge range of standard requirements from AUTH through to creating assets in Content Builder. Pair this up with Postman today.

The single most inspirational conclusion that can be drawn from this is that the resources the community are looking out for are diverse in terms of content, medium and function. We have resources that focus specifically on the development side of the spectrum as well as the Email Campaign Management. There are videos, books and code repositories & we’ll add the full set of resources shortly. If there’s any resources that you think have been missed, whilst the survey may be closed we would still be glad to hear of them.

Thank you again for all of your contributions to HowToSFMC.com and we would definitely recommend you check out any of the resources both above and below.

Read more
HowToSFMC
Published 11/01/2020
Web Studio
How to Create a Simple Authorization Endpoint Inside SFMC

Please note: There is no implied guarantee of security or any recommendations on security level or protection for your tools or websites in this article. This article is intended as educational only and all content contained is not intended as a ‘final product’ of any kind.

Have you ever run into a need for some added security for your web apps, dashboards, custom endpoints, etc. but don’t think you have the tools to build anything? Inside of SFMC there are a couple existing options such as the authenticated user via your SFMC account, using REST packages/components, etc. But each of these can get unweildy, expensive or potentially provide access/permissions outside the scope you want to provide. What if there were a different solution to provide a time-bomb guid for idetnification?

My solution is a custom built endpoint to pass back a token based on a username and password. This token would have an expiration period set to it in order to increase security due to requiring user/pass re-entry for extended use. Please do note that this is only a sample and is very simple in design to provide a baseline, and is not recommended to use ‘as is’. The idea is to provide a sample for you and your team to build on and make your own robust security endpoint.

First things first, lets lay the foundation for the endpoint to work. I would recommend the below for your endpoint:

  1. Create a BU that has limited access to only allow your admins or security team into
  2. A user/pass data extension to house your users
  3. Authorization Hash/GUID data extension to hold your tokens
  4. A code resource page to host your endpoint

I would highly recommend hosting this as well as all your other endpoints into a single separate Business Unit in your account. This will reduce the access that others will have (increasing security) and it helps to keep it in a centralized location. The second benefit is that this BU can host all your other custom endpoints as well. Since the majority will likely be utilizing REST API, you can host them all in the same enivornment since it would be the SFMC OAuth token that would place the context around the call - not the hosting environment.

It opens the gate for you to store confidential information like REST API ID/Secret codes, User/Pass, and more in a more secure location. That being said, you may want to have added security with Hashing, etc. to decrease the likelihood of someone being able to grab all the info here.

This data extension will be the repository of your users to allow for reference in creating the token. For the sake of this example, I am using it without any encryption or encoding, etc. Please feel free to adjust this to whatever security standards you desire.

Here is my example setup for this DE:

DE Name: Auth_UserPass

Name           |  Data Type  |  Length  |  Primary Key  |  Nullable  |  Default Value  |
UserName       |  Text       |   254    |  Y            |  N         |                 |
Password       |  Text       |   254    |  N            |  N         |                 |
LastLoginDate  |  Date       |          |  N            |  Y         |  Current Date   |
Status         |  Boolean    |          |  N            |  Y         |  False          |

UserName: This is the username of the user.

Password: This is the associated password of the user

LastLoginDate: This is used to show the last time a user logged in. I use this to check user activity and validate token matching, etc.

Status: This field allows you to toggle if a user is ‘Authorized’ or ‘Not Authorized’ without having to re-enter or delete the user from the Data Extension.

There are plenty of more fields and data points that you can store in here. I just added in my base recommendations above.

This data extension will hold the currently active hash tokens to be referenced for authentication. Again this is just a basic example and you can add/adjust to suit your needs.

Here is my example setup for this DE:

DE Name: Auth_HashToken

Name          |  Data Type  |  Length  |  Primary Key  |  Nullable  |  Default Value  |
HashAuth      |  Text       |   500    |  Y            |  N         |                 |
UserName      |  Text       |   254    |  N            |  N         |                 |
CreationDate  |  Date       |          |  N            |  Y         |  Current Date   |

HashAuth: The Token that was created inside of the auth endpoint

UserName The associated UserName to that token

CreationDate The date and time that the token was created.

As the data retention policy in SFMC only goes down to 1 day minimum, you cannot set these tokens to the hours/minutes that you may want. I am including CreationDate to allow for the easy set up of ‘time-bomb’ functionality. After whatever length of time you want, you can set it so that the token then no longer becomes viable and the user needs to create a new one. This also allows you to have flexibility on each endpoint to have a separate countdown rather than having it create an ‘ExpirationDate’ field that’hardcodes’ the expiration. That being said, I still recommend having a data retention on this DE to keep it clean, efficient and fast.

This is where you will build the actual endpoint itself. This page will use lookups and SSJS functions to create a JSON return with the token included. I would recommend making this a JSON code resource page inside of Cloud Pages.

There are a couple key things to note before I share the code:

GetPostData(): This handy dandy function is undocumented but powerful for SFMC SSJS. By writing Platform.Request.GetPostData() you are able to retrieve the data that was sent in via a POST to this page. As a note, this will take any JSON Arrays or Objects and turn them into Strings.

API Endpoint: This page is going to be an API Endpoint, so it will not be rendering or processing any HTML. So any attempts to make HTML in this page will result in the HTML being written as if it were text, not code. Also note that this page will require a POSTed Object to correctly function, so any views inside of a browser will likely result in either a 500 error or a returned error message from the page.

DEV Environment: I usually like to allow myself a ‘Dev’ (Development) or testing environment inside most of the endpoints, tools or programs I build to allow for easy programming and verification. To this extent I have allowed a flag at the top to turn on/off the development environment. Inside this final version, there is no need for it, but I like to include it for future usage. It is especially beneficial for ‘faking’ different requirements or conditions to allow for a streamline testing and to limit the variables in your development.

Debug Environment: This is probably as important if not more important than a 'Dev’environment. Having proper Debugging set up is instrumental to a good troubleshooting or QA process. Similar to my dev environment, I start this with a flag at the top to turn on/off. From there I usually write an inline conditional that if debug is true, then to Write() some data points for easy reference. E.g. debug ? Write('Debug is on!') : '';.

Below is the SSJS code to check if it is a valid user with the correct password, generate a token and then store it internally and pass it back:

<script runat=server>
Platform.Load("Core","1.1.1");

//---------------------------------------------------------//
//---------------------------------------------------------//

// CUSTOM API ENDPOINT TO AUTHORIZE CUSTOM ENDPOINT USAGE

// REQUIRES:
//  - Username
//  - Password
//  - Auth User/Pass DE
//  - Auth Hash/GUID DE

//

//---------------------------------------------------------//
//---------------------------------------------------------//

//set Dev environment for debugging and troubleshooting (1: ON | 0: OFF)
var dev = 0;
var debug = 0;


//Gathers the data POSTed to the endpoint and turns it into a JSON
var postStr  = Platform.Request.GetPostData();
var postJSON = Platform.Function.ParseJSON(postStr);

debug ? Write('postStr: ' + postStr + '\n') : '';
debug ? Write('postJSON: ' + Stringify(postJSON)+ '\n') : '';

if(postJSON) {
	//Gathers the values of the properties passed in payload
	var user = postJSON.userName,
	    pass = postJSON.password

	if(!user) { 
	  Write('{"ERROR": "Missing userName"}')
	  var fail = 1; 
	} else if(!pass) {
	  Write('{"ERROR": "Missing password"}') 
	  var fail = 1;
	} 

	// Error checking to output Error Object if missing
	// a required property from the payload


	if(!fail) {
		//grab authentication info from REST token DE    
		//this is a simple sample, may require different process/security precautions for you to implement
		var luDE = DataExtension.Init('Auth_UserPass');
		var lu = luDE.Rows.Lookup(['UserName','Password','Status'], [user,pass,1])

	    debug ? Write('lu: ' + Stringify(lu) + '\n') : '';
	    debug ? Write('lu.length: ' + lu.length + '\n') : '';
    
        if(lu.length > 0) {
        	var token = Platform.Function.TreatAsContent('%' + '%=GUID()=%' + '%');
        	debug ? Write('token: ' + token + '\n') : '';

            var hashDE = 'Auth_HashToken';
		    var insertToken = Platform.Function.InsertData(hashDE,["HashAuth","UserName"],[token,user]);
        } else {
            Write('{"ERROR": "Authorization is not available. Please add into the reference data"}')
        }
    }
    
} else {
  Write('{"ERROR": "Missing payload"}')
}
</script>

An example call to this endpoint would be something like:

POST /{{myAuthurl}}
Host: {{myHostDomain}}
Content-Type: application/json

Body:
{
    userName: 'myUserName',
    password: 'myPassword'
}

and an example return on success would be:

{
    "Token": c1d72dac-7986-4944-b8bd-a5395ce6be8c
}

and an example failure would be:

{
    "ERROR": "Authorization is not available. Please add into the reference data"
}

I hope this has helped to inspire you to create some awesome authentication endpoints of your own! I do want to make another final note here though that the above is in no way intended to be a final product nor is there any guaranteed or inherit security implied by utilization. It is provided as an example only and is not intended to be utilized as anything other then such. Please leave some comments or reach out with any thoughts, ideas, or questions!

Read more
Gortonington
Published 10/30/2020
General
Your first SOAP API Call - With AMPscript

When it comes to making your first API call within Salesforce Marketing Cloud, there’s a range of options and places to start. You could start with Postman and the library of API calls there, but then you have to worry about configuring an app etc. within SFMC. That’s a lot of effort when there’s actually a lot you can do with the SOAP API to get yourself started without going down the routes of authentication and similar.

In summary a SOAP API Invoke with AMPScript is formed of 3 key parts

  • Pick an object from the dropdown on the left here (In this case we’ll use DataExtension)
  • Check what SOAP API Operation you can do against it here (In this case we’ll use Create, so InvokeCreate)
  • Create your Invoke action in AMPScript. (This is the biggest of the 3 parts, sorry)

An InvokeCreate consists of a few key parts:

  • Creating the Object (DataExtension)
  • Defining the object properties (What attributes of the Object you want and how you want to define them)
  • Execution of the InvokeCreate

So in this example, we’re aiming to create a new Data Extension, which we will define with

Set @de = CreateObject(“DataExtension”)

From now on, any properties we want to set for this DataExtenstion object, we need to use the @de variable.

Examples would be for the Name, Description and IsSendable. The structure of the SetObjectProperty function has 3 arguments

SetObjectProperty(1,2,3)

  1. The API Object you are setting the property for (In this case, it’s @de)
  2. The API Property you’re looking to update (Eg: Name)
  3. The Value you want to set of the API Property in argument 2 (eg: HowToSFMCExample)

SetObjectProperty(@de,“Name”,“HowToSFMCExample”)

SetObjectProperty(@de,“Description”,“Data Extension Created via API - HowToSFMC Sample”)

SetObjectProperty(@de,“IsSendable”,“False”)

But - A data extension has fields, and DataExtensionField is a different object. But you can put an object within an object. So you need to configure a DataExtensionField object too. In this case, we’ll define this with

SET @deFields = CreateObject(“DataExtensionField”)

Much like the base DataExtension object, you need to set the properties for this one too. It works just the same here where you set the API Object you’re setting a property for, what the property is and what the value of that property is. For this example we’re going to give the field a Name, a FieldType, whether it IsRequired, IsPrimaryKey, IsNillable (It’s not for any of those) and a MaxLength of 100

SetObjectProperty(@deFields,“Name”,“MyFirstField”) SetObjectProperty(@deFields,“FieldType”,“text”) SetObjectProperty(@deFields,“IsRequired”,“False”) SetObjectProperty(@deFields,“IsPrimaryKey”,“False”) SetObjectProperty(@deFields,“IsNillable”,“False”) SetObjectProperty(@deFields,“MaxLength”,“100”)

Then you need to add this object to the parent object as an Array Item.

AddObjectArrayItem(@de,“Fields”,@deFields)

The AddObjectArrayItem has 3 arguments, these are:

AddObjectArrayItem(1,2,3)

  1. The object you are adding the array item to. In this case it is @de
  2. The element of the Object you are adding that array to. In this case we’re adding it to “Fields”
  3. The value of the array, which we defined as @deFields

Up next is to actually execute the InvokeCreate, you can do that using a SET function to capture the response from your call.

Set @CreateMyDE = InvokeCreate(@de, @Status, @Error)

The InvokeCreate function has 3 required arguments and an additional optional argument that isn’t necessary here.

InvokeCreate(1, 2,3)

  1. The API Object to be created. In this case, we defined the API Object as @de
  2. The AMPscript variable that will store the resulting status message. In the above example, this is set to store to @Status
  3. The AMPscript variable that will store the resulting error message. In the above example, this is set to store to @Error

Seeing as the InvokeCreate function does provide us with error messaging, we should display those somewhere. So in a lot of cases when setting up an API call these can be displayed on a Cloud Page or written to a Data Extension. For this example we’ll just display them on the a Cloud Page using %%=V(@Status)=%% and %%=V(@Error)=%% along with %%=V(@CreateMyDE)=%% that was used for the InvokeCreate.

Now if you take all of those snippets of broken down AMPscript and put them together, you should get this:

%%[ 
Set @de = CreateObject("DataExtension")
SetObjectProperty(@de,"Name","HowToSFMCExample")
SetObjectProperty(@de,"Description","Data Extension Created via API - HowToSFMC Sample")
SetObjectProperty(@de,"IsSendable","False")
Set @deFields = CreateObject("DataExtensionField")
SetObjectProperty(@deFields,"Name","MyFirstField")
SetObjectProperty(@deFields,"FieldType","text")
SetObjectProperty(@deFields,"IsRequired","False")
SetObjectProperty(@deFields,"IsPrimaryKey","False")
SetObjectProperty(@deFields,"IsNillable","False")
SetObjectProperty(@deFields,"MaxLength","100")
AddObjectArrayItem(@de,"Fields",@deFields)
Set @CreateMyDE = InvokeCreate(@de, @Status, @Error)
]%%
%%=V(@CreateMyDE)=%%<br> %%=V(@Status)=%% <br> %%=V(@Error)=%%

Now pop this snippet into a Cloud Page and click Schedule/Publish. You should get a nice preview pane with this in it:

Then head over to your Data Extensions root folder in the Business Unit that you’ve executed this in and you should see this: !How To SFMC Example Data Extension in your org

Finally if you click into it, you should see your one field configured the way you defined it to be, something like this:

!My First Field - Text - 100 length and Nullable

This should get you started with some SOAP API calls via AMPscript. If you’ve got any questions, keep an eye out for the Salesforce Marketing Cloud Technical Marketers Monthly calls and for the SFMC Lookup(Answers) sessions. These are the places where SFMC experts, MVPs, Marketing Champions and Salesforce Engineers themselves present cool things and answer questions from the community - live!

Read more
Jason Cort
Published 09/15/2020
How to Hack the Salesforce Marketing Cloud (SFMC): Where to go for help

Does this sound familiar? “I know you’re just learning this new tool "Salesforce Marketing Cloud", but can you launch an email tomorrow…stat?” The pressure is always on to show value quickly, but what if you’re just learning the platform? You don’t even know the difference between WYSIWYG or content builder…what’s a data extension (DE)? AMPscript – is that the name of a video game? Where do you go to become the expert that everyone thinks you are? Who can you vent to about all the nuances? Code overload – where are my like-minded email guru’s? Is there anyone out there going through the same pain points? I’m constantly being asked where to go for help, so I decided to pull together a couple of places for people to go to for help with Salesforce Marketing Cloud (SFMC).

I’m approaching my 13th year of using Salesforce Marketing Cloud “formerly ExactTarget email solutions”…and oh how times have changed. I remember how excited I was at first to learn about this new platform and its possibilities but worried about all the things to learn. 12 years ago, cloud communities, slack channels, LinkedIn, YouTube either didn’t exist or was just getting off the ground. Needless to say, support was your BFF or you faked it until you sent it. Today, there are great resources available, but you need to be a detective to stay on top of them. So, I’m gonna help you hack the cloud! I’m passionate about this, available and interested in your inputs/tips, and appreciate your readership! Please share 😃

  1. Join the Salesforce Marketing Cloud community: This group is dedicated to your success with the Salesforce Marketing Cloud ecosystem (Email Marketing, Social Media, Mobile Marketing, Web Marketing). Join the conversation here to ask questions, get answers, stay updated and share experiences. Surround yourself with like-minded savvy marketers who share the same passion around the platform. This is the best place to go when you have a question. Don’t be shy, and ask away! You won’t regret setting up your profile and diving right into the conversations.
  2. Bookmark the following pages:
    • Salesforce Marketing Cloud getting started: The Marketing Cloud training programs are designed to guide you through the key steps that are proven essential for a successful implementation. This is a great place to start when you are setting up your instance so that you can get the full advantages of this out-of-the-box solution.
    • Salesforce Marketing Cloud help page: This is the central location for all things Salesforce Marketing Cloud. You’ll find information on IP warm up, Account Configuration, Setting up content builder to Journey Builder best practices.
    • Salesforce Developer’s page: Here you’ll find information on the Marketing Cloud packages such as REST and SOAP APIs, SDKs, programmatic languages and other fun stuff.
    • Trust.marketingcloud.com: the Salesforce Marketing Cloud community’s home for real-time information on system performance and security.
  3. Become a member to one of the following groups. They exist both online and offline:
    • Salesforce Marketing Cloud User Group: Every month in towns and cities across most continents of the world, people come together to discuss all things Salesforce Marketing Cloud. The goal is to establish an engaged community of marketers. A culture of knowledge sharing and innovation, discussion of successes and missteps with the communal interest in growing and improving from our collective experiences with this platform. These aren’t Salesforce-organized events; these are events run by Trailblazers like yourself. Make this your one-stop resource for local Salesforce Marketing Cloud networking events, presentations and demonstrations.
    • Salesforce Marketing Cloud Meetup: These groups collaborate, network, exchange ideas and best practices around the Salesforce Marketing Cloud. The mission is to build an enthusiastic community committed to learning about the Marketing Cloud platform and achieving successful adoption of the platform.
    • Salesforce Marketing Cloud Developer Group: Useful resources for working and developing with Salesforce Marketing Cloud.
    • Marketing Cloud Developer Center: If you’re a developer, this is the place for you!
    • Salesforce Stack Exchange: Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between.
  4. [Register for Online Training](https://help.salesforce.com/htsctrainingcatalog
  5. Subscribe to the Salesforce Marketing Cloud YouTube Channel: The YouTube channel is a great place to go if you missed a conference, want to learn about a new product or need an overview of a product feature.
  6. Subscribe to the Marketing Cloudcast: Every episode offers key marketing campaign tactics, popular trends, interviews with marketing leaders, and relevant insights — such as social media stats and data management strategies in marketing today. Hosts Megan Collins and Tina Rozul will dive into topics such as performance on individual marketing channels, marketing career advice, the future of marketing, and beyond.
  7. Join a Slack channel: Yes, I said it. There are several email slack channels with a channel dedicated to Salesforce Marketing Cloud. Make new friends and share html codes, how-to’s, ask questions…Vent a little or vent a lot.
  8. Purchase the following books:
    • Salesforce Marketing Cloud For Dummies book by Chester Bullock and‎ Mark Pollard: Salesforce Marketing Cloud For Dummies guides you through the use of Salesforce’s exciting suite of cloud-based digital marketing solutions, which have the power to help you plan, personalize, and optimize your customers’ journey.
    • The AMPscript Guide by Eliot Harper and Adam Spriggs: AMPscript is a scripting language for Salesforce Marketing Cloud. This book extends the existing Salesforce documentation to provide an authoritative reference manual on AMPscript. This is the ultimate guide for anyone learning AMPscript or anyone who wants to freshen up their AMPscript knowledge.
    • The Data Handbook for Salesforce Marketing Cloud: This handbook explains the fundamental concepts for working with data in Salesforce Marketing Cloud, key tips for building a well-designed data model, how to utilize data across all of your marketing activities and much much more…
  9. Follow on Salesforce Marketing Cloud on Slideshare: This site doesn’t have the latest and greatest materials but it still has legacy platform information to help you understand the history of the Salesforce Marketing Cloud platform.

Please feel free to share where you go for help with the Salesforce Marketing Cloud!

Originally posted on LinkedIn by Guilda Hilaire

Read more
Guilda Hilaire
Published 08/08/2020
Email Studio
When you need to use WYSIWYG

Building dynamic content is always challenging for me to engage different customers based on their persona. When I looked at the challenge that was presented I was able to correlate with me and my team how we are challenged in the same way. l looked at the approach which will be reliable and easy to handle for me and my marketing team without any coding knowledge and the approach was towards WYSIWYG. WYSIWYG is an acronym for “what you see is what you get”. There are multiple ways to use WYSIWYG for building email content.

I talked about dynamic content in some of the line but what it is? What value it has? How I have used it? I will explain about it and will show how it was playing important role in my solution.

Dynamic content is content that displays in a content area according to the rules that you define based on the subscriber’s attributes or the targeted data extension column values.

A car rental firm wants to reengage with their existing customers .In this case reengaging with existing customer will be a challenge for marketing team because customer will be having different behaviour. Marketing team based on user behaviour (Like rental car duration or rental car model) can build different persona and assign particular content to user. Marketing team will create rule and assign particular content (Image/Email) to it and send for promotion. Dynamic content helps marketing team to engage/reengage with customers in personalize manner.

In this article I have focused on the approach which I have used to achieve dynamic content using WYSIWYG without HTML, AMPScript ,SQL query and CSS code. In this solution I have used data extension , dynamic content block and interactive email form .This approach works best for marketing teams instead of developers as I haven’t use any code for building dynamic content. I have elaborated the steps below which I followed to achieve this. Before creating email content I have done some of configuration which is described as well.

  • Sendable data extension is a table which stores subscribers/contact information which will be used for communication like email send.
  • For making sendable data extension Is Sendable checkbox should be checked.
  • Add atleast one email address for email send.
  • In sendable data extension send relationship field becomes mandatory where need to assign relation between data extension and subscriber.

Creation method: From the dropdown we can select any one option among three:

  1. Create from existing: Used existing data extension where only field will be referred not values
  2. Create from template: Use any existing template
  3. Create from new: Create new

I have selected Create from new option

  • Name: Enter any data extension name based on your convenient. I have given Name as Rental Car Customer Info.

  • External Key: Unique key for data extension(If we don’t enter value on this marketing cloud automatically assign some value for it). I kept this field blank while creating after saving the record saw external key automatically assigned to it by marketing cloud.

  • Description: Description related to data extension . I have enter description as “Storing Rental Firm Customer Info”

  • Location: Under which folder want to save DE. I have saved on one of my data extension folder.

  • There are 3 option to delete DE :

  • Individual records(Only single record will get deleted)

  • All Records and Data extensions(Data extension will get deleted with all records)

  • All records: (Only all records will get deleted)

  • Period: We can select specific time when want to delete data or data extension.

Firstly I have enabled retention setting by selecting ON flag and selected apply to option as all records and data extension so that data extension and all records should get deleted. Under period option I have specified specific day so that after particular day all records and data extension will get deleted.

!

There are multiple option which can be used to delete the records or data extension.

I have created multiple fields

  • Subscriber key(Primary key for customer)
  • Email address(Email Address Data Type)
  • Recent car hire start date(Date data type)
  • Recent car hire duration(Number data type)
  • Recent car hire model(text data type)
  • Recent car hire color(text data type)
  • Recent car hire price(decimal data type)
  • Recent customer name (text data type)

For all fields (except number and date field) I have assigned maximum length.

As subscriber key is a primary key for my sendable data extension so I have selected subscriber key field from relationship picklist to relates with subscriber info.

!

After creating data extension I have imported data on it using import wizard.

  • Clicked on create button. Email message, Email Template, Content Blocks, Upload option was visible.

  • Clicked on email template and there was two option available paste HTML and existing template.

  • I have selected existing template option but can be selected paste HTML option also based on your business use cases.

    !

  • Selected dynamic content and drag and drop to email content area where i want to include content.

  • Double clicked on the dynamic content block and clicked on browse button to upload or select existing content for default content.

    !

  • Clicked on create rule link + sign.

  • Selected the data source as data extension or audience option as I have stored customer info on data extension and clicked on OK button.

    !

In first rule the criteria I have defined based on the recent car hire duration field value and value I have assigned for this as greater than 15 days. According to me the users which fulfil this criteria they have shown trust on rental firm. Based on that created content for these users which will show them some good model of the car with compelling rent prices and unlimited mileage. Also, I have added some of the good deal in content to engage them for long term rental by comparing with their previous rental price and include some overall rental off.

In second rule the criteria I have defined based on the recent car hire duration field value and value I have assigned for this as less than equal to 7 days AND Recent car hire duration is greater than 1.

In third rule the criteria I have defined based on the recent car hire duration and **recent hire car model **fields. The values I have assigned for this as Recent car hire duration is equal to 1 day AND Recent car hire Model is the highest one which rental firm is providing.

In default criteria I have assigned all deals which rent firm can provide to their customers.

!

  • Clicked on interactive email icon from content block and drag and drop into email content .

  • Selected starting point as review and click on continue.

    !

I have previewed and tested email using data extension values and verify how email content looks like based on different criteria.

In summary WYSIWYG helps marketing team to build personalize and dynamic content which helps in reengaging or engaging customers. Using WYSIWYG without HTML , code and SQL query email content can be build.

So in this article we show what was my approach to achieve dynamic content using WYSIWYG without any code , query , HTML , AMPScript. How WYSIWYG editor helps in creating dynamic content based on the criteria which helps marketing team to engage with customers based on their behavior/persona. Also we show with point of click /no code approach we can solve some of the problems.

WYSIWYG provides capability for coding as well. If there is any business scenario where need some complex data in that case we can use HTML and AMPScript code to build the content. Also in my approach I have selected data extension for creating rules it’s just one approach we can select list as well. The rules in dynamic content can be created based on any logical operator like (AND /OR) also greater than, less than, equal to, not equal to and so on. Interactive email can be used for multiple option as it has lots of built in capabilities.

Read more
Shrishti Mishra
Published 07/16/2020
AMPScript
Baby Shark AMPScript Challenge Winning Solutions

First, thanks to all of our Baby Shark Challenge participants. There was an overwhelming response to this challenge; 120 submissions! After sifting through all of these great submissions, winners for each of the three categories were found. These winners claimed a signed copy of the AMPscript Guide book by Adam Spriggs and Eliot Harper.

The three categories were:

The concentration in this category was how easy it is to pick up and read the code. This includes adding comments, using more ‘basic’ and understandable functions as well as formatting it in a clear and readable way.

For my solution, my wish was to showcase that it is not always necessary to comment your code when your code itself reveals all about itself! I wanted to make it such that anyone who would take a look at it would be able to understand what is its purpose and how it is achieving it. It comes from some of my experiences that people who love coding do not necessarily like to comment on their code all the time, which in turn introduces gaps in understanding for other individuals.

Ivanjan Chattoraj


For innovation, the challenge was to come up with the most unique or ‘out of the box’ approach to output the Baby Shark lyrics in the specified format. However, you had to keep in mind that complexity alone does not equal innovation. Efficiency and elegance were also a consideration in this category.

I looked at the structure of the song and it made me realize it will look very nicely organized if we put it in an XML format. Then I used XSLT to easily format the song as I needed.

Pato Sapir


The efficiency category was based solely on character count. The challenge to the community was to output the full lyrics in a specific format with as few characters possible. ‘Hacks’ and abnormal structures that go against best practice were allowed and encouraged - the end result was all that mattered.

To get started, I had 2 ideas in mind, whether to use one FOR loop or 2. I started off my approach using a single FOR loop, modifying the AMPScript as I went through using functions like REPLACE to reduce the character count. After a while, I found that I reached a limitation towards the single FOR loop process, which is when I decided to switch to a 2-loop process, which drastically decreased the count. I did enjoy the competition, especially the Efficiency method having a clear goal to achieve, being based on just the minimum character count possible, and competing against fellow SFMC developers in the community posting their latest character count submission to motivate others into exploring other means. I was constantly looking for methods to reduce the script down as much as possible, understanding limitations within AMPScript and measures that normally would not be used, such as replacing whitespace with line breaks which did not count as valid characters on the character count site yet stills renders the AMPScript as valid, or inserting a single character into an IIF statement that does not render a value.

Nathan Stone


There were a few other amazing solutions other than our winners that we wanted to share. The hard work and great efforts they put into their amazing solutions deserves recognition. The two stand-outs are below:

A shining example that shows just how a thinking outside of the box can create a beautiful AMPscript output. This submission unfortunately, didn’t meet the printing lyrics requirements of the competition, therefore was disqualified from winning a book - but, it is a complete piece of art. It would be unfair to say just how long she spent on the code for this, but there were definitely some long days involved!

It’s stunning, it’s clever and it’s a submission that was completely unpredictable.

Akin to the example above where the code outputs the lyrics in the form of a shark this individual took the concept of printing out the Baby Shark lyrics and flipped it on its head. The code submitted to the innovation category in itself was written to look like a baby shark. Another stunning example of making code look awesome.

!alt


We hope you enjoyed this contest as much as we did. We were absolutely blown away by the sheer number of participants, the creativity and also in some ways, the commonality between some submissions. There were a few techniques that were used in multiple submissions that we’ll share some of our thoughts on in a future article. But we have to say a huge thank you to everybody who got involved. We have more tasks like this coming in the next few months, so keep an eye out for us on socials and on here. But until next time, thank you!

Read more
HowToSFMC
Published 07/14/2020
Web Studio
The 'Catches' of Try/Catch in SFMC SSJS

Many people lately have been putting out awesome articles on debugging SFMC SSJS, which is fantastic. Most have a section talking about utilizing try/catch, which is a great tool to help figure out specifically what is wrong in a code block. The issue is, most of these posts leave out some important considerations when using try/catch. I hope to help address those here.

For those who are unaware of what ‘try/catch‘ is, it is a statement that wraps around a block of code that you will ‘try’ and should any exception or error be thrown, it will abandon that block go to a ‘Catch’ block.

The main benefit here is that it will not default to likely throwing a 500 error, but instead you can gather the error thrown and then elegantly execute other actions or properly display/log this error as you see fit.

Technically, try/catch is not actually the complete form of this statement. There is an optional third clause that can be included – the ‘finally‘ clause. This will execute after the try/catch portion, regardless of which is executed.

So if the try attempt is made, but errors out so catch clause runs, it will then move to finally clause. The same is true if the try succeeds, and the catch is ignored, it will then move to the finally clause and execute there. The finally block will always execute regardless of try/catch success. Note too that by including a Finally clause, you actually make the catch clause optional.

My best recommendations when dealing with try/catch:.

  1. Put as little code inside of a try/catch as you can.
  2. Declare global variables outside of blocks, as all variables declared inside are ‘local‘ only.
  3. Use conditionals in your catch block to handle different exceptions/errors in different ways
  4. KISS (Keep it Simple, Stupid). The more complex you make it, the more likely you will need to debug your debugger

The reason I recommend putting as little inside a try/catch block as possible is for ease of reading, maintaining and potential scope issues. This also applies for the KISS recommendation, with the added benefit of the less nested try/catches or complex structures/scopes, the less likely something will go wrong or break. Taking it from a very specific use-case debugging tool to something that may require complex debugging itself.

Try/catch has little to no performance degradation or effect, so implementing it multiple times in different places is not just possible, but also can be recommended. This, of course, comes with the caveat that if the nature of the types of errors you are expecting/handling is singular and easily handled in a single block, then there is no reason to implement multiple try/catch statements as stated above.

As stated above, the try/catch/finally blocks are all local scope – so nothing declared in there will transfer outside of those individual blocks. This can certainly cause some major confusion if you do not take this into account.

With local scope, this means that something declared in try will not pass to catch or finally as it is ‘block specific values’. Now, there are ways to get around local scope….but most of them require a more recent version of JS, which we do not have.

The good news is that there is one that can help us get around this. By declaring the variable globally and then setting a value in the local scope, this will retain that value on the variable.

This example shows local scope not passing the value of the variable:

<script runat=server>
try {
	var a = 1;  //local scope only
	throw "Error";
} catch(e) {
	Platform.Response.Write(a); 
	//will write a null value to the page, as 'a' is undefined in this context
}

Platform.Response.Write(a); 
//will write a null value to the page, as 'a' is undefined in this context
</script>

Declaring the variable as a global variable, the value will be passed:

<script runat=server>
var a;

try {
	a = 1;  
	//cannot use 'var' here or it will be declared again,
	// making it local and not retaining the value
	throw "Error";
} catch(e) {
	Platform.Response.Write(a); 
	//will write 1 as a was set to 1 prior to the exception
}

Platform.Response.Write(a); 
//will write 1 as a was set to 1 inside the try/catch block
</script>

As noted in the comments of the sample scripts, the major thing to watch out for is do not ‘re-declare’ your variable in the blocks. This means, avoid using ‘var’ on your variables inside try/catch, only set the value (at least for your global variables). If you re-declare your variable with ‘var’ inside the block, this will make it be considered a local variable and will not retain the value.

This one comes straight from learnings inside of a [Salesforce Stack Exchange Question](https://salesforce.stackexchange.com/questions/311799/ssjs-platform-response-redirect-throws-unknown-error-error-in-application/311934?noredirect=1

Well! It appears this is a well documented .Net issue revolving around that Redirects will throw a ‘ThreadAbort’ exception, which would trigger the catch block to run instead. Now SFMC SSJS does not have all the great exception handling capabilities that you can see in languages like .Net, so what the heck do we do?

My recommended approach is to utilize an if statement inside the catch to watch for the ‘AMPScriptRedirectException’ (as it is called in SFMC error description) to be able to only have a redirect in the catch if it is appropriately needed. With this approach, the redirect will not be inside the catch statement when the RedirectException is thrown. Which means the try statement will correctly run and redirect accordingly.

Below is my recommended code:

<script runat="server">
    var redirect = 'https://google.com'

    try{
          Platform.Response.Redirect(redirect')
    } catch(e) {
        var desc = e.description; //Pulls the description from error object
        if(desc.indexOf("ExactTarget.OMM.AMPScriptRedirectException") > -1) {
          Platform.Response.Write(desc) //This is arbitrary as will not be run
        } else {
          redirect = 'https://yahoo.com'
          Platform.Response.Redirect(redirect)
        }
    }
</script>

The major benefit to this approach (other than that I submitted it as my answer) is that it allows you to differentiate how each type of exception/error is handled via the conditional. It allows you to create additional ‘else if’ statements to potentially have other Redirects or handling/logging options. It also is very readable and clean – allowing for easy maintenance and low processing requirements.

A second approach to this issue is to utilize a second try/catch inside of your original try/catch statement for the redirect. This was provided by the Original Poster (shd.lux) based on the accepted answer by Daniel Koch. Inside this example, there is a nested try/catch inside the original one. Because there is no ‘catch’ action in the nested try/catch, the ‘try’ redirect will function correctly. But if there are any exceptions in code surrounding this nested try/catch, it will be sent to the Redirect in the parent try/catch.

Code snippet example taken from the answer:

<script runat="server" language="javascript">
Platform.Load("core", "1.1.1");

try {

    // do stuff
    try{
        Redirect("http://www.google.com", false);    
    } catch(e) {}

} catch (e) {
    Redirect("http://www.yahoo.com", false);
}
</script>

I do see the value in this solution, especially in its simplicity, but I do worry about any risk that could come from attempting it. I worry if there is an actual error with the Redirect, your page will not toss any exceptions or anything as it will go to the empty catch - meaning it could potentially not redirect like intended, but instead stay on page.

The solution seems a bit to me like using duct tape to ‘fix’ a broken window. Sure, it will work and keep the water out of the car, but it is not pretty and can limit capabilities – like opening it or seeing through it. Depending on your comfort level and needs, this is a viable approach as long as you consider the approach and ensure it meets your needs and does not limit future requirements.

The final way (at least that I will cover here) is what was recommended by Daniel Koch. His recommended way is to declare your ‘try’ redirect var url above the try/catch as a global variable. Then if the ‘try’ throws an exception/error, you reassign a ‘catch’ value to it inside your ‘catch’ block. This value is then used in your Redirect function that is run below the try/catch block and will take you to the URL that was assigned or originally declared as appropriate.

Sample code taken from the answer:

<script runat="server">
    var redirect = 'http://www.google.com'
  
    try{
        //do your stuff here
    } catch(e) {
        redirect = 'http://www.yahoo.com'
    }
  
    Platform.Response.Redirect(redirect)
</script>

This is a very simple and elegant solution, but is also very specific to this particular issue. It doesn’t offer the same flexibility that I feel the conditional ‘catch’ block offers around handling errors. For instance, the redirect exists wholely outside the try/catch block, meaning if there is an error there, it will cause a 500 error on the page.

Now, that being said, I would definitely recommend this if your use-case is singular and you have no need for detailed exception handling or worries on the Redirect tossing any errors. This approach is easy to read, maintain and debug.

So, as you can see, there are a few things to keep in mind while using try/catch. The above is certainly not comprehensive and there are other things that can come up and cause ‘hiccups’ when using try/catch, but I feel the above examples should shed light on the majority of the ‘catches’ of try/catch. Hope this helps make debugging less painful for you!

Read more
Gortonington
Published 07/11/2020
Release Notes
July 2020 Release Notes - Deep dive & Summary

With the May 2020 release finally finishing it’s arrival in all of our orgs, we can now look to the new horizon of July 2020 landing in our orgs in a few weeks time. In summary, it’s another slim form factor release - but that doesn’t mean it’s a bad thing. Skip to the bottom if you’re not interested in the details and just want the abridged version.

The navigation is continuing down the form factor of bundling everything to do with Emails/SMS/Push/Cloud Pages under the banner of “Journeys and Messages”. Big improvement from a UX perspective is now not all elements of the release include “Month Year Release Notes”. A minor bug bear but it didn’t do much to help keep the release notes readable.

So a big thank you to the documentation team for making a tweak like this!

InfoSec professionals rejoice! Multi-Factor Authentication has arrived in SFMC. Whilst the actual implementation of it and what permissions you would need to manage the MFA settings (I would assume that there’s something in the Marketing Cloud Security Administrator user role or in the standard Marketing Cloud Administrator use role). Marketing Cloud Admin will be able to send temporary code to any users who lose their MFA device, but until we see it live we can’t say a whole lot about how it works. Fortunately, a range of MFA methods are available (including the Salesforce Authenticator app, so if you’re already using that then you at least don’t need something else!) Once we see this popping up we’ll add some details, as a security and access change it’s completely understandable that you may not want to make the change without some confidence that you won’t end up locked out! Fingers crossed support are ready for an influx of people who’ve seen it and thought “cool! We need this” without having thought of how it affects the users of their org.

Thank goodness. If you’re using SFMC and need to use Internet Explorer 11 - Show your IT team this post and let them know that the HowToSFMC team is officially against the use of Internet Explorer 11. If Microsoft themselves can embrace Chromium in Edge - so can you. It’s almost 7 years old. Just upgrade. Please.

New REST API search operators for those who use the /asset/v1/content/assets/query endpoint for retrieving the Name, CustomerKey, Content and Subject Line fields within a Content Builder Asset. Whilst it’s historically been more difficult than you’d want it to be if you don’t know the folder of an asset, it should become a little easier with these new operators:

  • MUSTCONTAIN
    • Results should have all the words included in the search term
  • EQUALS
    • Exact match of the search term
  • CONTAINS
    • Must have at least one of the words in the search term

Nice.

This is one of my favourite but also most frustrating elements of the release. Previously erroring for Journey Builder sending, Triggered sending or Job sending emails you wouldn’t necessarily find out straight away or event be notified at all. Now, however, you can input (this is the frustrating bit!) A SINGLE ADMINISTRATOR EMAIL ADDRESS to receive any and all alerts that are triggered around send alerts. It’s a cool feature and it’s one that I would have been grateful to have had in the past. I sincerely hope that we could include a variety of alerts for different reasons to different people & also different channels. Sometimes after a busy day at work (especially whilst we’re all on lockdown) I shock-horror turn off my emails for a few hours. There may be scenarios where an error is at the level of significance that an SMS would be more appropriate. But I’m still looking forward to trying it out and seeing how it works!

For those who have Interaction Studio, it appears to see that for all Interaction Studio customers will have another system user role created - Interaction Studio - Legacy & some new permissions to give users the appropriate permissions in the new version of Interaction Studio (previously known as Evergage). Make sure to take a look at these if you’re using it.

The second announcement about this for the A/B Testing via Marketing Cloud Connect. It relies on some Classic Content functionality (Which supposedly retired for creation of new assets just a couple of weeks ago) - So for Classic Content… This Ain’t Goodbye… Okay, well maybe it is - but thanks for your service. Long live Content Builder.

If you’re integrated with Salesforce CRM with Marketing Cloud Connect & you’re active in that side of the world - you’ll start to get some new banners appearing about the Business Units and Enterprise in the integration.

The release notes just say that they’ll “provide details” - Doesn’t specify what details. It might tell you how many people named Jason you have in your SFMC org or it might tell you something less useful than that. Maybe they’ll be customisable? Who knows, the release notes don’t in this case. Maybe there’ll be some screenshots added in the future to bring it to life (hint hint!)

If you’re in an integrated org, there’s a few more things to be aware of:

  • After a deadline date of July 31st in the May release, the July release now gives you until September 30th to migrate to the connected app. Please do it soon, it’s not too complicated and it means that Salesforce can switch it off and make everyone happy.
  • Distributed Marketing gets some enhancements to the workflow and send scheduling which is helpful. Send scheduling will allow you to schedule something that needs to be approved, but if the approval isn’t available in time the send gets cancelled. So make sure if you’re going to schedule something that whoever needs to approve it knows about it! Another new feature is that it’ll work on tablets (but still no robust phone support)
  • Integrate Interaction Studio with the Sales and Service Cloud with the managed package & the connector (but you need to have Interaction Studio premium in this instance)
  • Individual Link Details being moved to the Bulk API to help customers hitting their API limits due to tracking info sent over Marketing Cloud Connect
  • Synchronised objects with data type mismatches will have the individual field no longer sync, allowing the rest of the object to come through. It’s a shame this doesn’t come through from the Alert Manager rather than having to check the Synchronized Activity Log. But alas! At least it doesn’t all just fall over because someone changed a field type in Salesforce CRM.
  • New relationships get to retain the original relationships to keep things consistent. Not sure if that would be retained and added or retained and discarded. Release notes are a little vague in the fullness of 19 words.

We have the first casualty of the release notes and the release notes are only a couple of days old.

!alt

So if anyone managed to catch what the Modify Permissions for Marketing Cloud SFTP Folders was - let me know! I’m curious.

You can set this up in Contact Builder based on hourly, daily, weekly or monthly cadences. If you choose daily or weekly you get to pick an hour, monthly you get to choose which day of the month. So… Scheduling like we’re used to in other areas of the platform.

If you’re using Audience Builder, you may find your refresh process is taking a lot of time some days. If you happen to be a user who has lots of dimensions, but you only use some on a daily basis - the refresh procedure and data loaders will now automatically detect which ones aren’t being used and will not get that data processed. Just one to be aware of - as the process runs daily if you do need the dimension at short notice you may need to work out the best way to deliver that with Audience Builder. If you’re not sure how best to manage this, the system will only deactivate a dimension if it’s not used. So if you use a dimension in an Audience Filter Segment or Exclusion, the data will be refreshed every day. Not sure if this is a good update but if there are dimensions that you’ll never need you may as well not wait for them to process.

It’s definitely a light one for Developers this time around. You’ll be able to Link journeys to transactional message REST API Send Definitions. Anything you’ve already got in place, you’ll need to use the options.createJourney JSON parameter by PATCHing the /messaging/v1/email/definitions endpoint with your definition key.

With all of the details around SMS from Kevin Ryan from the Technical Marketers Bi-Monthly Meeting on 25th June - it’s great to see that the SMS Status Codes will be available via the REST API and not just the Data Views. The API will return the “highest” status code from the carrier. More details are available here.

Unsecured HTTP Callbacks for SOAP Async API are being turned off. Just switch to HTTPS and you should be good.

Everyone’s favourite cartoon genius (Sorry Dexter, your laboratory is cool and all - but I’ve never seen you ride a 3 person tandem bike with Astro who can’t reach the pedals and Codey who can’t reach the handlebars) is getting some new features!

!alt

  • Send Time Optimization is coming to Push. If you know STO for email & you know Push. Smash them together and you get Push STO.
  • Send Time Insights in Journeys, with STO you’ll start to get some more accurate details around the progression of contacts in the journey. You’ll get total counts for the last 30 days and see a breakdown of those who do and don’t have personalised send times modelled.
  • Some more info from the Einstein Messaging Insights Model card, which is awesome. Always a good sign to have more information made available to use rather than the insight being hidden away in a Salesforce box.
  • Content variety with a change in the Einstein Content Selection block - previously if you had more than one block in an email you may get 2 similar pieces of content selected. So at least this way you don’t send someone the same piece of content twice in one send! Just include the additional asset attribute you want to vary across in the Variety tab.
  • Einstein is going to play around in Interaction Studio a little bit more, using Einstein Personalization Decisions for next best actions and similar experiences.

Journey Builder is getting some great new features and considerations in the new release including:

  • Dedicated Journey Builder permissions. Administrators will be able to dedicate roles around the design and configuration of a Journey but not necessarily enable the same user to activate it. If you have a marketing team who have dispatch in their remit but not orchestration it may not be best practice for them to have permission to edit a journey. This way you can also create a safe way for newcomers to Journey Builder to get hands on without risking sending an email to your full customer base!
  • Transactional sending from Journey Builder and including near real time operational metrics is nifty, takes it out of the hands of just developers via the Transactional API and it does include the same out of the box features as Multi-Step and Single Send journeys.
  • Single send SMS is here. We’ve had Single Send Email & Single Send Push already so now it’s time for Single Send SMS. If you’ve got Journey Builder & you’ve got Mobile Connect - now you can do all your sending in one place with Journey Builder. It’s good to have consistency. Single Send Line next release?
  • Find and remove a contact from a journey in Journey Builder. FINALLY. Many of us have had an instance where we’ve needed to get someone out of a journey for whatever reason and we’ve all got our own ways of dealing with the situation. Some use Goal/Exit criteria and go change some data about the contact, some use decision splits to force people down a specific exit path and some use the API. None of which are ideal, usually when you need to hoof someone out of a journey it’s because something serious happened and they need to go now. If you’ve never done the API ejection or you haven’t got the Goal/Exit criteria - up until this goes live, you would have been up a creek with no paddle. Thanks Salesforce for finally adding this feature!
  • Abandoned Browse, Abandoned Basket and Abandoned Wishlist emails now possible with the collect tracking code within Journey Builder using the Behavioural Trigger data extension as an entry source to the Journey. Lots of us have custom solutions for this situation, but hopefully this will streamline it for future iterations!

For Mobile Push - Update your Auth Keys for iOS. You have until September 14th. Release notes for the 2020 Push SDK are available for iOS and Android

For SMS - There’s now a loop prevention process in place for those times where you accidentally send to a system that automatically responds to you. So now, if you receive more 35 messages from a single number in a 5 minute period, outbound messages will stop being deployed.

You’ll be able to update the SSL selection after a page has been published, which should be nice and help those who have any legacy pages prior to purchasing an SSL cert. Rather than fully redeploying a landing page (especially a preference centre) that has been active for months just because of an SSL cert.

CloudPages did have their base template updated to remove an insecure version of jQuery. So if you have any jQuery on a landing page I thoroughly recommend you go and check whether you need to add it yourself manually now!

Have you been trying out the new CloudPages Experience PREVIEW? Any thoughts on it? Let us know!

Email feels a bit like the last kid picked at school this release. The only change at all is the addition of Interactive Email being able to accept a pre-populated field. So if you’re providing a contact with a form including a name or any other details - you could surface this information for them to confirm or reject it directly from the email.

Pretty neat, don’t think it’s massively a game changer but any improvement for Interactive Email is massively welcome.

Overall, whilst this is a pretty slimline release, there are some great call outs to be made but most of them aren’t on the super fun stuff we all love to play with. It’s definitely a release of practicality and it almost feels like SFMC is growing up and becoming more responsible for itself. MFA is a great addition for a security perspective, the Alert Manager is massively welcomed to any solo admin of a wide org & the journey removal process now being in the UI to make things a little easier. On the whole, this is a sensible release and fingers crossed it all lands before the release notes for the October release hit the internet!

Read more
Jason Cort
Published 07/08/2020
Release Notes
Journey Pausing - A quick summary and considerations

After being announced as part of the May release, delayed until the end of June, organisations will have started to see the Journey Pause capability roll out at the beginning of the second week of July (We’ve waited a while for this anyway, what’s a couple more weeks?). We’ve now started to see it in the wild so wanted to share some insight about HowTo Journey Pause and what implications there are for some of the situations where you’d need to pause a Journey.

To start, we’ll look at how to configure Journey Pause.

When your SFMC instance has received the update to enable Journey Pause, you’ll see this in the top right of an active Journey.

When I read the original release notes, I was under the impression that it would be an all or nothing pause as opposed to a pause that could be applied at a journey version level. Which does add some additional flexibility to applying a journey pause to the marketing cloud users. Eg: If you’ve got a requirement to apply a pause and are currently finishing a previous version of a journey - you could leave that to finish regardless to close it out or you can pause it too.

The only configuration beyond selecting which version of a journey gets paused, is the duration and the Wait by Duration extension option.

!alt

!alt

In the instance below we have a fixed wait by duration Pre-Birthday email campaign. With the way it is configured, anything longer than a 7 day pause would definitely cause a problem for all contacts involved in the flow of the journey. But, if you were using relative wait steps (Wait Until Date / Wait By Attribute) you would have no option but to have everyone flow through immediately when the journey becomes unpaused. So it’s something to consider when orchestrating the flow controls of your journey if you are taking into account the functionality of Journey Pausing.

!alt

The other option available is the post-pause-period action. If you set a journey to pause for 14 days, what behaviour should SFMC take with the journey if it hasn’t been resumed? Much like we referred to in the original release notes write up that this isn’t a true/permanent pause. It is the opportunity to pause a journey and make a decision about what to do in a period of crisis without just pulling the plug. But, in order to deploy a pause you do need to decide what happens if you do not come to a conclusion and either resume or stop the journey manually.

One such scenario would be that if your journey has to pause due to an inventory bug on an eCommerce store showing all products out of stock, it might make sense to set a 3 day pause and then the automatic response be to reactivate. However, I suspect that isn’t necessarily the best way to go about it. If the bug gets fixed in 2 days you wouldn’t wait for day 3 and if it takes longer than 3 days to fix you’ve kind of backed yourself into a corner with the pause expiring.

!alt

My suggestion would be that if you need to pause a journey, unless you have a specific reason to need SFMC to make the future decision for you at a shorter time frame - Just go for a 14 day pause. You can go back and resume or you can go back and stop a journey whilst it is paused. You cannot currently extend a shorter pause without resuming the journey first.

!alt

So in summary - Journey Pausing is a great new tool to have in your arsenal. It goes part of the way towards supporting us with some of the issues that can crop up, whether it’s a stock control issue, a data quality issue or a global pandemic. But, be aware that when you’re using it - it will only do what you tell it to do and it will only hold your journey in for as long as you tell it to (for up to 14 days).

Good luck & I hope you never need to use this feature in a crisis (because that means something went wrong and nobody likes that!)

Read more
Jason Cort
Published 06/06/2020
Winning Solutions
James Lamb - Subscriber Preferences Management

Everywhere I’ve worked, Subscriber Management has been a complex beast. You’ve got multiple backend systems that know about a person, you want to make sure a person is empowered to control their subscription status (not just unsubscribe), you’ve got legal issues around compliance and you’ve got the technical challenges of bounces.

At each place I’ve worked, we’ve approached that challenge from a different angle with varying levels of success, but recently I had a chance to design a system from scratch. My solution is for our particular business objectives and based on U.S. law which is far more lax than some other countries. In the case of law compliance and procedures, always make sure to check with your company’s legal team. I wish I could share more detail (because I’m pretty proud of it), but there are proprietary/confidential details that I can’t share. So I’ll cover a simplified version of this:

One automation, 20 data extensions (less if you left off development and qa), 42 queries (again, less w/o dev and qa), 1 Data Extract, 1 File Transfer and 1 Import.

  • Honor all unsubscribe requests in 10 business days or less. Hopefully much, much less.

  • Allow subscribers to unsubscribe without authentication (existing landing page allows them to enter their email address and hit submit to unsubscribe. This is also linked at the bottom of all of our transactional emails and used by our Customer Service team when a person asks to be unsubscribed during a call or chat)

  • Allow subscribers to subscribe or unsubscribe from within their authenticated Account Management portion of our website (applies only to their current, primary email address)

  • Allow use of the native ESP (SFMC) unsubscribe method (click or reply “unsubscribe” in email)

  • Get most recent instruction if they use multiple methods to give conflicting instructions (for instance, use the Account Management to subscribe and then use an email to unsubscribe)

  • Include a Development flow (the right half of the flowchart) and a QA process. I won’t talk much about the Development flow (it mimics production as best as it can). The QA process allows you to get before and after snapshots for comparison. This is useful in troubleshooting. Together with the Development flow, the QA process will be instrumental in any future changes to the process.

  • An email address that’s not unsubscribed is eligible to receive emails. It might or might not actually be sent any emails.

  • An email address that is unsubscribed should not receive any marketing emails.

  • Any new subscribers should get our current blog email (their address should be passed over to that automation to be handled)

  • Unique Key: Email Address. Each account must have a unique active, current email address, each email address can belong to one and only one account. (An email can’t exist on more than one account.)

  • Marketing Election: The user’s indicated choice to be subscribed or not subscribed. It is an attribute of Email Address. It has a timedate stamp.

  • Never use one complicated query when several simpler queries will do

  • This assumes SFMC doesn’t start charging by

  • The entire extended automation takes about 35 minutes end-to-end. With more data, additional optimizations may be necessary.

  • Explicit Opt-In - they have told us they wanted to receive email from us. By U.S. law, this also covers people who have a financial relationship with us (i.e., they’ve made a purchase).

  • Explicit Opt-Out - they have told us they do not want to receive email from us. (They have unsubscribed.)

  • Implicit Opt-In - they have not told us one way or another. Because our website predates our ESP (and the one before it), it’s possible that there are some customers (long-term paying subscribers) whose email address has never been passed to the ESP.

Salesforce Marketing Cloud receives inputs in the following manner:

  1. Subscribe. We offer opportunities on our website to subscribe to an email. Accepted email addresses are passed from the form to SFMC via API (rowset) to an “Opt-In” Data Extension. This stores the Email Address, the timestamp and the form.
  2. Unsubscribe
    1. Native - SFMC offers several native methods for unsubscribe via Profile Center, Subscriber Center or by replying “unsubscribe” in an otherwise blank email. These update the “_Unsubscribe” Data View.
    2. Form - we offer a CloudPage where you can enter an email address to be unsubscribed. It is stored in an “Unsubscribe Request” Data Extension.
    3. API - there are some cases where our website determines an email address should be unsubscribed from marketing emails. These are passed by API (rowset). This does not prevent them from being able to re-subscribe if they so choose.
  3. Subscribe/Unsubscribe - From our Authenticated User Preferences section on our website, there is a checkbox for “Receive Marketing Emails”
    1. When someone first visits the page, an API call is made to SFMC to determine their “Marketing Election”
      1. SFMC responds “Subscribed” - the checkbox is displayed as checked
      2. SFMC responds “Unsubscribed” - the checkbox is display as unchecked
      3. SFMC says it doesn’t know - the checkbox is displayed as checked, another API call (rowset) is made to mark their election as “Subscribed”

Runs hourly

  1. Query - get any new records in the past 24 hours from the _Unsubscribe Data Extension (target SuppressionMaster, update) where the record isn’t also in MarketingEmailElection in a subscribed state with a later timestamp.

    SELECT
     
        u.SubscriberKey as EmailAddress,
        MAX(u.EventDate) as dteOptOut,
        '_Unsubscribe' as strSource
     
    FROM
     
        [_Unsubscribe] u
         
    WHERE
     
        u.EventDate > DATEADD(HOUR, -25, GETDATE())
     
        AND
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = u.SubscriberKey
    AND u.EventDate < mee.dteUpdate and mee.blnOptIn = 1)
          
    GROUP BY
     
         u.SubscriberKey
    
  2. Query - get any new records that were passed via the unsubscribe API (target: SuppressionMaster, update)

    SELECT
     
        s.Email as EmailAddress,
        s.[Last Modified] as dteOptOut,
        'suppression_prod' as strSource
     
    FROM
     
        [WebSuppression] s
         
    WHERE
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = s.Email
    AND s.[Last Modified] < mee.dteUpdate
    and mee.blnOptIn = 1)
    
  3. Query - clear WebSuppression (could explore using SSJS to remove all rows) (target: WebSuppression, OVERWRITE)

    SELECT
     
        EmailAddress as Email
     
    FROM
     
        [SuppressionMaster]
         
    WHERE
     
        EmailAddress = 'intentionally, this will never match anything@hello.com'
    
  4. Query - get all the opt-outs from the opt-out form (target SuppressionMaster, update)

    SELECT
     
        eco.EmailAddress,
        eco.[dteSubmitted] as dteOptOut,
        'ExternalCloudpageOptout' as strSource
     
    FROM
     
        [ExternalCloudpageOptout] eco
     
    WHERE
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = eco.EmailAddress AND eco.[dteSubmitted] < mee.dteUpdate
    and mee.blnOptIn = 1)
    
  5. Query - Clear optout form. (similar to query #3)

  6. Query - get any new unsubscribed records from _Subscribers (All Subs). Target SuppressionMaster, update

    SELECT
     
        s.EmailAddress,
        s.[DateUnsubscribed] as dteOptOut,
        '_Subscribers' as strSource
     
    FROM
     
        [_Subscribers] s
         
    WHERE
     
        s.status = 'unsubscribed'
     
        AND
     
        NOT EXISTS (select 1 from [MarketingEmailElection] mee WHERE
    mee.EmailAddress = s.EmailAddress AND s.[DateUnsubscribed] < mee.dteUpdate
    and mee.blnOptIn = 1)
    
  7. Query - get all the unsubscribes from MarketingEmailElection - target SuppressionMaster, update

    SELECT
     
        EmailAddress,
        dteUpdate as dteOptOut
     
    FROM
     
        [marketing_email_election_prod]
         
    WHERE
     
        blnOptin = 0
    

    Ok, at this stage “SuppressionMaster” now has all of the unsubscribes from all the different places you can unsubscribe. Now, we’re going to handle all of the new “subscribe” (yes) elections.

  8. Query - get all the “yes” entries from the past 72 hours from MarketingEmailElection and store in MeeYes1, OVERWRITE. (At this point, you’ve probably guessed that I’m changing the DE names from what I use in my instance.)

    SELECT
    	EmailAddress,
    	blnOptin,
    	dteUpdate,
    	strSource =
    		CASE
    		WHEN strSource is NULL THEN 'AcctPage'
    		ELSE strSource
    		END
    FROM [MarketingEmailElection] mee
    WHEREblnOptin = 1
      
     AND
      
     DATEDIFF(hour,mee.dteUpdate,GETDATE())<72
    
  9. Query - now, get only the “yes” requests that are newer than the “no” (unsubscribe) requests in SuppressionMaster. Target MeeYes2, OVERWRITE.

    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        sm.dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
         
    INNER JOIN
         
        [SuppressionMaster] sm
            ON mee1.EmailAddress = sm.EmailAddress
             
    WHERE
     
        mee1.dteUpdate > sm.dteModified
         
        AND
         
        mee1.blnOptin = 1
             
    UNION
     
    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        NULL as dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
     
    WHERE
     
        NOT EXISTS (select 1 from [SuppressionMaster] sm
    where sm.EmailAddress = mee1.EmailAddress )
    
  10. Query - now get all the records that are in SuppressionMaster unless they’re in MeeYes1. Target SuppressMasterTransit, OVERWRITE. We need to do this because we need to remove the new “yes” entries from SuppressionMaster.

    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        sm.dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
         
    INNER JOIN
         
        [SuppressionMaster] sm
            ON mee1.EmailAddress = sm.EmailAddress
             
    WHERE
     
        mee1.dteUpdate > sm.dteModified
         
        AND
         
        mee1.blnOptin = 1
             
    UNION
     
    SELECT
     
        mee1.EmailAddress,
        mee1.blnOptin,
        mee1.dteUpdate,
        NULL as dteModified,
        mee1.strSource
         
    FROM
     
        [MeeYes1] mee1
     
    WHERE
     
        NOT EXISTS (select 1 from [SuppressionMaster] sm
    where sm.EmailAddress = mee1.EmailAddress )
    
  11. Query - now we write them back to SuppressMaster, OVERWRITE. At some point, I’d love to replace this with an SSJS SQL “DELETE” query instead.

    SELECT
     
        EmailAddress,
        dteOptOut,
        dteModified
         
    FROM
     
        [SuppressionMasterTransit]
    
  12. Query - now we need to make a list of the records that need to be updated in _Subscribers. Target SubscribersUpdate, OVERWRITE

    SELECT
     
        y2.EmailAddress as [Email Address],
        y2.EmailAddress as [Subscriber Key],
        'active' as status,
        y2.blnOptin,
        y2.dteUpdate,
        s.DateUnsubscribed
         
    FROM
     
        [MeeYes2] y2
         
    INNER JOIN
     
        [_Subscribers] s
        ON s.EmailAddress = y2.EmailAddress
    
  13. Now that we’ve got that file, we need to Export it to our SFMC SFTP Storehouse.

  14. And then transfer it from the Storehouse to the SFTP Import Directory.

  15. And then import it into _Subscribers, update.

  16. Get everyone from Yes1 in the past 24 hours and place into Yes3, OVERWRITE.

    SELECT
     
        yes1.EmailAddress,
        yes1.dteUpdate,
        yes1.strSource
         
    FROM
     
        [MeeYes1] yes1
         
    WHERE
     
        yes1.dteUpdate > dateadd("d",-1,GETDATE())
         
        AND
         
        yes1.blnOptin = 1
    
  17. Place everyone from Yes3 into the “optin” data extension. A different process will read from this Data Extension and send the latest blog email to all new opt-ins. It’s designed to only send them the email once, even if they appear in here more than once.

    SELECT
     
        yes1.EmailAddress,
        yes1.dteUpdate,
        yes1.strSource
         
    FROM
     
        [marketing_email_election_yes1_prod] yes1
         
    WHERE
     
        yes1.dteUpdate > dateadd("d",-1,GETDATE())
         
        AND
         
        yes1.blnOptin = 1
    

!Schema table

  • mee = MarketingEmailElection, MeeYes1, MeeYes3
  • mee2 = MeeYes2
  • ms = not explained in this simplified version for HowToSFMC
  • qa1, qa2, qa3 = used in the QA process, also not explained here

SuppressionMaster and SuppressionMasterTransit

!

!Extract Configuration

!File transfer setup

!Import configuration

Read more
James Lamb
Published 06/06/2020
Winning Solutions
Matt Brocious - Subscriber Preferences Management

For the “preferences management” solution, I wanted an option that was user-friendly (both internal and external users), that was scalable for a business of most any size, and that played to the strengths of both the Marketing Cloud tool and the AMPscript coding language.

As a quick overview, this solution uses a CloudPage to capture user’s email content preferences and if they want to unsubscribe from your emails or not. Once submitted, the preferences and whether or not the user is mailable (unsubscribed or active) is stored in a data extension which can be used to segment customers or can be used to populate a field in your customer master. Additionally, we use a data extension that holds your preferences to populate the form on the CloudPage. This will allow you to easily modify your preferences without making major updates to the form.

Before I begin describing the setup in more detail, there are a few things you’ll want to make sure are setup within your account:

  • You’ll need to make sure you have access to CloudPages within your account.
  • If desired, ensure you have personalized URLs setup with SSL certs for your CloudPages.
  • Review your business unit rules to ensure they are setup accordingly. For example, if unsubscribes happen on a business unit level or if statuses are shared.

This setup also makes a few assumptions, so if they don’t apply to your business then you might need to make some adjustments:

  • Your business rules allow for users to be opted-in to all preferences by default.
  • You are not allowing users to update their email address in the preference center.
  • Your business rules allow for users to re-activate themselves within the preference center after the unsubscribe themselves.
  • You are only allowing users to access the page via a link in an email.

So, if you have everything setup and are okay with how this solution works, let’s start building.

For this setup, you will be creating two data extensions. One to house the user’s preferences and one to house all of the preferences we are listing out on the page.

First, you’ll want to build the preference center data extension. For this setup, we are creating the data extension within the Shared Data Extensions folder via the parent business unit. Below is the data extension we are using for this setup but make any necessary adjustments for your use case.

  • Name: Master_PreferenceCenter_CustomerPreferences
  • Description: Master data extension for user’s preferences and unsubscribes
  • External Key: Master_PreferenceCenter_CustomerPreferences
  • Data Setup:

Next, you’ll want to set up a data extension that will be populated by the preferences you want listed out in your preference center page. This will allow you to easily add additional preferences as your marketing efforts grow more complex. The “priority” field will be used to denote the order in which the preferences should be displayed. The “key” field will be the same for all preferences and will be used in the AMPscript within the CloudPage.

  • Name: Master_PreferenceCenter_PreferencesList
  • Description: List of all email preferences
  • External Key: Master_PreferenceCenter_PreferencesList
  • Data Setup:

!Data Setup

Finally, if necessary, add any additional fields to your customer master you’d like to use in conjunction the prewithference center. For example, a “Preferences” field.

Now that the data extensions are setup, we’ll move onto the CloudPages. For our setup we’ll need one CloudPage that will house the form and process the user’s data and one page to be used as an error page.

So, in the CloudPages section of Marketing Cloud, let’s create a new collection named “Email Preference Center”.

Next, create a new page named “Error”. This page doesn’t need much but feel free to stylize and needed for your use case. For this example, we’re simply going to have some text on the page that reads “An error has occurred. Try your request again or contact your system administrator.”.

Once that is done, we’ll move on to creating our main preferences page. Let’s create a new landing page named “Email Preference Center”. You can design the form as you wish, but you will need the elements listed below on your page for this setup. For an example page - see document sfmc_prefcenter.html.

The below AMPscript code should be added to the top of your page and is used to update the user’s preferences and status on the master preference center data extension and, if the user unsubscribes, within the All Subscribers. It also sets a confirmation message which is displayed on the page after a user takes an action. If the page is accessed and there is no valid email address provided, the page will redirect to the error page we created in the previous step. There are notes in the code to help show which lines are performing which actions.

And, if you aren’t familiar with some of the AMPscript functions being used, it might be helpful to read up on them. Here are some of the functions being used:

  • IsEmailAddress – https://ampscript.guide/isemailaddress/

  • RequestParameter – https://ampscript.guide/requestparameter/

  • Row – https://ampscript.guide/Row/

  • Field - https://ampscript.guide/field/

  • Create Object - https://ampscript.guide/CreateObject

  • SetObjectProperty - https://ampscript.guide/SetObjectProperty

  • AddObjectArrayItem - https://ampscript.guide/AddObjectArrayItem

  • InvokeExecute - https://ampscript.guide/invokeexecute

  • Empty - https://ampscript.guide/empty/

     %%[
     
     /* Set the variables used in this page */
    
     var @email, @jobid, @submitted, @unsubscribe, @preference, @mod_date, @url
    
     set @email = emailaddr
     set @jobid = jobid
     set @submitted = requestparameter("submitted")
     set @unsubscribe = requestparameter("unsubscribe")
     set @preference = requestparameter("preference")
     set @mod_date = Now()
    
     /* If the @email variable is empty but the @submitted or @unsubscribe value are populated with the correct values, populate the @email variable with the email parameter. */
    
     if empty(@email) and (@submitted == "Yes" or @unsubscribe == "Yes") then 
       set @email = requestparameter("email") 
     else endif 
     
     /* If there is no valid email address provided, then redirect the user to the error page */
     
     if isemailaddress(@email) == "false" then
       set @url = "errorpagelinkhere.com"
       redirect(@url)
     else endif
    
    
     /* If the user submitted their preferences, then upsert the record on the master preference center data extension and update their status to active */
    
     if @submitted == "Yes" then 
    
       UpsertDE("Master_PreferenceCenter_CustomerPreferences",1,"Email_Address",@email,"Mailable","Y","Preferences",@preference,"Last_Modified_Date",@mod_date)
    
      SET @sub = CreateObject("Subscriber")
       SetObjectProperty(@sub,"EmailAddress", @email)
       SetObjectProperty(@sub,"SubscriberKey", @email)
    
       SetObjectProperty(@sub,"Status","Active")
       Set @options = CreateObject("UpdateOptions")
       Set @save = CreateObject("SaveOption")
       SetObjectProperty(@save,"SaveAction","UpdateAdd")
       SetObjectProperty(@save,"PropertyName","*")
       AddObjectArrayItem(@options,"SaveOptions", @save)
       /* Here is where we actually update the Subscriber object */
       Set @update_sub = InvokeUpdate(@sub, @update_sub_status, @update_sub_errorcode, @options)
    
       /* Set the confirmation message */
    
       set @message = "Preferences have been updated<br><br>"
    
    
     /* Else, if the user unsubscribed, upsert their record accordingly (Clear the Preferences field and flag them as Mailable = "N") and log the unsubscribe */
    
       elseif @unsubscribe == "Yes" then
    
       UpsertDE("Master_PreferenceCenter_CustomerPreferences",@email,"Mailable","Y","Preferences",@preference,"Preferences","","Last_Modified_Date",@mod_date)
    
       /* set the reason for the unsubscribe */
       set @reason = "Custom Unsubscribe"
    
       /* initiate the LogUnsubEvent request */
       set @lue = CreateObject("ExecuteRequest")
       SetObjectProperty(@lue, "Name", "LogUnsubEvent")
    
       /* configure the properties of the API object */
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "SubscriberKey")
       SetObjectProperty(@lue_prop, "Value", @email)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "EmailAddress")
       SetObjectProperty(@lue_prop, "Value", @email)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "JobID")
       SetObjectProperty(@lue_prop, "Value", @jobid)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       set @lue_prop = CreateObject("APIProperty")
       SetObjectProperty(@lue_prop, "Name", "Reason")
       SetObjectProperty(@lue_prop, "Value", @reason)
       AddObjectArrayItem(@lue, "Parameters", @lue_prop)
    
       /* this is where the unsubscribe is performed */
       set @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId)
    
       /* check status/errors */
       set @Response = Row(@lue_statusCode, 1)
       set @UStatus = Field(@Response, "StatusMessage")
       set @Error = Field(@Response, "ErrorCode")
    
       /* Set the confirmation message */
    
      set @message = "You have been unsubscribed.<br>To resubscribe, click the submit button below.<br><br>"
    
     else endif
    

For the form on the page, you can style it as desired, but within the <form> tags, the below code will need to be included. This code uses an AMPscript loop to display all of the preferences defined in the “Master_PreferenceCenter_PreferencesList” reference data extension. Within the loop, we also utilize a lookup to define whether or not the checkbox should be checked based on the user’s previously defined preferences (if available).

Again, it might be helpful to lookup some of the functions being used in the below script if you aren’t familiar with them quite yet:

  • Process Loops - https://ampscript.guide/process-loops/

  • Lookup - https://ampscript.guide/lookup/

  • LookupRows - https://ampscript.guide/lookuprows/

  • LookupOrderedRows - https://ampscript.guide/lookuporderedrows/

  • Output - https://ampscript.guide/output/

     %%[
    var @preferences, @preference_name, @header, @i, @rows, @row, @checked
    
    /* The @preferences variable pulls in the value of the user's "Preferences" field in the master preference center data extension, if available. The @userlookupcount looks on the master preference center data extension to see if the record exists and sets the @existinguser variable accordingly. */
    
    set @preferences = Lookup("Master_PreferenceCenter_CustomerPreferences","Preferences","Email_Address",@email)
    set @userlookupcount = LookupRows("Master_PreferenceCenter_CustomerPreferences","Email_Address",@email)
    
    if @userlookupcount > 0 then
    set @existinguser = "Y"
    else
    set @existinguser = "N"
    else endif
    
    /* This sets up the loop to bring in all preferences listed in the preferences reference data extension. The @header field defines what the text above the preferences checkbox will be. Set this to whatever you'd like. */
    
    set @rows = LookupOrderedRows("Master_PreferenceCenter_PreferencesList",0, "Priority asc, Preference_Name","Key","X")
    set @header = "<b>Send me emails about:</b><br><br>"
    
    for @i = 1 TO RowCount(@rows) DO
    
    /* If this is the first row pulled, place the @header variable above the checkboxes */
    
     if @i == 1 then 
       output(v(@header))
     else endif
    
     /* Pulls in the preference_name field and applies it to the @preference_name variable */
    
     set @row = Row(@rows,@i)
     set @preference_name = Field(@row, "Preference_Name")
    
    
     /* Sets the checkbox as "checked" on the page if the user has previously opted-in to the preference or if the user has not yet defined their preferences */
    
     if IndexOf(@preferences,@preference_name) > 0 OR @existinguser == "N" then
       set @checked = "checked"
     else 
       set @checked = ""
     endif
    
     /* Sets the checkbox input field that will be displayed. All of the checkboxes will have the same name and will be submitted as one comma-delimited value */
    
     output(concat("<input type='checkbox' name='preference' ",@checked," value='",@preference_name,"'>",@preference_name,"<br>"))
     
     next @i
     ]%%
    

Now your preference center data extensions are created and your page is setup and (hopefully) looking good and functioning as expected. But, how do users access this? Like we mentioned above, we’ve set this up so that users can only access via a link in one of your emails. So, for this, we’ll be using the CloudPagesURL function in a link in our email. When users click on the link, they’ll be taken to our preferences page and they’ll have all of the necessary information (email address, job id) with them. Here is an example link for your emails:

<a href="%%=CloudPagesURL(123)=%%">Update Preferences</a>

The next, and final step is optional and depends on your data setup within Salesforce Marketing Cloud. If you have a customer master data extension and would like to keep this data within your customer master data extension, then simply setup an automation to run hourly. Within the automation have a SQL query that updates your customer master with the values from the master preference center data extension.

And then that’s it, you’re done. Your customers are now able to easily update their preferences, you can easily segment them based on those preferences, and you can even somewhat easily update the preferences you offer customers.

Read more
Matt Brocious
Published 05/22/2020
SSJS
Debugging Server-Side JavaScript

This article assumes some knowledge of Server-Side JavaScript. If you are new to SSJS feel free to dive into Greg Giffords articles that serve as an introduction to the topic.

SFMC Server-Side JavaScript 1: Intro
SFMC Server-Side JavaScript 1.5


If you’ve ever found yourself multiple days into writing Server-Side JavaScript (SSJS), with a few hundred lines of code, and a script that should work… But doesn’t… Just know that we’ve all been there.

I tend to live in the area between total coding elation and utter frustration. In fact… I had arrived at Camp Frustration (that’s right, it’s an actual place), set up a tent, and camped out for the better part of two weeks. And while it was frustrating, it was also extremely rewarding and I learned so much in the process!

I had written a script that aimed at taking a very long and manual process and automating it. Exporting tracking extracts and moving them to the SFTP server for a bulk period of time (one full year at a time).

This script, at every step, interacts with the SFMC APIs to create the activity definitions needed, execute them, then delete them (because they no longer ‘spark joy’ and are no longer needed).

During the process there were moments where I felt like an API super hero, but there were also moments that made me want to toss my computer out of my office window and give up (don’t worry, I didn’t). The most glaring issue was that no matter how many iterations my loop ran, it would only create the first tracking extract; but there were no errors and everything else ran perfect!

I quickly learned the value of being able to debug and troubleshoot things (not just API calls) in SFMC.

Testing is typically done on a cloud page or a code resource page. I’ve done most of my testing from the classic > code view cloud page editor.

My first tip is something I picked up recently from a fellow EmailGeek as I was getting frustrated that the cloud page was running my SSJS code as it rendered the preview:

%%[
if indexof(requestparameter('pageurl'),"CloudPages/Preview") < 1 then
]%%
                                                                
//Your Code                                                                
%%[
endif
]%%

This script captures the page URL and checks if the context of it is a preview. Using some standard AMPscript logic, it tells the page to only load the code you are running on a non-preview page.

When you’re working with the SFMC APIs, you not only need to instruct it what to do, you you also need to tell it to provide you with the details of what it just did. But why do you need to know what it just did? We trust SFMC, right? Well… Kinda… But this serves a few purposes. These Callbacks tell us what the values of the variables we are passing into the API calls are before they are used and what the response from the API call is.

So how do we get the callback and where should we put it?

With SSJS we don’t have the console to display our debugging efforts since everything is run server-side, so we use the Write function in it’s place.

In order to make your callback accessible, your API call needs to be set as a function expression; which means calling your function as variable. This stores the response in that variable so you can display or parse through it.

var createExtract = ...

Once your response is captured by the variable, you can use the Stringify function along with Write to display your response.

Something I’ve learned in this process is to leave yourself context with the details you are displaying.

Write(Stringify(createExtract)); will provide you with the callback but Write('Create extract definition: ' + Stringify(createExtract) + '<br><br>'); will give you what the response of for and some extra space so it doesn’t run into your next callback.

Where the callback details are generated from was something that I didn’t think of until very late in the game; and it would have saved me a lot of time.

When writing SSJS, I like to use functions so my code is reusable. At the onset, I was putting my callbacks directly after I initiated my function

var createExtract = createExtract (...);
Write(Stringify(createExtract));

While this was giving me some insights into what was going on… It wasn’t the best place for it. I was not getting the details closest to where the actual call was being made.

What’s a payload? The payload is simply the data you pass through your API call. It contains all of your information to create/interact with the API object.

This was my Ah Ha! Moment.

I was checking everything after the HTTP.Post call was being made, and I thought that because I was displaying my variables before I initiated my createExtract function I was verifying the correct data was being passed (I was… And I wasn’t).

To truly verify what was being passed into my function I went back to the trusty Stringify/write combination and displayed it before my HTTP.Post in the function.

What did this tell me? By adding this response here, this showed me that after my first loop iteration, the createExtract function was either not initiating or not injecting my variables into the payload (which because it’s a function, should be the same as the first iteration).

The try/catch function is not something native to SSJS but it’s one of the functionality in Vanilla JavaScript that works with it.

As with a lot of other instances, the error messages provided by SFMC tend to leave a lot to be desired. With the try/catch block, you are able to display a more detailed error message for that specific part of your code.

Try {

//Your code

} catch(e) {
   Write(Stringify(e));
};

My initial thought was that it’s not running the function after the first iteration, but it’s holding out the error. So I wrapped my function and settled in to find out what my error was.

Try {

var createExtract = createExtract (...);

} catch(e) {
   Write(Stringify(e));
};

Much to my surprise, the try/catch had a very welcomed but unexpected effect… It fixed the issue! The current theory behind why this worked is that try/catch is local scope only (visit here for more information on scope), so nothing can be cached for the second run. In the event that we are able to get more insight into this issue and why the try/catch worked, I will update this article.


The next time you find yourself waist high in code with no idea why it doesn’t work and you’re ready to throw in the towel (and the laptop too!) remember there are some very useful methods you can use to help troubleshoot and debug your code.

Keep in mind, this is not an exhaustive list of debugging techniques. There are other methods and best practices to keep in mind as you work through writing and troubleshooting your code.

Read more
Tony Zupancic
Published 05/16/2020
Release Notes
May 2020 Release Notes - Summary & thoughts

It’s that time again - we’ve got an upcoming release and as of today (16th May) there’s some interesting elements being included or introduced as part of this bundle of joy. After a couple of the more recent releases that have felt somewhat underwhelming and some that added simple but needed functionality, there’s some promising new features due to hit our instances when the release lands.

New training modules are coming/have arrived and been given some prime real estate in the release notes. There’s 3 new modules and an official Trailmix for someone getting started in SFMC. As with all things SFMC on trailhead, you don’t get a developer environment to play with for these features and it doesn’t look like there’s any simulations for these just yet either, but it’s a worthwhile read and some points if you go hunting for those.

I do have one bug bear with the content of the Marketing Cloud Account Optimization content for auditing your account.

Is there somewhere else besides a Data Extension that I don’t know about for storing data within SFMC? If there is, I’d really like to know because if it’s such a high threat - everyone should probably be working that one out!

Either way - I did the badge because of this article. There’s some useful content there if you’ve never tried to audit or optimize an account. No new audit tools unfortunately. Will I be adding any of these modules to the standard Trailmixes that colleagues who I work with to show them around SFMC? Probably. But probably only the interactive email modules that have been released. Not because I believe Interactive Email in its current guise is going to add value to marketing comms. But, I do believe it is a powerful module to help people understand more of the art of the possible in Email Marketing.

!alt

The new Journeys section appears to be an amalgamation of all of the individual studios in one place. There’s content for Web Studio, Content Builder, Push, LINE, Classic Content and basically everything that used to have it’s own dedicated part of navigation. So if you’re looking in the documentation at the release notes and not finding what you’re hoping for - it’s probably under Journeys.

!alt

  • Selecting Image URLs from Content Builder no longer needs a visit to the properties view so that should save some excess clicks
  • Push notifications built in line within Journey Building bringing some parity between Push and other channels like Email and SMS
  • Push notifications now being available for Single Step Journeys (No, I don’t entirely know why a single step journey is called a Journey. It’s just a batch send with humorously, more steps involved) but it definitely starts to bring Push support more mainstream in SFMC. So fingers crossed the development kits and capabilities are made more accessible and simpler to use for some basic implementations. (If anyone reading this is an avid user of Mobile Push and would be interested in writing an article about it from their perspective - do reach out to us!)

The new features cover a wide spectrum of channels and levels of technical experience required, but in summary it looks like there’s a lot of things happening - but I believe there is less in this release but it appears to be a lot more useful new features.

There is a new Cloud Pages experience that users will be able to trial if they currently have Cloud Pages available. You will be able to swap between the two because the underlying changes are happening to the same place. Definitely worth having an investigation, especially with no real indication about what’s changing. Except for FINALLY having the ability to copy pages like you can with classic content. But even that is a footnote in the release details. Fingers crossed it’s as useful as it sounds for those who have multiple similar cloud pages to deploy or a template to use.

You can now preview Interactive Email Blocks & if you’ve got a lot of data in a complex file structure - you’re no longer limited to 31 folders worth when trying to test an email. So a couple of useful new things to streamline some QA steps, one would hope!

I really don’t know what took so long, but we can finally pause a Journey rather than just have to stop and design a way of bringing people into a stopped journey in the right place or just treat them as half-complete and hope for the best. That said, it’s not a permanent pause it is only for 14 days. So if there’s something happening in a specific instance that means you need to suspend for longer than 14 days, it may still be better to stop and determine a longer term contact strategy. But, 14 days is a long time to be able to work that up so the pause is definitely a welcome addition!

Probably one of the two biggest elements of this release from a Marketing Cloud capabilities perspective is the new Path Optimizer in Journey Builder. A technology requirement that features heavily in a lot of marketing functions is the ability to A/B Test & Winner Pick. But, until this release, Journey Builder hasn’t been able to do that - despite the Sales team trying to convince us otherwise. But, if Path Optimizer works well, this has the potential to go a long way.

Things to be conscious of when using it - Journey Builder has limitations for how many steps to include for it to be performant (150-200 is listed in the documentation). So, if you’re looking to optimise at a segment level - this could lead to a lot of bloat in your journeys impacting performance. Once it’s live and GA, we’ll test it and hope to share some of our experience (if you use it after it goes live and would be interested in sharing your thoughts, it would be great to get some additional views on this! Let us know here)

It’s still being retired. This has been one of the longest retirement tours ever, but it is still being retired.

This is the second biggest announcement from my experience. Given that Contact Count is a billable volume in SFMC, it’s been ludicrous that there has been no straightforward method to manage this or clean out data that has no value. There have been workarounds historically here where the general situation was “if you didn’t pay for Mobile Studio when you signed up, you’re outta luck”. So it’s definitely a step in the right direction. Hopefully the complex days of contact management go out the window and this can supersede the hacky workarounds.

Einstein has been a common theme in the last few releases with a range of new features being added, moved around, renamed etc. but one thing has stayed consistent - Einstein won’t tell you how it works. Which for some data scientists makes it a no go, not understanding how the data is being used or processed and just hoping it works isn’t a part of the scientific method. However, the wording on the Send TIme Optimization release note does add some hope for some visibility _“Marketing Cloud’s Einstein Send Time Optimization model card provides insight into how our artificial intelligence (AI) model works.” _If Einstein will start to tell me how it works, then I think it’ll be an easier recommendation.

The inclusion of the Random Sending Path for STO is a welcome addition, previously an audience would need to be split prior to a Send Time Optimization node making a journey a little clunkier. Looking forward to testing and validating this one!

Important date to keep in mind (31st July 2020) if you’re using the MC Connector - make sure you update to the Connected App authentication as opposed to the legacy authentication. You’ve not got long before your integration will stop working if you don’t do it. You’ll need to block out a couple of hours to get it sorted, but it’s a couple of hours you can choose rather than an integration failure that’ll come bite you on the rear!

This looks to be a promising release with some great new features being included for end users, marketers and developers alike. Top 2 callouts for this set of features is the Journey Path Optimizer and the Contact Cleaning tools being made available. But I’m keen to try the new Cloud Page experience and if you’ve ever wondered how to audit a Marketing Cloud instance - there’s a module to take a look through on Trailhead that covers that in some high level detail.

There are some elements not covered here that I’ll call out specifically as references:

  • AMPScript AttachFile function getting some additional features including: 4 new mime-parts & an optional extra to determine if the default function is to inline or attach the file.
  • Deleting contacts via the API now only needs a ContactKey as opposed to a ContactID and a ContactTypeID so you can get remove contacts in one delete request instead of for each ContactTypeID
  • Removing content from the Content Builder CDN using the API rather than just overwriting it as a workaround
  • Go To Marketing Cloud links removed from the Connected App in Salesforce core platforms for security purposes. You’ll now have to navigate to SFMC manually
  • With the sunsetting of Classic Content - The ability to A/B test from Sales & Service cloud is no longer available
Read more
Jason Cort
Published 05/11/2020
Winning Solutions
Frequency Filtering for Email Subscribers

How often does your marketing team send out communications to a subscriber? Are the subscribers in your database getting too many emails in a week? Does this lead to many unsubscribes and deliverability problems? Are you able to control the emails that are sent to a subscriber for a specific duration? We can help resolve this through the technique of Frequency Filtering.

There are multiple ways you can achieve Frequency Filtering – and the solutions can vary for each organization depending on their processes. In this article, we will look at a simple approach on how to implement Frequency Filtering in your Salesforce Marketing Cloud account using the following steps:

  • Pre-Configuration: Using Profile Attributes
  • Send Data Filtering: Send Log and Filter Activity OR Data Views and Query Activity
  • Subscriber Exclusion: Using Exclusion Script

Now, let’s look at the detailed steps on how to set this up in your instance. For this solution, we will assume that you wish to restrict emails being sent more than once per day to a subscriber – i.e.

  • Frequency Duration = Daily
  • Max email sends allowed for a subscriber for the Frequency Duration = 1

You can tweak these values on the solution as needed – for e.g. some organizations may need the Frequency Duration to be Weekly – i.e. subscribers should receive an email only once a week.

Pre-Configuration

The Pre-Configuration step involves creation of a Profile Attribute to capture the number of maximum Email Sends per duration that should be allowed for a subscriber. You may keep this attribute as hidden if your marketing team would like to control the send frequency. However, if you wish to give the subscribers the option to control the frequency of with which they may receive emails, then this field can be exposed on the Profile Center.

<ol> <li>Go to Email Studio > Email > Subscribers > Profile Management</li> <li>Click on “Create” button to add a new Profile Attribute with the following properties:</li> <ol> <li>General Tab</li> <ol> <li> <b>Name</b>: Max Daily Email Send Count</li>
<li> <b>Hidden</b>: Checked (if to be controlled internally, else can leave it unchecked)</li> </ol>
<li>Data Tab</li> <ol> <li><b>Data Type</b>: Numeric</li> <li><b>Default Value</b>: 1 (you may change this default value as decided by your marketing team’s process – if you leave it at 1, then this means the send to this subscriber will be restricted to once per frequency duration)</li> <li><b>Max Length</b>: 3 (you may even keep this as 1 if you are sure you won’t need to send more than 9 times to a subscriber during a frequency duration)</li> </ol> </ol> </ol>

!screenshot of second tab on modal

Once the MaxDailyEmailSendCnt profile attribute has been saved, we can move to the next step – which is where we filter the Email Send data based on the desired Frequency Duration.

Send Data Filtering

Option 1: SendLog and Filter Activity

In the case that your organization is using Send Logging, then you may use the SendLog Data Extension to retrieve the Email Send information.

<ol start=“1”> <li>If you do not have <b>Send Logging</b> enabled, then go ahead and create the <b>SendLog Data Extension</b>.</li> <ol> <li>Go to “Contact Builder” > “Data Extensions”, create a new “Standard Data Extension”</li> <li>Select “Create” from Template option – and choose the SendLog template as shown below</li> <li>Save the Data Extension – it will show several pre-populated fields as part of the template</li> </ol>

<img src=“https://lh5.googleusercontent.com/lb-SxCMBlk1wTtl69TK04KkHosXY03jV6gUwgnX3_USIXSvBDroF6xLQK-poZtP7ghMxIfyX8P2L5vDt5u3sVjRiwxqbkOYYqoHjZ-NR1RjxzO01gDZRV2hUw8i-YOWiOJJqIO5ZhsTfG0y0mg” width=“100%” class=“py-5”/>

<li>Now add 2 more custom fields to the SendLog DE (if you have an existing SendLog DE, do check and add if these fields do not exist):</li> <ol> <li>Email Address – (type = EmailAddress) – this should match with the Profile Attribute “Email Address” on the Subscriber so that the email id of the subscriber gets populated in the SendLog DE during send time.</li> <li>Send Date – (type = Date, Default Value = Current Date) – this will get populated with the date on which the send happens for each record being entered into the SendLog</li> </ol> </ol> <img src=“https://res.cloudinary.com/howtosfmc/image/upload/v1588385953/shibu_de_insert_wnz0hs.png” alttext=“DE Insert screenshot” width=“100%” class=“py-5”/>

The SendLog DE would now store all Email Send details for the Account / Business Unit (depends on how it has been previously configured). We can filter this Email Send Data for a specific duration and then use the same for the final Exclusion step.

<ol start=“3”> <li>Create a Filter to segment subscribers from the SendLog DE with email id who were sent one or more emails for current day </li> <ol> <li>Go to Email Studio > Subscribers > Data Filters </li> <li>Create a new Filter with following properties:</li> <ol> <li><b>Name</b>: SubscribersSentEmailToday</li> <li><b>Source</b>: SendLog Data Extension</li> <li><b>Filter Criteria</b>: SendDate is equal to Today</li> </ol> </ol> </ol>

This Filter would segment and provide all subscribers in the SendLog DE that has a SendDate value of the current date – which means, all subscribers who were sent emails today.

!

Now if you want a different Frequency Duration – let’s say 7 days instead of daily, then all you need to do is change the Filter criteria in this step accordingly. For e.g. you can check for the “SendDate to be after Today – 7 Days”.

Sometimes, you may have a business scenario where you do not want Triggered Sends to be counted towards the Frequency Filtering as it may be used for sending transactional emails. In this case, you can add the Filter criteria to include only the records that have an empty TriggeredSendID. The resulting records in the Filtered Data Extension would not have any subscribers that were the recipients of a Triggered Send – however if they also did receive a non-Triggered Send email, then their record would populate in the data extension.

!

<ol start=“4”> <li>The third step would be to create a Filter Activity and schedule this through an Automation Workflow</li> <ol> <li>Go to “Journey Builder” > then go to “Automation Studio” > Activities > Filter</li> <li>Create a new “Filter Activity” with the following properties:</li> <ol> <li><b>Name</b>: Refresh Todays Email Sends Info</li> <li><b>Filter Definition</b>: SubscribersSentEmailToday (select this Filter that we created in step 3)</li> <li><b>Target Data Extension Name</b>: TodaysSendDE</li> </ol> <li>Save the Filter Activity</li> <li>Go to the Overview Tab and click on New Automation</li> <li>For Starting Source, pull in Schedule</li> <ol> <li>Configure the Schedule based on how often you wish to run this Filter Activity Refresh</li> </ol> <li>Under Step 1 of the workflow – pull in Filter Activity – and select “Refresh Todays Email Sends Info” that we created in 4b</li> <li>Save the Automation Workflow – you may run this manually using the Run Once option or schedule it to run every few hours or daily depending on your requirement.</li> </ol> </ol>

!

Please do note that a Filter Activity overwrites the data in the target Data Extension (in our case TodaysSendDE) – so as long as the Filter criteria is met, the data will be populated in the target DE. You will need to choose the appropriate times when this Activity should run daily (probably in between multiple Email Sends that you have) in order to have the updated data available for Exclusion.

<ol> <li>Once the Automation has run, go to Contact Builder > Data Extensions</li> <li>Select the TodaysSendDE – verify that the appropriate Send records have been populated based on the criteria in our Filter</li> </ol>

Option 2: Data views and Query Activity

In case your organization is not using Send Logging, or you are unable to get all Email Send info in the SendLog DE, then we have another option to retrieve the Subscriber Send information. This time, we will use the System DataViews and a Query Activity.

<ol> <li>As a first step, we need to create the target Data Extension that we would be using for the Exclusion step</li> <ol> <li>Go to Contact Builder > Data Extensions</li> <li>Create a new Data Extension</li> <ol> <li>Name:TodaysSendDE</li> <li>Data Retention: Keep an appropriate retention policy (we will use the Overwrite option from the Query Activity so that old records will be removed automatically during the Query update)</li> <li>Leave Sendable as un-checked as we will not be using this Data Extension for Email Sends</li> <li>Create the fields in the DE as shown below and save</li> </ol> </ol> </ol>

!

<ol start=“2”> <li>Once the Data Extension is ready, let’s build the Query Activity to fetch the Email Send Data.</li> <ol>
<li>Go to Journey Builder > Automation Studio > Activities</li> <li>Create a new Query Activity with following properties:</li> <ol> <li>Name:Retrieve Todays Email Send Data</li> <li>Target Data Extension:TodaysSendDE (that we created in Step 1)</li> <li>SQL Query: As shown in the code snippet below</li> </ol> </ol> </ol>

!

!

<ol start=“3”> <li>Save the Query Activity and now let’s use this in an Automation Workflow.</li> <ol> <li>Go to the “Overview Tab” and click on “New Automation”.</li> <li>For “Starting Source”, pull in “Schedule”</li> <li>Configure the Schedule based on how often you wish to run this Query Activity</li> <li>Under Step 1 of the workflow – pull in <b>Query Activity</b> – and select “Retrieve Todays Email Send Data” that we created in 2b.</li> <li>Save the <b>Automation Workflow</b> – you may run this manually using the Run Once option or schedule it to run every few hours or daily depending on your requirement.</li> </ol> </ol>

!

<ol start=“4”> <li>Once the Automation has run, go to Contact Builder > Data Extensions</li> <ol> <li>Select the TodaysSendDE – verify that the appropriate Send records have been populated based on the criteria in our Filter</li> </ol> </ol>

Subscriber Exclusion

Now that we have the Email Send information for the desired Frequency Duration in the TodaysSendDE Data Extension, let’s see how we can use this for excluding subscribers from being sent emails beyond the limit that we have set in the Profile Attributes (as part of Pre-Configuration).

  1. For excluding Subscribers during each Email Send, we can use an Exclusion Script that checks the number of records a Subscriber has in the TodaysSendDE and then compares it with the value of that Subscriber’s profile attributeMaxDailyEmailSendCnt.

In the desired Email Send definition(s), under Target Audience, include the Exclusion Script as shown below:

!Row Count and subscriber exclusion.

This Exclusion Script uses AMPscript functions – if the condition in an Exclusion Script evaluates to True, then the Subscriber is excluded from the current Send. The script is evaluated for each individual Subscriber that is in the Target Data Extension of the Send.

Script Demystified

The Exclusion Script uses the following functions:

  1. LookupRows – This AMPscript function returns a set of rows from a Data Extension based on a lookup Column and lookup Value. In the above solution, we use the Subscribers emailaddr value (which is a system attribute – hence not in double quotes) and check it with all the values in the “Email Address” column of the TodaysSendDE Data Extension – and then return the rows that have a match.
  2. RowCount – This AMPscript function returns the count of rows of a Rowset – when used with the LookupRows function, we can identify the number of rows returned from the Data Extension that satisfies the lookup condition. In our solution, this should give us the total number of records the current Subscriber has in the TodaysSendDE.
  3. AttributeValue – This AMPscript function returns the value of an attribute based on the context of the Subscriber. In our solution, we use this function to return the value of the MaxDailyEmailSendCntprofile attribute for the current Subscriber.

In summary, the Exclusion Script checks for the number of rows a Subscriber has in the TodaysSendDE (which has the list of all subscribers that was sent emails in the desired Frequency Duration – e.g. Daily, Weekly, etc.) and if this is greater than the value that was set in the Profile AttributeMaxDailyEmailSendCntto control Send Frequency, then the condition will return true and the Subscriber will be excluded from the Send.

Let’s say we have a Subscriber A that hasMaxDailyEmailSendCnt = 1and was already sent an email today. If we are going with a Daily Frequency Duration, then we can run the automation workflow and have his data added to the TodaysSendDE. Subsequently, if the Exclusion Script is added for the next Send today, then the condition would return TRUE as (1 >= 1) for this Subscriber A – and hence he would be excluded for this and all future sends today.


Conclusion

We have now seen how we can set up a Frequency Filtering mechanism in our Salesforce Marketing Cloud account. In order to change the limit for the Frequency Duration, teams can just change the profile attribute value. In the above solution, we have used Email Address for the lookup condition – this means that if we have multiple subscribers with the same email id (for e.g. say members of a household), then all of them would be restricted from the subsequent sends based on Frequency Filtering.

In case you wish to use the Subscriber Key instead of the Email Address, then you can use the _subscriberkey system attribute in the Exclusion Script as shown below:

!Attribute Value with Row Count visualization of code.

Overall, a marketing team or the ones formulating the strategy whether they are internal resources or agency partners must take into account the number of records in the SendLog or the DataView Query before implementing this solution.

In scenarios where the size of the audience is too large and it may not be feasible to run Refresh and query activities multiple times, then this solution may not be feasible. This is because the effort involved for larger projects may be too complex at times that leaves it open to human error without sufficient time for quality assurance to occur.

Read more
Shibu Abraham
Published 05/05/2020
Tutorials
SFMC Server-Side Javascript 1.5

As a warning, this article assumes certain knowledge. _ This link _ should provide you with any background or refresher information if needed. This is the second article (but only half step forward) in an ongoing series, please visit _ here _ to read the first article. This article is intended to give some background on what Server-side JavaScript is and basics to help make the next article make more sense.


JavaScript is a cross-platform, object-oriented scripting language. An Object Oriented Programming (OOP) language is defined if it can provide the following four basic capabilities to developers: Encapsulation, Aggregation, Inheritance and Polymorphism. JavaScript is designed for embedding in other products and applications – such as web browsers. It is heavily intertwined with HTML and CSS, but focuses mostly on creating dynamic and interactive elements. JavaScript contains a core set of objects:

These objects are the base objects for JavaScript to operate and interact with the environment. There are other ‘built-in’ objects that are available, but there are too many to effectively list out here. Here is a good link to check out a list. Please note that some of these are not available sever-side. JavaScript allows you to implement complex features utilizing:

  • Storing information in variables
  • Operations and evaluations
  • Event based scripts
  • Application Programming Interfaces (APIs) JavaScript contains two different contexts – server-side and client-side. Vanilla JS is shared between the two contexts, but most have their own libraries/environments specific to the context. Client-side code is code that is run on the user’s computer — when a web page is viewed, the page’s client-side code is downloaded, then run and displayed by the browser. It is mostly for displaying and manipulation of HTML in a windowed environment. This environment is run solely in the browser or ‘local user’ environment and is dependent on the browser/software that the user has. This means that all of your JavaScript is then fully exposed for the user to see via source code. Any information you use in your scripts, such as user/pass, will be able to be viewed by any visitor of the site – opening up security risks. Server-side code in contrast, is designed to work with resources outside the windowed environment – utilizing the ‘home’ server environment. It is able to interact with relational databases, API calls to other systems, or manipulate objects on the server prior to response rendering. Generally, server-side Javascript is run in a specific environment (like Node.js), which is run differently than vanilla JavaScript. The server script is not passed along to the requester, only the results of this scripting. So you can utilize ‘sensitive’ information in here (with appropriate security measures) without worrying that the end user will be able to see it.

Server-side JavaScript contains the same core language as the client-side version, but it does not include DOM (Document Object Model) or BOM (Browser Object Model) capabilities.

Overview of Runtime processing: (for example, a web page)

  1. User accesses the URL via Browser. The browser sends request to server for page
  2. The server finds the appropriate file and begins processing
  3. The runtime engine constructs the HTML page that will be sent over
    1. It first begins with those that are set to run at server
    2. It then compiles the results of this along with the remaining HTML/CSS/JS to be shared
  4. The runtime engine sends the new, compiled HTML page to the client
  5. The web browser then interprets all client-side JS that was in the compiled page, formats the HTML and then displays the page As you can see from above, the SSJS is run before the HTML page is compiled fully and sent to the browser. Essentially what it does is scan the page from top to bottom and each time it sees a runat=server it will then process and output that script instead of moving it across as content.

It then continues on with the scan until it reaches the end of the file. At this point it will have the ‘final’ rendered page that it sends back to the browser where then all the client-side scripting is run and processed.

Hopefully soon I will release the next full step forward to the basics of utilizing SFMC Server-side JavaScript.

Read more
Greg Gifford
Published 05/03/2020
Tutorials
Introduction to making API calls to SFMC from POSTman

API calls are one of the most powerful tools available to an SFMC user. It essentially allows you to make changes, perform actions, retrieve data and more all programatically via an outside resource.

Below I will give a quick overview on setting up an API integration package and then utilizing it in POSTman. This includes, creating the package, the component and gathering the required information from Salesforce Marketing Cloud down to creating the environment and setting up an authentication call and test calls inside of POSTman.

First you will need to go into the ‘Setup’ section which is under your user name in the top right of the screen. You will need to hover over it and the dropdown should have one of the first options being ‘Setup’.

From there you will want to navigate on the left sidebar to ‘Platform Tools’ and then expand ‘Apps’ and select ‘Installed Packages’.

!SFMC Packages

Click on ‘New’ in the top right. This will open a popup asking for name/description of your new package.

!SFMC Package Details

You then want to hit save to create your package.

Next part is the creation of the component inside the package to allow utilization of API integration between your external system and SFMC.

After building the package, this will then open up a screen with your new Package overview. The part we want to concentrate on is the bottom that involves ‘Components’. You will want to click on ‘Add Component’.

!

This will open a new popup to select the component type. For the purposes of this article, you will want to select ‘API Integration’. Then Click Next at the bottom.

!SFMC Component

Next you need to select your integration type. For this article, I am going to choose ‘Server-to-Server’ as this is only going to be used via POSTman or similar services. For more information on Component Types, see official documentation here.

!SFMC Integration Type

You will then need to assign the scope of your package (see official documents on scope here. For this article, I am going to allow full permissions on my package – basically allowing full access to the whole platform. After testing, you should make sure to go back and assign scope on your component as appropriate for your intended usage.

!SFMC Component Scope

You then hit save and the new component will be added to your package.

!SFMC Completed Component

You then will want to grab the following fields for use in your API call:

  1. Client Id
  2. Client Secret
  3. Authentication Base URI
  4. REST Base URI
  5. SOAP Base URI
    (make sure to append /Services.asmx to the returned endpoint)

These will be used inside your POSTman API calls.

EDIT I wanted to give a quick thank you to Vishal Kumar C V and Renato Oliveira for bringing up that I did not include the append needed to be added to the SOAP Base URI (/Services.asmx) for the SOAP API to work. See more details inside of this Stack Exchange post.

Next we will work on setting up an Environment in POSTman to best utilize your new Package in SFMC.

In POSTman you will want to navigate to the top right corner and select the gear icon to ‘Manage Environments’

!POSTman Environment Setup

You will then want to click the ‘Add’ button at the bottom to open the following window:

!POSTman add environment

This is where you will add in the endpoints and id/secret information to your environment. What I would also recommend is adding a field named ‘accessToken’ that you can use to hold your returned Token from your Auth call. You do not need to insert a value into this field

Sample completed Environment below:

!POSTman completed environment

You will then want to click the ‘Add’ button at the bottom. This will then add your environment to your POSTman.

Now that you have your environment set up, you need to aim at retrieveing your token so that you can do your REST/SOAP API calls. First things first, verify that your environment is selected in the dropdown in the top right of your POSTman.

Below is a quick overview of the call, that you can use if you have familiarity with POSTman to set up your call:

POST /v2/token 
Host: {{authEndpoint}}
Content-Type: application/json

{
"grant_type": "client_credentials",
"client_id": "{{clientId}}",
"client_secret": "{{clientSecret}}",
"account_id": "{{mid}}"
}

For the visual learners, use the below images to help figure out where to put this information:

!POSTman Auth Call Headers

!POSTman Auth Call Body

You will notice in my call, I have included a parameter account_id with a replacement named {{mid}}. This can be used to switch between BUs by assigning an MID to your environment and filling it in here. Please note that this can be included with mid being empty or null and it will still function as expected.

Another quick note to help auto-store your authentication is to utilize a script that can be added in the ‘Tests’ section in your Auth call. See below image and snippet:

!POSTman Auth Call JS Script

var data = JSON.parse(responseBody);
postman.setEnvironmentVariable("accessToken", data.access_token);

By adding this JS in, on each call of this, it will update the ‘accessToken’ var in your environment with the returned token in your Response.

After setting all of this up, you will want to click the ‘Send’ button in the top right, near where you set your environment.

Example Response:

!POSTman Auth Call Response

To test and make sure your environment and access token are set up properly, I am going to provide a very simple test API call. This call is basically a discovery call to gather all endpoints under a specific section. For this sample, we will use the ‘contacts’ section.

See below extract and image for setting up this call:

GET /contacts/v1/rest
Host: {{yourSubdomain}}.rest.marketingcloudapis.com
Authorization: Bearer {{yourToken}}
Content-Type: application/json

!POSTman Discovery Call

You then want to hit send in the top right. This should then return something like the below in the Response section:

!POSTman Discovery call response

Congratulations! You have successfully completed a REST API call to your SFMC BU.

For this sample, I am going to do a quick example Retrieve on a Subscriber via SOAP. There are certain differences that need to be noted in SOAP that are not in REST.

Here is a down and dirty list:

  1. Method will always be POST
  2. Requires a header key of ‘SoapAction’ – value will mimic the request type. E.g. RetrieveRequest will be SoapAction = Retrieve
  3. Authentication is inside of the payload (body) of the call, not in the header.
  4. SOAP utilizes XML for the payload, unlike REST which uses JSON
  5. The endpoint that SOAP aims at is universal for all calls, the payload/body determines content and action
  6. SOAP has Methods (declared inside of payload/body) as well as Objects that are to be utilized to determine action and result.
  7. SOAP requires explicit declaration of Properties in retrieve calls

Below is an extract of the SOAP call on retrieving a subscriber:

POST /Service.asmx 
Host: {{mySubDomain}}.soap.marketingcloudapis.com
Content-Type: text/xml
SoapAction: Retrieve

<?xml version="1.0" encoding="UTF-8"?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <soapenv:Header>
    <fueloauth>{{authToken}}</fueloauth>
  </soapenv:Header>
   <soapenv:Body>
      <RetrieveRequestMsg xmlns="http://exacttarget.com/wsdl/partnerAPI">
         <RetrieveRequest>
            <ObjectType>Subscriber</ObjectType>
            <Properties>SubscriberKey</Properties>
            <Properties>EmailAddress</Properties>
            <Properties>Status</Properties>
            <Filter xmlns:q1="http://exacttarget.com/wsdl/partnerAPI" xsi:type="q1:SimpleFilterPart">
               <q1:Property>SubscriberKey</q1:Property>
               <q1:SimpleOperator>equals</q1:SimpleOperator>
               <q1:Value>{{mySubkey}}</q1:Value>
            </Filter>
         </RetrieveRequest>
      </RetrieveRequestMsg>
   </soapenv:Body>
</soapenv:Envelope>

Below are a few Screenshots to help provide further context:

!POSTman SOAP Headers

!POSTman SOAP Body

This should then return a SOAP envelope with the requested fields shown above inside of it. See below image showing a response to the above call:

!POSTman SOAP Response

Congratulations! You have now successfully created, implemented and performed an API call to SFMC!

For future API calls, you can check out this POSTman library of some sample SOAP and REST API calls to get you started. I believe they utilize hostEndPoint instead of endPoint in their environment variables. You may consider adjusting your Environment to fit those parameters if you utilize that library.

Read more
Greg Gifford
Published 04/29/2020
Tutorials
SFMC Server-Side JavaScript 1: Intro

Marketing Cloud Server-side JavaScript is one of those topics that most people do not like talking about. Most people seem to acknowledge it exists, and then move on to another topic as fast as possible. Why? It is a very powerful language and has a plethora of uses, why does it seem to be swept under the rug?

My theory is that the stigma on SSJS is the same as the one around Email HTML. It is not regular HTML, it instead is almost its own little language that looks like HTML. It has different rules, requirements and guidelines/best practices than web HTML and has no standards or regulations.

No one liked talking about Email HTML until enough industry experts ‘blazed the trail’ and produced ‘how tos’ and resources to get people up to speed and comfortable with it. (HUGE shout-out to all those Email-Geek heroes that made email marketing what it is today)

This is where I think SSJS for Marketing Cloud needs help. No one wants to talk about it because no one wants to accidentally show ignorance or get embarrassed by passing incorrect information. I think this stems from there being no real definitive reference or tutorial, like AMPscript.guide for AMPscript. Plus, there are a lot of people that incorrectly snub SSJS saying that AMPscript is better in all areas and it makes SSJS obsolete. These people need to learn how wrong they are – each language is very powerful and shines in different situations or use-cases.

Sure there are the official documents inside of SFMC help docs, which do contain a ton of awesome information and tips and tricks. But they are not the easiest thing to read and honestly have an expectation of knowledge in certain areas that the majority of people do not have – creating confusion. Sadly, there are also some samples or references that simply are not true, incomplete, are missing reference information or just do not work like explained, which causes even more confusion.

This means that a lot of people may no longer use those docs as reference because all it does is make things ‘worse’ for them. They instead will go to places like SF Stack Exchange, Email-Geeks channels, blogs or the Success Community/Groups to research or ask their questions on specific tasks or needs.

A lot of these places and resources are super helpful, but they also provide only specific information on a specific use case. Getting the answer does not mean that you have learned the solution. This means that it is not a good reference place to learn the language or to further your knowledge on it as there will be gaps and misunderstandings.

Learning vanilla Client-side Javascript is certainly helpful, but same as learning HTML 5 in order to learn Email HTML – it can lead to confusion as certain functions/capabilities and best practices for Client-side are simply not possible in SSJS. Of course, to make things worse, there are also things in SSJS that are not possible in client JS as well.

So how the heck DO we learn SSJS for Marketing cloud? Honestly, right now its just through a ton of trial and error. You need to research piecemeal in a bunch of places, scouring documentations to try and find options that ‘may work’. Then pray you can implement or adjust it accordingly to work in Marketing Cloud. Basically, in order to advance in SSJS right now, you need to constantly mess up and fail. Which can be a great learning method (e.g. ‘Trial by fire’), but it is very inefficient, frustrating and time consuming.

Far from optimal for sure. My goal here is to put forth a couple articles to help begin this conversation and in the future put SSJS on near equal footing to AMPscript conversations.

In a nutshell, SSJS is an older version of vanilla JS that is processed on the server instead of on the client-side computer. Due to it processing on Marketing Clouds server instead of client-side, it does not work with the DOM nor does it function with external libraries.

SSJS is mostly used inside of landing pages/Cloudpages, but it does have uses inside of messaging as well as in Automation Studio Script Activities – which, without some ‘fancy footwork’, cannot use AMPscript at all.

Most of the functions/Capabilities that are native to JS (and not browser/DOM dependent) are allowed inside SSJS. Things such as:

SSJS is separated into two ‘sections’. You have the Platform class and the Core Library. I know we just stated that external libraries do not work with SSJS, but Core is a library that is created by SF and hosted on the SF servers, so it is an exception to this rule. The rule is intended to apply to ‘user-defined’ external libraries.

In a future article, I will go over Platform and Core in more detail, but below is a quick synopsis on them:

Platform SSJS: Platform is very closely related to AMPscript, containing many of the same functions and uses. There are a few differences in syntax and capabilities (Cough Cough WSProxy Cough) that can help decide which language is better for your use-case, but that will need to be in the next article.

Core SSJS: Core is, in a nutshell, SSJS functions that are built to do SOAP API calls. Core mostly can only be used inside of a Cloudpage or landing page environments and not in messaging. Core opens the gate to be able to interact with SOAP objects ‘behind the scenes’ inside Marketing cloud through a simpler and more familiar method (to those that already have background in JS).

Hopefully this was a helpful introduction into SSJS and helps pave the way for my future articles. My next one will be a deeper dive into the ‘basics’ of SFMC SSJS and syntax. Then my next focus will be on Platform and Core and their usages (including where WSProxy fits in). Let me know anything you are interested in around SSJS and I will be happy to try and work it in.

Until next time, keep safe and healthy!

Read more
Greg Gifford
Published 04/10/2020
Winning Solutions
Building Reusable Email Templates

Emails need to look good and email developers need to make sure they look good on all devices and browsers, however creating a custom email layout for every campaign send can be an incredibly resource consuming task. Copying emails or reusing elements from previous sends can save some time, however it’s still a laborious task.

Designing email templates to be modular and reusable is an effective way to increase speed to market without compromising on email design choices.

Disassembling an Email - Template, Layouts and Content Modules

!Modular Layout

When building a reusable email template, it helps to visualize the design areas as layers that stack on top of each other - like a cake!

Our email cake will have 3 layers:

  • Template: The foundation layer that holds the key HTML & CSS structure to support the other 2 layers.
  • Layouts: The middle layer of the email that uses style classes built in the Template to change how the email looks on different devices.
  • Modules: The top layer and final touches of content, containing pre-formatted HTML, text and images; examples such as a Header, Call-Out, Product Banner, etc.

Just like a real cake, your Template “base” will rarely change, however you may have a few different sizes or styles to suit different use cases. The Layout “middle layer” is where the email/cake begins to take shape. The Module layer is the icing on top, here you have full creative control and can make the final product look exactly as needed.

Now that we’ve covered the basics, let’s look more closely at each of these layers.

Template - The Email Base Plate

!Desktop Squish Stack Hide for Email Templates

The template is the bottom layer of your email and contains all the key HTML tags to support your email. This includes the <head>, <style> and <body> tags.

The Stylesheet in your template needs to accommodate anything your email end-users could want to build, so a comprehensive list of style classes is recommended to ensure no further customization is needed. Make sure these styles are designed inline with your practical use cases, as redundant style classes (stylesheet bloat) can have an impact on your viewable email length in some email clients.

Layout Framework - Stack, Squish and Hide

Looking critically at the most common email designs, there are only a handful of unique layout configurations that affect how a responsive email displays. Here are the 3 layout methods that I use to fulfill my design needs:

  • Squish: Elements remain side-by-side and get narrower on mobile.
  • Stack: Elements can move from side-by-side on desktop to “stacked” on mobile.
  • Hide: Elements can toggle hide/show between desktop and mobile.

Here is a sample StyleSheet that can achieve these functions:

<style name="responsive" type="text/css">
.responsive-show { 
	display: none !important; 
}

@media screen and (max-width: 500px) {
    .responsive-50 {
        width: 50% !important; 
        height: auto !important;
    }
    .responsive-100 {
        width: 100% !important; 
        height: auto !important;
    }
    .responsive-100-block {
        width: 100% !important; 
        height: auto !important; 
        display: block !important; 
        float: left !important;
    }
    .responsive-hide { 
        display: none !important; 
    }
    .responsive-show { 
            display: block !important; 
    }
}
</style>

Code Explanation

The most important element in this stylesheet is the @media declaration. This element is stating that the styles contained within will only activate when the user’s screen is under 500px wide.

Within the @media statement there are a series of styles that produce different responsive layouts. To achieve the “squish” layout, we can use the styles contained in“.responsive-50” and “.responsive-100”. These styles force their element to become 50% or 100% width of the available space, allowing elements to share the same row.

The “stack” layout is achieved with the “.responsive-100-block” style, as it contains instructions to push any following elements below it, denying elements from sharing the same row.
The “hide” layout can be achieved using “.responsive-hide”. This style uses the display attribute to hide elements in mobile view. The reverse can be achieved with “.responsive-show”, which in the stylesheet above will only make an element visible when viewed on a screen width less than 500px.

Take a look at the attached HTML file for an example of how these layouts change between desktop and mobile screen resolutions.

These 3 responsive behaviors can be applied to single and multi-column layouts to create numerous configurations. You can have a 4 column layout on desktop that squish and stack into a 2x2 configuration on mobile, or a 3 column layout on desktop that stacks and hides the middle column when viewed on mobile.

Understanding the kinetic possibilities of a responsive email template is an important step in creating a framework that allows for high reusability. Learning how these styles work can open up new opportunities for unique email features and designs; such as interactive menus, image carousels and embedded email forms!

Responsive Content Modules - Designing for Layouts

With the structure of the email covered, we can now look at the content blocks or modules. Looking at most marketing emails, it’s common practice for businesses to use the same product or promotion layouts week after week; after all there’s a limited number of ways to display a product in a clear and concise manner. Sometimes this “product module” will be in a single column format 600px wide; other times it might be in a 3-column format at 200px wide each. In both examples above the content is the same, it’s just scaled differently based on the layout it sits within.

When designing content modules, it’s important to think about how they could reasonably be used by the email team, and ensure the CSS and table-containers allow for every use case. Creating a small library of pre-formatted content blocks that are suitable in 1|2|3-column layouts give your email team the flexibility to dynamically create unique emails on the fly.

For example, a brief may require 5 key products be shown in the middle of the email. The end user can drag in the 1x2-coland 1x3-collayouts, one after the other, and use the “Image-Header-Price” module for all 5 products. The standardized approach also means the artworks team doesn’t need to resize product images uniquely for every email - they can keep to a standard set of tile sizes.

This design process also extends to headers and footers too - creating a standardized yet customizable asset pool to choose from. Other supporting elements such as Call-to-Action (CTA) buttons, graphical page dividers or full-width promo banners can also be designed to add more variety to the module library.

How to build your own reusable library

Below is a copy of a reusable email template with some sample layouts to show how the CSS classes work. You can use this as a starting framework to develop your own suite of responsive modules.

GitHub Link

Read more
Cam Robert
Published 02/26/2020
Career
Appreciate Everything. Sweat Profusely. Regret Nothing.

Have you ever taken a second in your busy work day to look in the mirror, take a nice deep breath, and then with great conviction say…”I’ve made a huge mistake.”?

All of you? Yes? Even the biggest, baddest, most perfect person you can think of has wanted to vomit (or more likely HAS vomited) from nerves at some point in their life. This could be from an unplanned project getting dropped on their lap or even having to dump everything they just did and start from scratch. This is a part of learning and growing. You need to ‘embrace the suck’ and push yourself beyond your comfort zone.

Is this scary? Absolutely! But, it also is the best way for you to better yourself, to become successful and to make it seem less scary the next time. We at HowToSFMC thought that, appropriately (as many of us have vomited many times while making this website), we would make our first article about pushing through these terrible feelings and doing what you should, not what makes you feel safe.


Below are a few words of wisdom from each of our founders:

Genna - The old cliche is true, there really are no dumb questions. Seriously, everyone says it, but it’s true. There are no stupid questions, just people too scared to ask because others might think less of them. Most people are more concerned about themselves (possibly also having panic attacks, while writing… ) and aren’t paying attention to you. So don’t worry about losing face, ask.

It’s through mistakes that we learn, see new perspectives and innovate. Being human means feeling dumb – a lot! It’s how you rebound from those experiences that really matter. Experiences that are difficult often have the best outcomes so take challenges when you are presented with them, even if they terrify you. And never ever be afraid to ask for help, or to join a team or community. No one can do it all so you have to look for those who have complementary skills and similar values to build your community with.

It’s okay to talk about your accomplishments, even if you think it sounds braggy. Being humble can be detrimental when taken to the extreme. So, self-promotion is not a bad thing when done in moderation. You have to learn to promote yourself at the right time in order to get ahead in life.

You never know, you may suffer from impostor syndrome at work and then have a complete belief that you are the greatest home brewer ever while introducing your taste-testing friends to Captain Trips. No one wins them all. And if you’re a plant lover like myself, then no one keeps them all alive either. You’re going to kill something at some point, accept it, move forward.

Jason - For every one question someone thinks could be dumb, there’s likely an actually dumb piece of documentation to make the question need asking. Which unfortunately is only something you find when you need to know an answer quickly. (Looking at you, User Permissions documentation).

Let’s face it, in the early days of most of our learning experiences, documentation and guides are the primary sources of knowledge. Which is great if you want to follow a path laid out for you like Waze and you know what your A to B is. But, the more useful skill to learn is how to drive the car. Then it doesn’t matter if you know where A and B is or if you need to stop off on the way and do something else - You can still get there.

The number 1 question that I wish I’d asked on day 1 was “I created a filtered Data Extension, how do I make it refresh/update automatically?”. What I should have done is asked that question before I’d made about 40 of them that didn’t refresh… I then asked it later and found that I’d done them all wrong. But alas, I learned the most useful lesson that day - Ask the question first before spending a few days possibly doing it wrong.

The second most useful lesson I’ve learned is “Someone already invented the wheel, unless you believe you’ve got a better concept - why reinvent it?” There are some fantastic people in the world of SFMC and it would be remiss to not reference the Isaac Newton quote “If I have seen further it is by standing on the shoulders of Giants.” The best thing about the Giants in SFMC, is that lots of them make their shoulders available for you to stand on.

If there’s anything to take away from this - Ask a question. As children we’re often taught to politely ask questions, we’re raised to be inquisitive. Let that childish inquisition come back. Make mistakes, but only make the same mistake once, then ask how to avoid it if your inquisitive experiment didn’t go according to plan.

Greg - Sometimes the people that are viewed as the ‘stupidest’ are actually the smartest ones in the room. They realize that how others perceive them is not the real definition of their intelligence. By suppressing your question to ‘save face’ you limit the knowledge you are able to gain. Is embarrassment worth the gaining of knowledge? HECK YEAH. In 5 minutes that embarrassment disappears, but that knowledge stays with you forever.

Don’t be afraid to fail. Sometimes you aren’t the best. It really sucks when it happens, but unfortunately you are not an omnipotent God. Failing is important though as you then know exactly what NOT to do next time. As good ol’ T. Edison used to say “I have not failed. I’ve just found 10,000 ways that won’t work.”

Even though failing sucks, you should always try new things and explore. That being said though, you need to do this smartly. Don’t dive into a project that you literally have never done before and promise the moon. That is setting up yourself and your team for failure. But, if you are open and honest and put forth the extra effort to fill in all your knowledge gaps, then you are much more likely to succeed regardless of skill level.

Tony - Every time I ask a peer in the email industry about their background I’ve heard very similar stories: “I kinda just fell into it…” “They needed someone to run their email program so that’s what I did…”

My story is the same; I started out in print design and then magically transitioned to email design. When I started, I knew basic HTML, a little CSS and not much else. So how do you learn? I quickly realized, the only way to learn is to ask as many questions as you can.

However, learning isn’t just about asking the question, it requires asking about the why, the how, and the theory behind the answer - not just blindly accepting it. There is no doubt that jumping into a new community and asking questions is daunting and overwhelming, but the benefits far outweigh any embarrassment.

So you know that question you haven’t asked yet? Ask it!

Aysha - You know that problem you’ve been working on all day? That one question you want to ask, but think if you do you’ll look like a chump because it’s a ‘stupid’ question? Just ask it already! It’s important to realize that looking stupid for a minute is the only way you can learn and continue to get smarter.

We, in the email industry, are quite lucky. We are fortunate to have a fairly substantial community of peers. With so many knowledgeable, innovative, boundary pushing professionals, you may ask yourself “how in the world can I… a beginner… contribute?!” Simple: Ask questions. Answer Questions. Talk with people. But, most importantly: get involved!

Stuck on some AMPScript? Need to troubleshoot Content Builder? Just ask. Chances are there is someone else with the same question that will also benefit.

When I first joined an Email community, it was very one-sided. Lots of questions were asked about how to do basic tasks, but I couldn’t give anything back. After a while, I noticed a significant shift. New community members joined, similar questions were asked…but this time I could answer them…and DID! I quickly realized that even though I was still learning I could contribute by answering the questions that I already knew.

You don’t have to be the most knowledgeable about a topic or even know every detail. The best thing about contributions from users starting out… perspective. Sometimes a fresh perspective can yield some very interesting results!

!Do I regret it? Yes. Would I do it again. Probably.


Are you still sweating? Hopefully we’ve put your mind at ease a bit. Our goal is to show people that there is no ‘best way’ to do things. That there is no right and wrong way, just different solutions. Are we saying that these nerves and agita will go away? Nope. BUT we are saying that you will become more confident and learn with better ways to deal with the ‘rattlers in your gut’ so no one would ever know how close to vomiting you really are.

Hope you enjoyed it. Please continue contributing to our new community here and hopefully we will be able to provide a safe space for everyone to share ideas and collaborate to bring us all to the ‘next level’.

Authors (in order of appearance):
Genna (gemliza) Matson
Jason Cort
Greg (Gortonington) Gifford
Tony (Tony Zups) Zupancic
Aysha (BlackenedHonesty) Zouain

Read more
HowToSFMC
Published 01/18/2020
General
HowToSFMC Mission Statement

HowToSFMC is a group of people that aim to provide valued information and learnings to the Salesforce Marketing Cloud (SFMC) community across the various skill levels of its users. This assemblage will require community involvement and participation in order to flourish. Essentially creating a “For the Community, From the Community” environment.

HowToSFMC will essentially display community provided issues/common tasks/roadblocks and then ask for the community to submit their solutions for these tasks. The goal is to show that tasks/issues do not have only a single solution, but instead multiple angles and approaches to solve it as the ‘best solution’ may not actually be the best choice for everyone.

We will be opening this up to the community to provide us with the ‘problems’ that they face on a daily basis. We will then identify the most relevant ones to present for the monthly vote. These problems can be anything and everything. Although the more specific the problem is, the less relevance it may have toward the community as a whole.

Once selected, the problem will then be posted to the website for the community to review. We then will ask community members to submit proposed solutions to this problem. These solutions will need to be proven and explained, we cannot accept half answers or just code blocks.

After we receive the solutions, we will gather them, rank them, and display them (anonymized so the same person doesn’t win every time) for the community to vote on. We ask the community to vote based on the best overall solution. This includes efficiency, versatility, elegance, future-proof and results.

After the ‘winning’ solution has been identified, we will remove the anonymization so people can proudly share their work and be recognized for it. We will also have the winner (or a proxy if the winner is unable to) create a quick video explaining their solution and how to implement it. The other top solutions will be listed as honorable mentions/alternate methods. This will not only help illustrate the different ways tasks can be handled, but also provide an opportunity for community discussion.

Although it would be greatly appreciated, we do want to stress that you do not need to participate in each and every step of the above to be a valuable contributor of HowToSFMC. Even if you choose to only participate as a voter once every so often, you have provided a great contribution and we appreciate it. We simply ask you to do what you can, when you can to help our community grow and learn.

Read more
HowToSFMC
All Rights Reserved
Made with by your fellow SFMC users.