Skip navigation
All Places > Products > Blog > 2018 > November
2018

The Marketo Master Class series is back with another deep dive into one of the many facets of Marketo: Lead Nurturing. This time around we were lucky enough to team up with Marketo Champion Alumni and Founder of Marketing Rockstar Guides Josh Hill on a special piece of content. Past master classes have drilled into a particular functionality and provided technical tips for navigating the Marketo platform. For this master class, rather than walk through how some of our successful customers leverage Marketo, we wanted to discuss the philosophy every marketer should consider before implementing any and every lead nurturing campaign.

 

1. How did you develop your nurture philosophy?


I developed the Journey Session Framework after working on various nurtures using smart campaigns as well as the Engagement Program. After a few tries, I realized that in order to make it work, there were the five questions to answer. If you cannot answer all of the questions, you won’t be able to launch the nurture properly. In many ways, this harkens back to the old Marketing or Campaign Brief, from a marketing automation point of view.
The Five questions are Who, What, When, Where, Why, and How. But that’s not how I ask them to get the answers we need for a nurture program.

  • Entry – Who
  • Exit – Bad – Why
  • Exit – Goal - Why
  • Cadence - When & How
  • Content – What


If you can concretely answer these questions, you can do amazing work in Marketo.
For example, you might end up with a Journey Doc like this:

  • Entry – Who – people who fill out the free trial form with XYZ fields.
  • Exit Bad – people who unsubscribe or cancel their trial.
  • Exit Goal – people who pay for the service.
  • Cadence: one email within 1 hour and then every 2 days at 2pm afterwards.
  • Content: onboarding emails – 3 to start, we may add 3 more.

 

2. What metrics are you looking at if you’re trying to improve a nurture? (open rates/click rates?)


I am not a big proponent of such metrics for a nurture. These days, clicks are about 80% fake – created by spam bot filters. A click can only tell us a little bit about that particular asset, not whether the journey itself is leading toward the desired lifecycle stage. If your nurture is designed to advance someone (a lead or Account) to MQL or SQL, that’s the only metric to consider.
Remember that your stages are (usually) based on a lead scoring methodology or your funnel methodology, so it is predeterminate to say that “We’re driving 20% more MQLs with this new nurture.” Is that because it takes 1 click to reach MQL? Is it three whitepaper downloads? Are your assets structured to hook into that scoring?
In some sense, you can treat a long term, carefully planned nurture, similar to the lead funnel. The ability to monitor the inflow and outflow of the nurture against the key stages is critical.

  • Entry – how many entered each day or month
  • Exit Bad – how many people fell out because of unsubscribes or excluded traits.
  • Exit Goal – how many people reached our goal – MQL or Upsell.

 

3. How do you make changes to an existing nurture/program without reinventing the wheel or breaking the system?


The best way is to add new content. However, I do encounter this issue frequently. Sometimes the marketer made a mistake, sometimes we add new nurtures and have to shift leads to a different nurture after the fact. Most of the time, at least with an Engagement, this is easy to do with smart lists and campaigns.
The challenge is when you have a Smart Campaign drip, or Irregular drip with wait steps running. If leads are in the flow, you may have to remove them and start again. You can also, if you are careful, adjust the flow steps, however, this is risky. Leads could pop out to the wrong step or you could create a bug in the smart campaign and cause it to fail.

4. How do I determine whether content should be in a separate stream or separate program entirely?


I use my Nurture Waterfall concept to decide things like this. Nurtures aren’t about a set of content, they are about the people. The Waterfall has several nurture programs, or streams, designed to take someone with a limited profile to entice them to offer us more information about themselves. Once they do, we move them to more specific nurtures targeting their Account-Solution-Persona.
Usually if the Lifecycle Stage is different or the Buyer Persona is, that’s a reason to have separate Nurture systems. Depending on how you structure the Streams, you can either do this by Persona-Stage or separate the Nurtures. There’s no perfect way to handle this.

 

5. What are the common pitfalls you see in nurture planning and execution?

 

Lack of planning.

Lack of content.
If you don’t understand the Journey Questions, you won’t be able to build the nurture in Marketo or any system.
It is critical to have a continuous stream of content to add to the Streams to extend the nurture as most B2B buyers will take months to years to be ready for your sales people.
The other pitfall is this strange assumption that every nurture begins with 4 emails. The assumption blocks a successful nurture because:

  1. Four emails are rarely enough. Most marketers can barely get 4 emails finalized on Day 1 and then they will rarely come back to add more than four. If you know it takes 60 days to reach SQL and 180 days to make a sale, why is your journey 4 emails?
  2. Only need one email on Day 1. Create a rolling schedule of new content. Your audience won’t notice if there’s a delay, but you definitely need to feed the machine or risk a dead list. Psychologically, creating one email at a time is less daunting than building a 49 week nurture.

 

6. In what situations would you want to leverage a nurture program over any other program?

 

This isn’t the right question. A nurture program is part of a continuous multi-channel effort. People should find content organically or through advertisements, then opt in to further communications. The nurturing program keeps your audience engaged while specific Events, Webinars, Videos, etc will spark their interest further. Of course, not everyone, but enough to keep business moving.

 

Each lead who opts in from offline events should enter a long term nurture. Leads who don’t advance in the funnel should move to an appropriate stream related to the reasons they did not work out this time.

 

We hope you learned some key takeaways to think about while planning your next nurture campaign. Do you have a nurture checklist of your own? We would love to hear about it in the comments!

***Posted on behalf of Rachel Noble, Manager of Client Services at Digital Pi***

 

Reporting on multi-touch attribution

If you have Revenue Explorer (aka Marketo Advanced Report Builder), you’ve seen (FT) and (MT) show up on a number of reports. Marketo gives a full breakdown of these two distinct types of attribution, but today we are going to focus on (MT): Multi-Touch.

 

Traditionally, multi-touch attribution provides demand-generation analysis.

 

Multi-Touch answers a complicated business question, "Which programs are most influential in moving people forward in the sales cycle over time?"

- Marketo

 

To get a basic understanding of how multi-touch attribution really works, let’s start by looking at the Opportunity Influence Analyzer in the Analytics section of Marketo.

Image_01_Elevate_DC.jpg

 

For any opportunity, Marketo will show the story of how that opportunity was influenced by marketing. The little red dots above are clickable, and are comprised of interesting moments and program successes.

Image_02_Elevate_DC.jpg

Understanding which marketing interactions influenced an opportunity will help you understand multi-touch attribution. (MT) is essentially the same story, but presented in a way that allows you to aggregate or slice-and-dice the data. In Revenue Explorer, (MT) is calculated by taking an opportunity and splitting credit evenly between each program success.

 

For example, let’s take Jane Doe. She attended a webinar, visited your booth at a tradeshow, and then downloaded an eBook from your website. Shortly after, an opportunity for $300,000 was opened and associated with her contact record. Now, we have $300k of pipeline that can be directly connected with the webinar, tradeshow, and eBook. A (MT) opportunity report will credit ⅓ of the opportunity to each of these, and a (MT) pipeline report will credit $100,000 each to the webinar, tradeshow, and eBook.

 

Let’s look at one more example: John Smith. He was acquired via a list purchase, engaged with a direct mail piece, and then visited your booth at the same tradeshow before an opportunity for $100,000 was opened and associated with John. Since the list purchase was not a program success, it will not receive credit when using (MT) attribution. But the direct mail and tradeshow engagements were successes, so each will receive ½ an opportunity, and $50,000 pipeline credit.

 

Now that we know how multi-touch is calculated, we can pull pipeline reports per program. Perhaps we want to know how much (MT) pipeline is associated with the tradeshow. In this case, Revenue Explorer will credit the $100,000 from Jane and the $50,000 from John for a total of $150k in pipeline from the show.

 

Image_03_Elevate_DC.jpg

 

Now, we can aggregate the data. What if we want to know the total pipeline associated with all of the tradeshows? A multi-touch pipeline report by channel will return the total sum of (MT) pipeline associated with each channel.

 

Image_04_Elevate_DC.jpg

 

How do you know when to use (MT) attribution?

(MT) focuses on program successes, which are direct representations of engagement. If your goal is to drive engagement (or MQLs, or pipeline, or demand generation, etc.), then you should use a multi-touch attribution report. If, however, your goal is new-name acquisition, stay away from these reports and focus on first-touch attribution instead.

 

Reporting when you use dynamic content

There are two places in Marketo where you can use Dynamic Content: on a landing page or in an email. Today, we’re going to focus on reporting when you use dynamic content in an email.

 

Dynamic content allows a marketer to deliver the same email to an audience with content variations based on segmentation. For example, if you have your database segmented by industry, you can send someone in the education industry Email A with an introduction specific to education, and a generic introduction to everyone else. It’s a powerful tool, but reporting on dynamic content can be complicated.

 

Of course, you can always create an Email Performance Report or an Email Link Performance Report to understand the performance of the overall email, but what if you want to understand how each dynamic variation performed? Here’s the easiest method. First, create an Email Performance Report. In the Smart List, add a filter referencing a specific segment:

Image_05_Elevate_DC.jpg

Cloning this report and updating the segment will give you a separate report for each version of the email that went out.

 

However, there’s a catch! It is possible for someone’s industry segment to change between the time they receive the email and the time you pull your report. So how do you know for sure which version someone received?

 

When building your program, first identify how many versions of the email you will have. Let’s say in this example, we have industry-specific dynamic content for:

  • Education
  • Manufacturing
  • Technology

As well as a generic email for everyone else.

 

When we send the email, we will also create a smart campaign to assign recipients to static lists depending on their segment.

If you've been around the block with Marketo Smart Lists, you know there's no Ends With operator, only Starts With and Contains.[1]

 

This puts a damper on a common need: accurately searching for an email domain (@gmail.com, @example.co.uk) or TLD (firmographic clues like .edu, geographic ccTLDs like .cn).[2]

 

Some have attempted extravagant combos of Contains and Not Contains, which require a whole lot of prep just to determine that... they don't work. (Read my comments on the linked post for some examples of how such approaches are broken.)

 

There's a much easier way: maintain a custom field, here called Matchable Email, that always holds the value of {{Lead.Email Address}} followed immediately by two quotation marks "":

 

ss

 

ss

 

Then, to do a domain search, search that Matchable Email field for Contains @example.com"", which is equivalent to searching the original Email Address for Ends With @example.com:

 

ss

 

Pretty easy, right?

 

Why two quotation marks (“”)?

The key is to add a sequence of characters to the end of the email address that can never occur in the middle of the email address, so Contains @{{domain}}{{characters}} is functionally equivalent to Ends With @{{domain}}.

 

Finding those appropriate {{characters}} is a lot harder than it sounds. For awhile I was lazily appending $ to the end, because I like the fact that the dollar sign represents end-of-line in regular expressions, so it was easy to remember. But the email address "$ke$ha@marketo.com$"@gmail.com — note the quotation marks around the mailbox part, it wouldn't be valid without those — is a Gmail account, but would match Contains @marketo.com$.

 

Yes, RFC 5321 is just that generous. There are so many crazy-but-valid email addresses, however inadvisable it would be to use them in the real world, that it's hard to find something that, without exception, can only occur outside of a valid address and so can be used as your anchor point.[3]

 

I think I've found that something, though. Two quotation marks in a row "" can occur inside an email address, but they can never be preceded by a character that is a valid part of a domain name.

 

Let me explain.

 

First of all, as you may already be confused by this part, it's possible to have a quoted mailbox name (called a quoted-string in the standard). That's how you can add spaces on the mailbox side of the @: "sandy spacebot"@teknkl.com is a valid SMTP address.

 

You can also put quotation marks inside an already quoted mailbox name, but if you do so, you have to escape them with a backslash.  Thus "Clarence "Frogman""@henry.com" is not a valid email address, but if you escape the quotes as "Clarence \"Frogman\""@henry.com it is valid. Even though this address has two quotes in a row "" (see the characters right before the @?) they are by necessity preceded by a \.  And the \ can never be at the end of a domain name. 

 

Therefore you can accurately search the Matchable Email field for a string that Contains @gmail.com"", knowing that that sequence of characters cannot be found at the start or middle of the value, only at the end.

 

Enjoy!

 

 


 

 

Notes

[1] As a sometime database architect, I've never understood the technical reasoning and figure it must be just legacy-code-nobody-wants-to-touch syndrome. When searching strings, Starts With is faster than Ends With unless specific indexing is used; yet Contains and Ends With have equivalent performance — often terrible performance, don't get me wrong, but roughly the same either way. Plus, it's way easier to add indexing to speed up Ends With than it is to optimize Contains (an index on the reversed value in the first case, n-grams in the second case, FWIW). But here we are.

 

[2] My colleague EU points out that Marketo attempts to optimize a search for a domain-like pattern, one that begins with the character @, and turn it into an SMTP domain search. The problem is that it still doesn't work: The valid address "me@gmail.com"@outlook.com will (as we would expect given the concept of contains) match both Contains @gmail.com and Contains @outlook.com so it doesn't successfully emulate Ends With. It will also false negative on Contains @outlook.co, which is just plain wrong.

 

[3] The way to do this in a technically complete manner is to add an ASCII control character (like ASCII 30 RECORD SEPARATOR, one of my faves) which is never allowed, not even in quotes. But while you can append such a character with a specially hacked Change Data Value, searching for those characters is, unless it's just a one-time thing, effectively impossible. So we'll have to make do with "".

Hadn't even heard of Appointlet until the other day, but when user SS mentioned he was trying to use the REST API to integrate an Appointlet widget with Marketo, I knew there had to be a better way. (There's almost always a more reliable + scalable alternative to server-to-server API calls for what should be browser-side integrations, Unbounce being another example.)

 

In line with services like ChiliPiper, TimeTrade, Calendly, et al. Appointlet is a service I wish I'd thought of because I'd be rich right now dedicated to scheduling. It interacts with a cloud calendar — O365 or Google calendar in this case — in real time to check availability, alerts reps of new bookings, and sends periodic reminders. (Again, no endorsement intended, just describing the published features... only spent 1/2 hr figuring out the API, so perhaps the platform might turn out to have humongous bugs, but it definitely looks useful enough so far!)

 

The Appointlet embed code gives you a button, which when clicked brings up the rep's availability:

 

 

And then a place for the lead to enter their personal info (more fields can be added but these are the defaults):

 

 

Naturally, when you're offering an Appointlet Book Now instead of a full Marketo form, the questions are:

 

  • How do you insert/merge the lead's info into Marketo?
  • How do you make sure past + future web activities are associated with the newly identified lead, i.e. how do you associate the Munchkin cookie with the lead, the way it works with a Marketo form?

 

The best answers are definitely not found in the Marketo REST API. Appointlet does offer outbound HTTP callbacks (accurately called webhooks, but they must not be in any way confused with Marketo's outbound 'hooks). So yes, you could set up your own gateway to receive said callbacks, and you could map them to the Marketo REST API endpoints (plural) that sort-of-maybe emulate a Marketo form post. But that means raw coding labor, new servers to maintain, and Denial of Service exposure. And no upside.

 

Instead the answer, as usual, is to simply post a Marketo form in the background, relaying the lead info from the Appointlet UI.

 

To do this reliably, Appointlet needs to have a client-side JS API. And indeed they do!

 

The Appointlet widget itself is rendered in an IFRAME, and like other sophisticated IFRAME-based embeds (the YouTube player, for example) the widget sends standard browser events to the parent document (that is, to the outer Landing Page) that include interesting info from the widget. We just have to listen for those events, add corresponding values to a Marketo form, and submit. Then we'll get a standard Filled Out Form activity in the Marketo Activity Log, which you can trigger and filter on like any other, and past + future Visited Web Page and Clicked Link on Web Page activities from that browser get merged in, too.

 

Step 1 of 3: Create a form

So first, set up a form that'll catch submissions from your Appointlet widget. (You can set up more than one form if you want to see cosmetically different Filled Out Form names for different pages, but it's not necessary and you don't want to create complexity.)

 

It doesn't need any fields at all, since we'll be populating the fields via API, but you can leave the default 3 fields in place. Just don't make any of them Required.

 

 

Step 2 of 3: Add the Marketo form to your page, with the <form> element not displayed

Inline style="display:none;" is easiest. With the embed code:

 

<form style="display:none;" id="mktoForm_787" class="mktoForm"></form>

 

With a Guided Marketo LP:

 

<div class="mktoForm" id="appointletForm" mktoName="Appointlet Hidden Form" style="display:none;"></div>

 

Or you can put it in a separate <style> which is more professional I suppose.

 

Step 3 of 3: Add the Forms 2.0 API custom JS

This is of course the meat of the solution.

 

MktoForms2.whenReady(function(mktoForm) {
var appointletUserConfig = {
allowedOrigins : ["https://teknkl.appointlet.com"],
formFields : [
{
appointletName : "first-name",
marketoName : "FirstName"
},
{
appointletName : "last-name",
marketoName : "LastName"
}
]
};

/* NO NEED TO TOUCH BELOW THIS LINE! */

window.addEventListener("message", function(message) {

var appointletGlobalConfig = {
messageType : {
TYPE_BOOKING_CREATED : "booking:created"
},
pattern : {
RE_AL_POSTMSG : /^appointlet:/
},
err : {
ERROR_NON_ORIGIN : "Message received from non-Appointlet origin",
ERROR_BAD_JSON : "Message received from Appointlet API but could not be parsed"
}
};

var appointletEvent,
isAlOrigin,
isAlBookingCreated,
mktoFieldsObj = {};

isAlOrigin = appointletUserConfig.allowedOrigins.some(function(origin){ return origin == message.origin; });
if (!isAlOrigin) {
return;
}

try {
appointletEvent = JSON.parse(message.data.replace(appointletGlobalConfig.pattern.RE_AL_POSTMSG, ""));
} catch (err) {
return console.log(appointletGlobalConfig.err.ERR_BAD_JSON);
}

if (appointletEvent.type == appointletGlobalConfig.messageType.TYPE_BOOKING_CREATED) {
mktoFieldsObj["Email"] = appointletEvent.data.email;
appointletUserConfig.formFields.forEach(function(fieldDesc){
mktoFieldsObj[fieldDesc.marketoName] = appointletEvent.data.fields.filter(function(alField){
return alField.field.slug == fieldDesc.appointletName;
})[0].value;
})
mktoForm.addHiddenFields(mktoFieldsObj);
mktoForm.submit();
}
});
});

 

Most of the code is no-touch, but there's a short config area at the top where you put your company-specific variables. From your Appointlet settings, get your Booking Page URL. That goes in the allowedOrigins config property:

 

 

Then the formFields property is an array that maps each Appointlet field name to its corresponding Marketo field name. (You didn't think it would be so easy that the separate products would miraculously use the same names, didja?) I filled in the First Name and Last Name mappings for you. Names of additional custom fields can be found via browser inspection, the Appointlet » Form Fields UI, and the SOAP API Name column of a Marketo UI » Field Management CSV export.

 

And that's it! Now, any confirmed Appointlet booking will post the form to Marketo.

 

What about the rest of the Appointlet setup?

That's on you. I found it very easy to set up an Appointlet account, link to a test Google Calendar, and grab the button code. But since I don't want to imply an outright endorsement, better to leave the rest of the product evaluation in your hands.

To switch up the Nancy Sinatra song, Booleans keep truthin’, when they ought to be falsin’.

 

As explored in earlier blog posts, when Marketo exposes Boolean fields in Velocity fields on the Lead/Person object, not on other objects they become Strings, not real Booleans.

 

And they're not even very Boolean-like Strings: they have values "1" and "" (the empty string) which in Velocity are both truthy values.[1]

 

As a result, you can't use a standard Boolean expression

 

#if( $isCustomer )
You're a customer.
#else
Wouldn't you like to be a customer?
#end

 

because everyone will match the first condition.

 

You have to be more exact, unfortunately making your code less self-documenting:

 

#if( $isCustomer == "1" )
You're a customer.
#else
Wouldn't you like to be a customer?
#end

 

Now, this more verbose version may not seem like a big deal, but I consider it to be poor programming practice because it relies on a “magic string”: a person reading your code has no way to know that "1" has some special significance and that the variable could not hold any other string value. That is, any Boolean-ish thing should be an enumeration only allowing 2 values (one representing true and one representing false, whatever those values might be) but since it's a freeform String it has no such restriction.

 

So here's something you can add to your global {{my.velocityIncludes}} token. (You do have such a token, don't you?  All the cool kids do.)

 

 

#set( $mktoBoolean = { "1" : true, "" : false } )

 

 

With that one-time include (put it in the <head> of your templates) now you can refer to those Boolean-ish fields like so:

 

#if( $mktoBoolean[$isCustomer] )
You're a customer.
#else
Wouldn't you like to be a customer?
#end

 

Now it's clear that you're using the variable as a Boolean.

 

I've recently decided this simple method is good enough. In the past I'd been  using a list (manually maintained in {{my.velocityIncludes}}) of known Boolean field names, then “massaging” those fields on the lead to turn them into real Booleans before using them. But that takes prep work and IM(new)O isn't worth it.

 

Code breakdown (if you need it)

The snippet above is just the kind of thing that can make new devs think VTL syntax works a certain way, and then try to adapt it to other scenarios only to find syntax errors.

 

So let me explain exactly what's happening, as short as it is.

 

First, let me add line breaks for readability:

 

#set( $mktoBoolean = { 
  "1" : true, 
  "" : false
} )

 

By using Velocity's map literal syntax we're creating a simple Map object with 2 keys.

 

(Informal/imprecise terms for such an object are Hash, HashTable or Dictionary, and the exact type is LinkedHashMap. Also feel like noting that even though Velocity's map literal syntax looks the same as JavaScript's object literal syntax, it creates an object that is different in one critical way, though that difference isn't relevant here.[2])

 

The keys in the Map can have any values, even including null, and can certainly include any kind of string, including an empty string. You access the keys in a Map using .get, bracketed-property syntax or, when it can be parsed unambiguously, dot-property syntax.

 

So for a more general example, if we defined this Map:

 

#set( $someOtherMap = {
  "FaveFruit" : "apple",
  "FaveVeg" : "broccoli"
} )

 

Then we can use one of 3 equivalent ways to access the person's favorite fruit:

 

$someOtherMap.get("FaveFruit")
$someOtherMap["FaveFruit"]
$someOtherMap.FaveFruit

 

Those all address the same key and will all show apple.

 

In the specific case of the $mktoBoolean Map, we can't use the 3rd option of dot-property syntax though, because $mktoBoolean.1 isn't a valid expression in Velocity Template Language since it starts with a number.  We're limited to

 

$mktoBoolean.get("1")
$mktoBoolean["1"]

 

This limitation isn't a big or surprising deal, by the way. Just one of a zillion cases where certain accessing syntax might be unusable, but that doesn't mean the initial definition of the variable was wrong. Sometimes you end up limiting the ways to refer to object keys — another common case is when a string key has a space in it ({ "My Other Car" : "Lambo" }), which also doesn't work with dot-syntax so you have to use $someOtherMap["My Other Car"] or $someOtherMap.get("My Other Car") — but you get other benefits in return.

 

Aaaaanyway, so we have a Map with 2 keys, both Strings. The value of each key is a real Boolean: I used the literal Boolean values true and false, not Strings.

 

That means we can use bracket-syntax to access the corresponding key in the Map, which will return a Boolean.  When I do

 

#if( $mktoBoolean[$isCustomer] )

 

I'm getting the value from the $mktoBoolean Map that matches the key $isCustomer. That is, within our reserved world of "1" and "", I'm always getting either $mktoBoolean["1"] or $mktoBoolean[""], and those values are both Booleans so the result can be used clearly and consistently.

 

Hope that all made sense!

 

 


 

 

Notes

[1] In Velocity's underlying Java environment, these would both be String instances and thus neither true nor false; they'd throw fatal errors if you tried to use them as Booleans. (Unlike other languages you might have used, in pure Java only an actual java.lang.Boolean can be used in a Boolean expression; there's no concept of truthy or falsy strings or numbers.)

 

But VTL is an often frustrating “friendlier” dialect on top of Java which minimizes visible errors. In VTL, #if ($someStringVariable) won't chuck an error message into your email; yet the logic it uses is basically “everything but exact Boolean false or null is true” which can be very misleading.

 

[2] That way being that the keys in a LinkedHashMap are ordered. Key order has no bearing on the way $mktoBoolean is used in this scenario, but it's a classic source of confusion in object-land. In JavaScript, object literal syntax creates a plain JS Object, which is unordered and is roughly like a simpler Java HashMap.

Filter Blog

By date: By tag: