Skip navigation
All Places > Products > Blog
1 2 3 4 5 Previous Next


354 posts

For any Velocity project, I've taken to offering clients a separate config token, call it {{my.ThisIsWhereYouChangeStuff}}, where they can manage some output settings without having to email me all the time.


Then there are one or more {{my.PleaseDontChangeAnythingInHereItsFragile}} tokens with the meat of the code.


Velocity #define directives are really handy for the config token. They're a bit more fault-tolerant than #set statements, where the non-technical person has to remember to escape quotes, close parentheses and such.


That is, instead of:


#set( $baseURL = "" )
#set( $linkText = "Say \u0022Hello\u0022 to our new preference center." )


I give them a token like so:


#define( $baseURL )
#define( $linkText )
Say "Hello" to our new preference center.


As long as they leave the #define/#end lines alone they can change anything in-between (especially good for multiline text, as you might imagine).


There's a little trick to using #define, though, and that is like everything in Velocity, it preserves whitespace.  What whitespace, you may ask? Well, look at the end of this line:


That has a carriage return + line feed (or just LF, depending on the OS) at the end.


So if I output a link like so:


<a href="${baseURL}">${linkText}</a>


The email will contain:


<a href="
">Say "Hello" to our new preference center.


Instead of what you intended:


<a href="">Say "Hello" to our new preference center.</a>


Which is bad because it will wreck your links even though you may not even see the wreckage in Preview because of the way HTML itself swallows line breaks.


Now, you can suppress the trailing whitespace by adding a comment ## at the end of the line


but I daresay that's not an improvement, since the idea is to offer this token as a not-too-fragile place for a non-technical person to make adjustments, and adding ## is something they're bound to forget or mess up.


So what you want to do is let them enter text in as close to free-form fashion as possible. Then in your code, strip out extraneous whitespace at the beginning or end to be tolerant of minor messups.


How trim() works

The documentation of the trim() method in Java, which exists on any Java String and therefore on any Velocity String is almost lovable in its complexity.


trim() does exactly what we want, but you have to understand the ASCII table to know that! Not that a programmer shouldn't understand ASCII, but it's a particularly circuitous explanation IMO:


[L]et k be the index of the first character in the string whose code is greater than '\u0020' (the space character), and let m be the index of the last character in the string whose code is greater than '\u0020'. A new String object is created, representing the substring of this string that begins with the character at index k and ends with the character at index m-that is, the result of this.substring(k, m+1).


Let me put that in clearer terms:


If a contiguous block of characters between ASCII 0 and ASCII 32 is found at at the beginning and/or end of the string, the whole block is removed.


ASCII 0 through ASCII 32 means the nul (0) through space (32) characters, inclusive. In that range are the quite common carriage return (13), line break (10), and tab (9) characters, and some more obscure ones like vertical tab (11).[1]


So though it only explicitly mentions the space character \u0020 (hex 20 is decimal 32), which you're probably familiar with as %20 in URLs, in fact it covers line breaks as well. If there's a long intro or outro of spaces, line breaks, and tabs, trim() will clean 'em all out.


trim()-ing what's inside a #define

So trim() is perfect, but you can't simply do this:


<a href="${baseURL.trim()}">${linkText.trim()}</a>


That'll throw an error. The reason is that any #define, when you address it directly, is a Velocity-specific Block$Reference, not a generic java.lang.String.


A Block Reference doesn't itself have a trim() method. But it does have a toString() method. (In fact, toString() is called under the hood when you output a plain ${reference} in Velocity, otherwise you couldn't output it at all.)


So I know this was long-winded but hopefully you learned something you need:


<a href="${baseURL.toString().trim()}">${linkText.toString().trim()}</a>


And you're done!





[1] But not all whitespace characters, since some as common as non-breaking space (the famous &nbsp; in HTML) are above ASCII 32. And over in JavaScript, the almost-identically-purposed trim() does strip non-breaking space. Is there nothing in programming that's not complicated when you care to learn the details? ☺

Will Harmon

Adobe to Acquire Marketo

Posted by Will Harmon Employee Sep 20, 2018

As you may have heard, earlier today Adobe announced they have entered into a definitive agreement to acquire Marketo to widen their leadership in customer experience. Marketo CEO Steve Lucas published a blog that highlights both Marketo's and Adobe's joint vision to empower marketers to deliver exceptional end-to-end customer experiences:


"I am thrilled to announce that Marketo has entered into a definitive agreement to be acquired by Adobe. Adobe and Marketo both share an unwavering belief in the power of content and data to drive business results. Together we will deliver an unrivaled solution that will place customer experience and engagement at the heart of digital transformation. This announcement is a momentous occasion for Marketo, as it signals the next phase of our company’s growth."

For more information about the pending acquisition, please read Steve Lucas' full blog and Adobe's press release below.


Blog from Marketo CEO Steve Lucas: Adobe to Acquire Marketo to Place Customer Experience and Engagement at the Heart of Digital Transformation
Adobe Press Release: Adobe Expands Customer Experience Leadership with Addition of Marketo | Adobe Blog

Quick Velocity learner EC asked a seemingly simple question:


How do you sort Velocity objects based on a Boolean field, such that false values come first?


What makes it not-so-simple is that it's actually 3 questions in one!


Realize that something labeled as Boolean in Marketo (in the Field Management or Custom Objects admin sections) may or may not end up as a Boolean field in Velocity.


Booleans on Leads

As I've delved into before, regardless of the data type set in the database, Lead/Person fields in Marketo become String properties of the $lead object in Velocity:

  • Dates and DateTimes are not dates in Velocity, but merely date-like Strings (that's why you need to go through all that pain to convert them).
  • Integers are number-like Strings (which can wreak havoc on comparisons if you don't realize it).
  • Boolean fields are boolean-ish Strings. They have the possible values "" (empty String) if the original Boolean was false and numeric String "1" if true.


But the same doesn't hold for Boolean fields on other objects besides the Lead.


Booleans on Oppties and Salesforce COs

On Opportunities and other SFDC Custom Objects, bizarrely, Booleans become a different type of String, which has the possible values "0" for false and "1" for true (this is admittedly a more traditional conversion than empty String and "1" but why, oh why, must it be different?).


Booleans on Marketo COs

On Marketo Custom Objects, Booleans are presented as true Booleans! As in real Java Booleans with the reserved values true and false, the way we dream they'd be everywhere.


Wow, that's confusing!

Uh-huh. As I'm sure you realize, such differences all have to be considered for sorting.


So the 3 questions EC was implicitly asking were:


     (1) How do you sort objects based on a String field that can be empty string "" or numeric string "1", such that "" comes first?

     (2) How do you sort objects based on a String field that can be numeric string "0" or numeric string "1", such that "0" comes first?

     (3) How do you sort objects based on a Boolean field that can be true or false, such that false comes first?


Now, at the code level, the answer to all 3 questions happens to be the same. But this is coincidental because it derives from different Java sorting rules. The fact that there's one answer that covers these 3 cases must not be interpreted to mean any ways of representing true and false will follow the same sorting logic.


At any rate, that answer is:


#set( $sortedList = $sorter.sort($originalList, "fieldName:asc") )


Because ascending (:asc) is the default order in SortTool, this can be shortened to:


#set( $sortedList = $sorter.sort($originalList, "fieldName") )


So if you have these 3 objects in a list $originalList:


  "Name" : "A",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-07-01"
  "Name" : "B",
  "IsActive" : true,
  "ExternalCreatedDate" : "2017-08-01"
  "Name" : "C",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-09-01"


And you do:


#set( $sortedList = $sorter.sort($originalList, "IsActive") )


Then $sortedList will be:


  "Name" : "A",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-07-01"
  "Name" : "C",
  "IsActive" : false,
  "ExternalCreatedDate" : "2017-09-01"
  "Name" : "B",
  "IsActive" : true,
  "ExternalCreatedDate" : "2017-08-01"


And the same code also works if IsActive uses either of the 2 "boolean-ish" String conversions instead of true Boolean.


But again (and I'm going to pound you over the head with this) it's a coincidence. If Marketo happened to use yet another way of String-ifying Booleans, you might need to sort descending ($sorter.sort($originalList, "fieldName:desc")) or use another sorting method entirely to get the falses to the top.


The challenge

Now, the fun part. Rather than jump right to Part II (where I go deep into the technical details) I'll keep that follow-up post as a draft for a week, until September 18, 2018. 


From now 'til Sept. 18, if you can answer the below challenge correctly, I'll give you a major shout-out in the follow-up... and hey, I may even have some side gigs for you! (Leave your answers in the comments, and please only try once per person.)


The background:


If Marketo used the Strings "-1" for false and "+1" for true then you'd need to use :desc instead of :asc. (And there's no standard that says they couldn't String-ify Booleans this way.) And same if they'd used symbols "" and "" , which appropriately denote truth and falsity (verum and falsum) in logic. Ditto if they'd used the Unicode characters for thumbs-up and thumbs-down.*


The question:


Why, in these 3 alternate cases, do you need :desc to get the originally false values to come first?


Be precise... no credit for "Because that's what Velocity requires."



*Would've displayed the characters here, but the Jive editor strips them

out! You can see them on the original version of this post on my blog.

In the August edition of the Fearless Forum, we're diving deeper into the topic of marketing attribution. In a recent challenge on Purple Select, a lot of Marketo users signaled they struggled with proving marketing's impact to their organization. In order to shed some light on how the experts are successfully proving their marketing impact everyday, we asked Libby Koebnick of PitchBook to help us understand how she's leveraging Marketo + Bizible.


Q1: Can you describe how marketing attribution works at your organization?

Web visits and form fills are captured via Bizible’s tracking scripts, and offline activities, including prospecting, are tracked via Salesforce activities. Our model takes all these touchpoints, connects them to and normalizes them in the buyer journey, and attributes a percentage of revenue back to them. This allows us to see how marketing and sales are contributing to our bottom line.


Q2: How did you manage your marketing attribution efforts before implementing Bizible?

Before Bizible, we were using a siloed last touch attribution model. This means we would attribute an entire contract to a single action of a single individual on that account. Since we’re a B2B SaaS Company with enterprise subscriptions, that model doesn’t make much sense for us. Now we use a custom Bizible attribution model that takes into account all the touchpoints of all the individuals involved in the conversation of the sale.


Q3: Can you share a specific benefit you’ve seen from implementing marketing attribution?

Our Bizible attribution models have showed us that over half our potential clients’ early interactions with us are via Citing this data, we successfully petitioned leadership for the resources to redesign our entire website.


Q4: How do you use Bizible with Marketo?

The way we get Bizible to see Marketo campaigns is to tag all the links in our emails with UTMs that Bizible will parse out when it captures landing pages from web visits. We just have to make sure all our links direct to our website or other pages that have the Bizible tracking script on them.


Q5: What are some unique ways you’re using Bizible with Marketo?

We started using in-platform forms in social media, which are super effective, but they don’t allow us to add Bizible tracking scripts. We found a workaround by using a Marketo campaign triggered by the form fill that creates a Salesforce activity task with the appropriate medium, source and campaign values. Bizible then turns those activities into touchpoints under the correct channel and subchannel.


Here's a closer look at how Libby leverages a campaign trigger in Marketo to turn activities into touchpoints in Bizible:

Screen Shot 2018-08-27 at 3.37.21 PM.png

Bizible Touchpoint.png


Q6: What was the most challenging part of choosing/implementing/building out your attribution model?

The most challenging part is getting our entire organization to correctly and consistently use UTMs. Web visits are bucketed into channels by building rules around landing page UTMs. Every time someone uses a new UTM (such as a new channel) or has a typo in their link, I have to create a new rule in Bizible.


Q7: How did you decide on the particular attribution model you’re using?

We are using Bizible’s Full Path model that attributes 22.5% of the revenue to each of the following buckets:

  1) First touch

  2) Lead creation

  3) Opportunity creation

  4) Close

The remaining 10% is attributed to the other touchpoints prior to the closed deal. Our old last touch attribution model was attributing 100% to the opportunity creation touchpoint. The Bizible model gives us context to the sale, more like account based marketing. We can see how the account first learned about PitchBook, how we warmed them up over time, and what eventually contributed to the win.


Q8: What advice would you give to Marketo users who are considering more advanced attribution?

Start collecting Bizible touchpoint data now! Bizible will naturally collect touchpoints, so by the time you’re ready to build your model you’ll already have your data. It takes a long time to build up historical data, and you don’t need an attribution model to start tracking touchpoints.


No attribution model is going to be completely right or wrong, and you can always adjust as you go. The model we went with was the most logical, considering our sales process, and the output passed the gut check.


When you put it all together, here's what attribution data looks like in Bizible Discover:



We hope you enjoyed reading about how PitchBook is setting themselves up for success when it comes to proving their marketing impact. For even more tips and tricks on best practices, check out our brand new edition of the Fearless Forum which features a special video by Marketo Champion Juli James!

In the April edition of the Fearless Forum, we received a lot of questions about how to use Marketo Engagement Programs. So we sat down with Chris Saporito of Paycor to discuss how he executes successful engagement programs within Marketo to onboard new clients.


Q1: What led you to develop an engagement program?

A: We decided to revamp the way that we are onboarding clients because we had a gap in the quality of the current process. Our service organization wants to combine automation and personal touches to make the process as seamless and easy as possible. By adding sophistication to the automation and mixing in personal touches we hope to improve the efficiency and quality of the onboarding process. The ultimate goal of the project is to reduce the number of no-starts that we have, meaning the clients don’t make it through onboarding and we lose the business.


Q2: Could you describe the engagement program you created for onboarding new clients?

A: Our engagement is a series of emails that are triggered throughout the onboarding process for new clients. We created a new custom object in SFDC and synced the object to Marketo so that we can key off of the values. There are 7 emails in the series that we use the custom object for to tell Marketo which dates to fire the emails and which versions of the emails to fire. Most of the emails are standard for all clients, where we do some tokening within the emails, but the fourth email in the series is critical for onboarding. The custom object that we created has a field that we can populate with the information that is needed from the client. So, when the email is triggered, we are dynamically sending a specific email based on what the client has or has not given us yet. This is important because the data and documentation are critical to have before the client can process payroll. Also, within the series of emails our client service department has a specific cadence where they are mixing in emails and phone calls with the client to ensure that the client is on track and has a good user experience.


Q3: What was the process like to build out the engagement program using custom objects to trigger emails in Marketo? How long did it take?

A: The entire process for the project took about 3 months due to some internal prioritization. It took some Salesforce development work to build the custom object and to dynamically populate it to meet the needs of the business. Also, during the process we were able to get a temporary Marketo Sandbox to test out these processes, which had a learning curve in itself. Once we had the custom object built I was able to easily sync it to the Marketo sandbox and begin QA’ing the processes. Within a couple of weeks of QA’ing and working out the kinks we were ready to pull the trigger on the program.


Q4: Before using the SFDC custom object, had you tried to create a similar engagement program in Marketo only?

A: We have an email series that we ran out of Marketo for a few years to help client onboarding. The issues that we ran into were that it was difficult to customize the process due to some nuances of our business. By creating a custom object and doing some of the complex decisions in SFDC, it made it possible to pass that information to Marketo and create a better experience for the client.


Q5: What was the most challenging part of building and executing the program?

A: The most difficult part of this project was figuring out what all was needed to build this out. There was a learning curve since this was the first time that we had built something like this. Also, with the customization there was definitely some trial-and-error to get it to where we wanted it. Anytime that you have multiple organizations within a business working together for the first time it can be difficult but overall, I think it was a great success.


Q6: What advice would you give to Marketo users who are creating engagement programs?

A: I would recommend with any project and especially projects of significance, understand what the ask is. Truly understanding what the project needs to accomplish before determining how to build it is critical to creating the best product that you can. Something else that is crucial to projects like this is to give yourself plenty of time to QA. Test everything and then test is again, that’s the best way to make sure these projects have the smallest chance of error when they launch.


We hope you enjoyed reading about Paycor’s new Engagement Programs. To start building your Engagement Program today, check out Josh Hill's 5 step guide to building a successful Engagement Program in Marketo.

In the feedback from the June edition of the Fearless Forum, we received a lot of comments asking for ABM success stories. We spoke with Kyle McCormick, Marketing Systems Manager at Palo Alto Networks, to learn what steps you need to take to achieve success with an ABM campaign.


What are the key steps to launching a successful ABM campaign?

The first and most important step to launching a successful ABM campaign is to define the main stakeholders and everyone’s level of involvement.  At a minimum, this list should include marketing operations, sales leadership and IT (SFDC product owners). Depending on the size of your company and who manages accounts and contacts, this list might be significantly larger and could include sales operations, sales enablement, and business development to name a few. Once these stakeholders are aligned on objectives, timelines, and success metrics for the campaign, the last box to check is determining which accounts will be targeted and how they will be flagged within the systems. When selecting accounts, start small by focusing on 20-30 accounts at most.  If you choose too many accounts, you will not be able to devote the attention needed to nurture those accounts through the sales cycle.


How do you pick the accounts to use for the campaign?

At my previous company, we took a three-step process for selecting our accounts. We first asked our enterprise sales managers to select the top 25 accounts they would like to target. As you would imagine, we were quickly inundated with an overwhelming number of accounts. It would be unrealistic to select all of them and we knew we needed to focus on just 20-30 accounts. Secondly, we leveraged Mintigo to aid in our account selection process by analyzing current customer data. Using their predictive model, we scored the accounts our sales managers sent and then chose target accounts with an “A” grade that were in the 99-100 percentile. Lastly, we got official sign off from all stakeholders to make sure everyone was on the same page as to which accounts would be selected. After receiving approvals, we ended with roughly 30 targeted accounts.


What steps are critical to success?

The most important step to ensure success is preparing and aligning systems prior to launch. At my previous company, we leveraged our sales operations team and pioneered a company-wide cleanup effort to remove duplicate accounts, contacts, and leads prior to launch to ensure we were not targeting accounts we already actively had in the sales cycle. Understanding sales operations’ processes and best practices were necessary to effectively measure the success of our ABM efforts. We also would not have been able to launch an effective program without engaging our information technology/systems team early (and often) to ensure our field-level tracking requirements were understood, tested, and in place prior to launch.


How do you align with other teams critical for campaign success?

Leveraging and integrating technologies, specifically Mintigo and Marketo, can play an essential role in aligning the sales and marketing teams. Understanding and sharing the power of predictive analytics with the sales team helped everyone realize the potential opportunity for account penetration. As previously mentioned, we were able to keep our IT/IS teams aligned with our goals by communicating early and often within the process.


What are some challenges you’ve had to overcome?

The biggest challenge at my previous company was getting global buy-in across all functional teams. Everyone talks about sales and marketing alignment, but it is bigger than just a catchphrase. You need to ensure your IT/IS teams are on board with changing fields in Salesforce and Marketo, and you need to make sure individual stakeholders are on board. The second most challenging obstacle was the account selection process. We went through a massive cleanup effort to remove duplicates, but duplicates will always remain, and it is important to regularly check and clean the database. We had to go through multiple iterations of combing through selected accounts to ensure we were not selecting current customer or partner accounts. 



About Kyle McCormick




Community Page: Kyle McCormick


Have any questions or other ABM success stories to share? We would love to hear them in the comments below.


This content is featured in the August edition of the Marketo Fearless Forum: Edition 03 . Feel free to check it out for more best practices, tips and tricks, and product updates!

Can't believe someone other than me has thought about cryptographic techniques in Marketo! But in this recent Nation thread, a user asked about attaching the SHA-256 hash of a recipient's email address to (email) links.

The aim being to send a secure representation of a lead's email to an external (non-Marketo) site, where it could be looked up as part of a 3rd-party subscription center.

In other words, a link would look like this:

The target site's database would hold pre-computed hashes of all their registered users' emails, the hash being stored in a separate column from the email itself.[1]

A match would then be done from hash-to-hash (the way hashes are always used, since they can never be reversed to the original input) to load the user's record.

Mind you, I have no idea why this would actually be done as opposed to sending the URL-encoded email address:

Or the easily reversed, Base64-encoded (not encrypted) address, which wouldn't require a hash lookup:

Being a realist, I suspect the justification is merely that they want the links to look more “technical” than they would with Base64 (?) and they don't truly have any need for security (as it makes little sense to care about the security of the recipient's own email address![2]). UPDATE: OP says the client now wants AES encryption instead of hashing, so they really are trying to securely transport stuff in the URL. That gives me a chance to bring out the big guns and show you how encryption is done in VTL... at some point.

Anyway, should you need it, here's a utility Velocimacro to gen a SHA-256 hash:

    HashTool in VTL v2
    @copyright (c) 2018 Sanford Whiteman, FigureOne, Inc.
    @license MIT License: all reproductions of this software must include the data above
#macro( SHA256_v2 $mktoField )
## reflect some dependencies
#set( $Class = $context.getClass() )
#set( $java = {} )
#set( $java.lang = {
     "StringBuilder" : $Class.forName("java.lang.StringBuilder"),
     "Appendable" : $Class.forName("java.lang.Appendable")
} )
#set( $ = {
    "MessageDigest" : $Class.forName("")
} )
#set( $java.util = {
    "Formatter" : $Class.forName("java.util.Formatter").getConstructor($java.lang.Appendable)
} )
## get an MD and make it go, then (important) reset MD singleton for reuse
#set( $MD = $"SHA-256") )
#set( $digestBytes = $MD.digest( $mktoField.getBytes("utf8")) )
#set( $void = $MD.reset() )
## gen hex string representation
#set( $hexString = $java.lang.StringBuilder.newInstance() )
#set( $hexer = $java.util.Formatter.newInstance($hexString) )
#foreach( $B in $digestBytes )
#set( $void = $hexer.format("%02x",$B) )
## return uppercase, but note a hex string should be treated as case-insensitive

After including the above in its own token (best to not pollute your user code with utility functions) use the macro like so:

#set( $emailHash = "#SHA256_v2($lead.Email.toLowerCase())" )
<a href="${emailHash}">Click here</a>

Note that I toLowerCase()d the Email before passing it, as that's appropriate for that particular field (as I've written about before, though SMTP addresses are actually case-sensitive, they're commonly matched case-insensitively for sanity's sake).


[1] Hope I'm not hoping for too much here. They'd better be already pre-computing hashes for all the stuff in their database, or this idea goes from merely frivolous to very bad. If they didn't pre-hash, they'd have to hash every record in the database, on-the-fly, for every lookup. This would absolutely destroy performance and be a sign that the back end was not well thought out.

[2] Long as the destination site runs SSL. But if the site doesn't run SSL then the connection could be intercepted and you have a lot worse problems than showing the attacker an email address.

The Forms 2.0 Email type — just like a standard HTML <input type="email"> — won't throw an error on email addresses like:





That is, domains with only a single DNS label to the right of the @ sign (@example), rather than multiple labels separated by dots ( or, are valid entries.

Confusing, sure. But no, it's not a bug. A mailbox @ a single-label domain isn't an “incomplete” or “partial” address, and while most such addresses happen to be invalid on the public internet, it's impossible to know whether they're valid using JavaScript alone.

To understand why, you have to know more about how SMTP domains are looked up in the global DNS.[1] (To my admittedly unreasonable dismay, nothing about SMTP or DNS is taught to marketing students!)

A laughably brief overview of SMTP and DNS

This is silly to try to go over quickly. But here are the basics:

  1. (1) For an email address to be theoretically routable over the public net, it must have a domain part, the part to the right of the @.
  2. (2) For an email address to be factually routable over the public net, the domain needs to have an MX record[2] in the global (public) DNS.
  3. (3) A web browser cannot perform arbitrary DNS lookups on its own, so it cannot know whether a domain actually exists, let alone what records are in the DNS zone. It can only know if it's well-formed: that is, if it matches the string syntax rules for a domain.
  4. (4) A single-label domain like gmail (or, for that matter, a single label like com) is no less a well-formed domain than one with multiple labels, like or looked up, these will differ greatly in terms of being private (assigned year-by-year to a company or other entity) or public (registered semi-permanently to a country or independent NIC operator).[3] And they may or may not be registered, or even legally eligible for registration, at a given point in time. But they are nevertheless all domains.
  5. (5) A single-label domain, if it exists in the global DNS, may also have an MX record (and some, as we shall see, do). It's a rare case, yes — but it's technically valid, and since form inputs don't perform DNS lookups, there's no way to know whether the particular single-label domain somebody entered is routable or is a dead end.

In sum, the only thing a web browser alone can know about a domain, without invoking a remote lookup service, is that it follows proper syntax. It can't have commas instead of dots; it can't have anything other than a tiny subset of ASCII characters (though it may be displayed as if it has more exotic characters, which is another topic); it can't have an individual label longer than 63 characters; it can't have 2 dots in a row; the total length including dots can't exceed 253. That's pretty much it: one string that matches those requirements is as valid as any other.

You might be getting it already, but let's go further

From the intro above, you should already have your mind opened.

But what exactly do I mean when I say a single-label domain may still be a publicly routable domain? Most likely, you've never sent or received mail from joe@gmail, only But that's just because you haven't sent enough email. (Like, you haven't personally sent billions of messages!)

As of today, these TLDs have MX records, meaning the email address user@tld is publicly routable:

.ai .ax .cf .dm .gp .gt .hr .km .lk .mq .pa .sr .tt .ua .ws

These are mostly small island countries, like Anguilla, Dominica, and Trinidad and Tobago. With full respect to the governments of those countries, with relatively limited budgets they may have set the records up accidentally. But the list also includes Guatemala (.gt), Ukraine (.ua) and Sri Lanka (.lk) — so we must assume it's no accident that you can address mail to sirisena@lk.

I get that you still want to “fix” your forms, though

Even if you believe everything above, you probably still want to hand-wave it and require multi-label domains! So here's a little snippet if you insist:


MktoForms2.whenReady(function(form) {   
  form.onValidate(function(nativeValid) {      
    if (!nativeValid) return;      
    var currentValues = form.getValues(),         
    formEl = form.getFormElem()[0],         
    emailEl = formEl.querySelector("[name=Email]"),         
    RE_EMAIL_ASCII_PUBLIC = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;      

    if (!RE_EMAIL_ASCII_PUBLIC.test(currentValues.Email)) {         
        "Must be valid email. <span class='mktoErrorDetail'></span>", 
    } else {         

Obviously if you already have custom onValidate behaviors, you have to integrate this code with those. (When you're flipping the Forms 2.0 global submittable flag on and off, you need to make sure all conditions are considered together.)

A deep note about commercial gTLDs

OK, there is one glitch in my claim that browsers are completely justified in accepting all single-label domains in Email inputs.

In fact, newfangled domains like .space and .lawyer and .futbol are expressly prohibited from having MX records at the top level. From the guidebook for applying to run one of these “new gTLDs”:

ICANN receives a number of inquiries about use of various record types in a registry zone, as entities contemplate different business and technical models. Permissible zone contents for a TLD zone are:

• Apex SOA record.

• Apex NS records and in-bailiwick glue for the TLD’s DNS servers.

• NS records and in-bailiwick glue for DNS servers of registered names in the TLD.

• DS records for registered names in the TLD.

• Records associated with signing the TLD zone (i.e., RRSIG, DNSKEY, NSEC, and NSEC3).

In other words, non-essential records like A or MX records are not permitted at the apex level of these new gTLDs.

The guide does go on to imply that another record type might be permitted by special exception, but it seems awfully doubtful that any would ever be approved:

An applicant wishing to place any other record types into its TLD zone should describe in detail its proposal in the registry services section of the application. This will be evaluated and could result in an extended evaluation to determine whether the service would create a risk of a meaningful adverse impact on security or stability of the DNS.

So, if you know you're dealing with one of these new gTLDs, .lawyer for example, you in fact can be sure without a DNS lookup that joe@lawyer will not be routable over the net, since that top-level domain is not legally allowed to accept mail.

So browsers could, in theory, throw an error on <user@new gTLD> (since they do have a copy of the Public Suffix List internally and don't have to hit DNS for that). But, well, they don't. Go figure!


[1] Plus, it helps to understand that SMTP delivery doesn't even require a DNS lookup. The SMTP standard predated DNS by a few years, and even today billions of messages are passed from server to server without DNS being consulted. But I'm going to ignore this other contributing factor today.

[2] Yes, for the purists, an MX or an A record. I'm not trying to overwhelm people here.

[3] Or of course the 3rd variant: private on the pure DNS level, as they're children of a public TLD, but to be treated as public for purposes of browser security. This is why the PSL exists: it tracks the effective public domains, a list which can't be derived via DNS alone.

By Phillip Wild, original discussion can be found here.


Marketo’s Engagement Programs can do some extremely useful things for the marketer. What was once a series of complicated user journeys can now be distilled into an email drip campaign (called “casts”) based on a cadence you specify.

However, the built-in functionality has one flaw for how we run things at G Adventures:


If you use sub-programs, and a person doesn’t get sent the email via a smart campaign when the cast goes out, the system will stop there and won’t check the next program.


Effectively, when a person enters a sub-program within an engagement program, and triggers a smart campaign, regardless of whether they qualify or not, Marketo will stop. It won't check the next one. Great behaviour in some cases, but not for what we are trying to do at our business.


Let’s illustrate this with an example. Let’s say we have the following structure in an engagement program.



You can see that we have three streams — based on the time zone of the sends — that are otherwise identical. Within the streams, we have a series of 8 emails.


This structure is designed to give a person in this program one email a week on a Tuesday. It’s a group of our most engaged leads, so we are trying to send them the most appropriate content in order to get them to make that last step and book a tour. Each of these emails has an additional set of criteria beyond simply being a member of the engagement program (we don’t want to send our Active tour content to those who habitually book our polar expeditions, for example). So whenever the engagement program “casts” out a send, the default behaviour is:


  1. Check membership of the top program (basically, whether they have received the email previously). If a member, then start this process again, but from the next program down.
  2. If not a member, then check criteria for the send. If criteria passes, send the email. If it doesn’t pass, do nothing.

So that’s great if the user has already received the first email (defined in Marketo-speak as “is a member of the first program”). But if they haven’t, and they don’t qualify for the send, then…and this is the important bit…Marketo won’t check whether they qualify for the next program immediately, but will instead do nothing for that cast and will wait until the next occurrence. So in our situations of 8 programs above, top to bottom, each with narrowly defined criteria, then with a weekly cadence of sends:

  1. If you qualify for emails 1,6 and 7, you would receive an email the first week, nothing for 5 weeks, then emails 6 and 7, finishing with nothing in the last week.
  2. If you qualify for emails 2,3 and 4, you would receive nothing the first week, then an email a week for the next three weeks, then nothing for the remaining 4 weeks.

This clearly isn’t great for what we are trying to do here. We are trying to motivate this highly interested group of people to book, so for them to go several weeks without receiving what would be a highly effective piece of communication is very sub-optimal.

So how can we get around this? One way would be to use emails instead of sub-programs in the engagement program above — but you can’t have additional criteria on an email cast. The email would go out to everyone. That’s not going to work given how targeted these communications are. Not an option. The solution is to use how Marketo defines membership to “skip” non-qualifying emails. (A shout out to Roxanne McGlumphy at Marketo first off for showing us this little-known hack. It’s been incredibly useful for us).You might have noticed from the explanation above that Marketo first checks for “membership” of a sub-program before it even looks into the criteria for sending. If a person is already a “member”, Marketo decides that it must have already received that send, and moves on to the next one — immediately! So if you set membership of a program for those that you DON’T want to receive it, Marketo will skip them through to the next program, and so on until it finds something they are not a member of, or until we run out of content.

To build this in practice, here’s what we did and the thought process behind it:

  • Our deployments trigger from around 2:00am — 6:00pm EST Tuesdays. So membership of sub-programs would need to change before then.
  • We also need to change membership back for those who didn’t end up receiving the email. Otherwise, if they stay as a member of the sub-program forever, they will never be able to receive the send, regardless of whether they qualify or not.

Extrapolating upon this, let’s say our criteria for the first sub-program, “Non Inca Trail Peru”, is something like this. You will receive it if:

  • You’re an email subscriber; AND
  • You’ve never received that email before; AND
  • You are interested in Peru.

So if we want to ensure that those that shouldn’t receive it will be “skipped”, and could potentially receive the next email immediately, then we need a smart campaign to run each Monday night with the following criteria:

  • You’re not an email subscriber; OR
  • You’ve received that email before; OR
  • You’re not interested in Peru.

For anyone who qualifies for that smart campaign, you want to use this flow step: By changing their Program Status of the sub-program to be “Sent” you are making them a member of that program, and ensuring they will be skipped in the next cast. Marketo thinks they have already received it.That program should run weekly, just before your cast goes out. If your casts only occur monthly, then you would only run that smart campaign monthly (you get the idea). Now the second element: for those who didn’t receive the email, change their status back to not be a member of that program. It’s entirely possible that after you changed them to be a member they became interested in Peru, became an email subscriber, or both! So they should qualify for that send next time. This smart campaign will run after all your casts have completed (in our case, after 6pm Tuesday EST), and will have the criteria: Pretty straightforward. Since an email could live in multiple engagement programs, we use “contains” instead of the absolute match of “is” (Marketo believes that an email in a different program is a different email). And the flow step: So each week, here is what is happening:

  1. Before the first cast on Tuesday at 2am EST, we are checking membership for all sub-programs in the engagement program, and marking those who don’t qualify as “members” of those programs.
  2. Marketo runs through the casts and will move straight on to the next sub-program if it finds that a person is a member. So if you only qualify for emails 4 and 5, you will receive email 4 in the first week, and email 5 in the second week. Receiving the email will change a person’s membership of a sub-program to “member”.
  3. Once the casts are done, for anyone who is a member of the sub-program but didn’t receive the email, change them back to not be members of those programs, so they can qualify for future sends.

The power of this should be apparent. You no longer have to settle for one of two options:

  1. Having a generic stream of content where everyone receives ALL emails in the stream, sequentially.
  2. Having a customised stream where you can qualify for one, some, or all, but you have to wait for several casts before receiving the piece you actually qualify for.

This method allows you to build a dynamic queue of content that will be different for each user. And don’t forget, within Marketo engagement streams you can also:

  • Have “timed” content so it only goes out during a certain time period;
  • Change the order of content in the stream to prioritise based on objectives;
  • Use email scripting and/or dynamic content to customise your emails further;
  • Send more than emails — flow steps are available to you as part of sub-programs, so think sales alerts, remarketing lists for social, or even webhooks for direct mail sends.


It’s a bit daunting at first, but once you see the power of engagement programs used in this manner, it’s going to be hard to argue with the results. Happy casting!

By Tom Grubb, Chief Strategy Officer, Digital Pi


What can you say about marketing analytics that hasn’t already been said? Plenty when you know how many well-intentioned marketers too often misrepresent, misread, misunderstand, miscalculate and flat-out misuse – or don’t use marketing analytics at all. Considering the amount of time and money companies invest in people, technology and marketing programs one would think that getting marketing analytics right and putting them to work to improve ROI would be a top priority for every organization right up to the CEO. Even the companies that make marketing analytics a top priority often fail to set the right objectives, ask the right questions, and drive adoption across business functions. The journey to transforming your organization to embrace marketing analytics as the common language of marketing isn’t a journey at all: it’s a mission that requires a leader who’s not afraid to take on this formidable challenge.

Revenue_Attribution_Post_Quote_1.pngIn my work consulting with Marketo customers I’ve seen how marketing analytics champions can become a lightning rod for criticism from all sides for many reasons. The area of analytics that causes the most problems for those championing analytics is revenue attribution reporting – the method of tying marketing performance to revenue in order to use pipeline and revenue as a guide for optimizing marketing. In plain speak, the question can be framed as “show me which programs or tactics had the most/least influence on pipeline created or revenue won.” The good news for Marketo customers is this: the Marketo platform is designed to collect and manage data in a way that lends itself to revenue attribution reporting. Before I dive into Marketo’s revenue attribution model, we need to consider a very different approach to revenue attribution that I encounter in various forms: single source revenue attribution.


For this article, I am focusing on B2B companies. Single source attribution says, we can attribute the entire credit for an opportunity to one buyer – say, Sarah Smith, the Director of Purchasing. If lead source is the one and only attribution we apply, and we say Sarah is the person in the deal who counts the most, then we look at Sarah’s lead source – trade show, and apportion all of the credit for revenue to the trade show that acquired Sarah’s name in our database – great job marketing! Or alternatively, assume instead a sales person found Sarah on LinkedIn and added her name to the database. In that case, all of the credit for the deal would go to Sales for finding Sarah. There are lots of ways to declare single-source credit for a deal, from acquisition program to last response, to home-grown algorithms. The overarching goal for single source attribution is usually to determine whether sales or marketing sourced a deal, so management can allocate resources based on which function is sourcing the most pipeline or revenue. Dividing the world into sales or marketing revenue attribution has great appeal for obvious reasons, but it can lead to a distorted picture of how marketing and sales influence pipeline and revenue creation.

Revenue_Attribution_Post_Quote_2.pngThe example above is built on the idea that a business can declare one person as the entire reason a deal happened, to the exclusion of all other people and their engagement that influenced a deal. In reality, most B2B deals involve more than one contact from the buyer, often defined by their role in the decision such as influencer, or primary. Depending on the product or service being sold, a seller might engage with a few people or many dozens. Let’s say for our purposes we’re focused on deals that average five to ten buyer contacts for any opportunity. In order to get a complete accounting for the role marketing played in any opportunity, we have to consider all of the people associated with the opportunity and understand all of the marketing programs they engaged in – like attending webinars, visiting a booth at a trade show – any and all buyer responses to a marketing stimulus.


In simple terms, B2B marketers are responsible for 1) adding new names to the marketing database and 2) engaging them through marketing programs. If marketing is successful in pursuing those core missions, some of those people should make their way onto opportunities, some of whom should eventually convert to revenue. The big picture looks like this: marketing adds new names acquired by a number of programs/tactics, and marketing engages people through a range of tactics/programs that change frequently. Compared with single-sourced attribution, the world of multi-touch attribution can get very complicated very quickly.


Revenue Attribution the Marketo way

As I stated earlier, the Marketo platform is designed to collect and manage data in a way that lends itself to revenue attribution reporting. Understanding this architecture is the proverbial key to the universe when it comes to getting the full value from your Marketo investment and getting to good analytics. If you already know Marketo, read on anyway because this simplified explanation can come in handy when you have to explain Marketo to the un-indoctrinated.



Marketo channels are your marketing tactics defined as templates – Webinar, Trade show, Online Advertising and so on. For each channel you must define the actions someone can take – these are called member statuses in Marketo. As an example, the Webinar channel typically includes: Invited, Registered, No Show, Attended, and Attended On-demand. You must also state which member statuses you consider a marketing success. Using Webinar as an example again, you would definitely say people who attend your webinar reached success, but when/if someone only registers – is that a success? It depends if you consider someone registering for a webinar a marketing win; some do, some don’t. Marketo declares all successes are created equal – that is, a webinar success has equal value to a white paper download. This prevents marketers from trying to be too clever at assuming we know which engagement influenced someone more than another.  There are lots of attribution models out there, seemingly more every day – this article is devoted to Marketo’s attribution model.

Revenue_Attribution_Post_Quote_3.jpgThere’s one last component you need to understand to get the Marketo architecture religion: Marketo programs. you must create a program for every webinar, trade show, ad campaign, etc. that you plan to run. Those Marketo channels I explained above? Here’s where they come into play. Every time you create a program, Marketo asks you which Marketo channel to use as the template for the program. If you’re creating a program for a webinar, you pick the Webinar channel; for a trade show – the Tradeshow channel and so-on. After you create a program, you can start adding names to the program as members.  Those member statuses, like Registered and Attended? For every member in a program, you can (and must) build the logic in Marketo to change their member status when they do whatever it is that maps to a member status. For example, someone in a webinar would start with member status is “Invited when you add them to the program, then when you send them an invite the member status needs to change to “invited,” when they register it changes to “Registered” and so on.


How you define and use your Marketo channels, member statuses, and which member statuses are successes is the key to defining how you will measure, analyze and optimize your marketing based on marketing analytics. Salesforce has similar architecture design concepts that map to Marketo: Marketo Channels = SFDC Campaign Type, Marketo Channel Member Statuses = SFDC Campaign Member Statuses, and Marketo Success = SFDC Responded. The big differences are, SFDC campaign types are not templatized for re-use, and SFDC does not support the idea of success defined according to each unique channel member status. These seemingly small differences are what give Marketo a big edge in collecting and storing data for reporting. Now you have the essential building blocks to get your Marketo analytics right. I cannot overstate how important it is to get these right, nor how often we see Marketo customers miss on these important elements.


Marketo – the game

Think of marketing as a betting game. You start with a plan, placing your bets on the calendar across marketing tactics and campaigns. You bet some of your budget on webinars, some on newsletters, trade shows, paid online advertising – spreading your bets across multiple marketing channels and dates. The dates approach, the programs run, things happen (or don’t). Some programs bring in new names, some engage names already Marketo. How do you know which bets paid off, and which ones didn’t?



How revenue attribution works

Let’s stick with the assumption that on average, an opportunity has five to ten contacts we engage for any opportunity. Instead of saying any one person or one action gets all the credit for an opportunity, we spread the credit across all of the people associated to an opportunity. If there are five contacts on the deal, we must examine the marketing history of all five contacts to determine the impact marketing had on the deal. If the marketing history for those five contacts shows they were members in twenty Marketo programs, Marketo would examine all of their marketing history in all twenty programs to determine how much credit each program receives for influencing the deal.

Revenue_Attribution_Post_Quote_4.jpgHere’s the big idea: it takes multiple people acquired and engaged by multiple programs across multiple tactics, played out over time to make a deal happen. You have to connect opportunities to people and drill into their marketing history to connect marketing with pipeline and revenue. Many struggle to fully grasp this idea, so I will put this another way: marketing doesn’t invite opportunities to webinars, or talk with opportunities at the trade show booth. If the people they invite to webinars or talk with at the trade show booth eventually become associated to opportunities, Marketo can find the connection between marketing and the opportunity through their actions by First Touch, and Multi-Touch attribution.


First Touch – the Marketo program that acquired the person

If the goal of the program was to acquire new names, the measure of success will be how many new names the program added to Marketo. For example, if you setup a program called “March 2018 Google PPC” to collect new names brought in by your Google ad campaigns, and the program added 100 new names in a month, the “March 2018 Google PPC” was a good bet for Marketing to the extent that it added 100 new names. If you setup your program correctly, every name added to Marketo that originated from “March 2018 Google PPC” program will have the program name “March 2018 Google PPC” contained in the Acquisition Program field on the lead record.  This field is a Marketo default field established when you setup Marketo, and an important one at that. If you want to know which marketing tactics/programs performed best to add names to the database, this is the field Marketo uses to answer the question.

Revenue_Attribution_Post_Quote_5.jpgIn the Marketo Revenue attribution model, Marketo calls this First Touch attribution – the program that gets credit for adding a person to the database. In hindsight they probably should have named it “Program Source” or something like that so people wouldn’t misinterpret it to mean the first program that engaged a person. Marketo acquisition program is similar to a lead source because it addresses the question: where did this person come from? But lead source and acquisition program are different – though there is often uniformity between the two, as in the case when a trade show program acquires a name, we can infer the lead source is trade show from the fact that the program that acquired the name is built on the Marketo trade show Channel. In Marketo, you need to have lead sources and acquisition program values assigned every time, and consistently – even when a name is added by a sales person from the CRM. That’s right, you should create a Marketo channel, call it Sales Generated, and create a Marketo program on the channel to collect new names and assign the First Touch credit for new names added by way of the CRM. That way, you can compare the ROI on acquiring names from the CRM with marketing tactics like Online Advertising or Tradeshows.


Multi-Touch: the Marketo programs that engaged people to reach success

If the goal of the program was to engage names already in the database to prompt them to take a meaningful action – like attend a webinar – every person reaching success in a program puts that program on the boards for consideration toward sharing in the credit for a deal. Think of Marketo as a board game again. You play the game by placing your marketing bets on the calendar with programs and tactics.  You score early wins when people reach success in your programs (attend, download, etc.). Those successes translate to pipeline and revenue influence. If the object of the game is revenue, the winning strategy is to drive true engagement all the time, where the engagement (Marketo program success) translates to pipeline and revenue influence. Note I said influenced, and not created. Marketing does not create qualified opportunities – marketing engages people some of whom will make it to opportunities. Here is a simple example of a multi-touch scenario.


Sue the prospect attends a webinar and downloads a white paper. Bill the prospect downloads the same white paper and attends a live event. Suppose Sue and Bill get added to a $10K opportunity, Marketo will look at Sue and Bill’s marketing history and tally up their successes:

Successes.jpgThat makes four successes total across three programs. Sue and Bill were both in the White Paper program so that program gets 50% of the credit, the other two programs each take 25%.

Analytics.jpgIf the $10K opportunity makes it to closed won, the revenue won amount will be applied by the percentage portion that each program received: $5K to White Paper program, $2.5K each to Live Event and Webinar programs.

Analytics_2.jpgMarketo also splits First-Touch across people, so in this example Marketo would determine the acquisition program for each person and split the first-touch credit accordingly. This was a simple example, as you can imagine the more people involved in a deal, and the more time / engagement that goes by, the more complicated revenue attribution gets. Depending which reporting tool you use, Marketo gives you options to filter and calculate data using different assumptions, including whether to count program successes up to opportunity created date, or through opportunity closed date.


Reporting Revenue Attribution in Marketo

Now that you understand the basics of Marketo revenue attribution, you must learn where and how to report revenue attribution.  Before I dive into that, you need to understand how Marketo calculates the numbers and where those calculations are stored. Those calculations are performed nightly and stored in an application and database separate from your Marketo application and data.  Here are the analytics tools Marketo sells to access and analyze the performance data:

  1. Marketo Advanced Reports Builder (ARB) – formerly named Revenue Cycle Explorer (RCE). ARB is a BI tool that uses an Excel-like pivot table metaphor that supports drag and drop to assemble reports, show them as charts and dashboards and a lot more. When you launch ARB from Marketo, you’re firing up a separate program. Don’t go looking for ARB in your Marketo UI, it’s still referenced by its old name Revenue Explorer.

  2. Program Analyzer: An x-y axis chart that lets you pick and choose plots using revenue attribution field and values. Interestingly, this tool has a few calculations that you don’t see anywhere else, like cost per MQL.

  3. Marketo Performance Insights (MPI): this is a new tool offered by Marketo that is purpose-built to make it easy to see program success and revenue attribution packaged in the Marketo Insights UI.

Performance_Insights.jpgWhere ARB gives you the kitchen sink to build anything you want the way you want, MPI provides the essential program performance reports pre-built, with some flexibility provided in the form of drop down filters, and export features. What you trade-off for the complete flexibility you get in ARB, you gain in rapid time to productivity and from my limited experienced so far with MPI, some real-time performance gains on refreshing charts and tables.


Marketo Attribution Model Choices

Marketo was early to the marketing attribution game to realize the impact contact attach rate with opportunities (or lack thereof) has on revenue attribution reports. If sales doesn’t add anyone to an opportunity, there’s nobody available for Marketo to consider for marketing influence. So Marketo gives you a configurable setting that determines how it will identify people to associate with opportunities:

  1. Explicit: only count the contact roles associated with opportunities (this is SFDC’s one and only way to make the people connection for its pipeline influence report)

  2. Hybrid: look for a contact role on the opportunity; if it doesn’t find at least one, look at the opportunity company, find the account contacts with and without roles, and consider all of them for influence

  3. Implicit: Look at the opportunity company, find the account contacts with and without roles, consider all of them for influence. Think of this as account-based influence.

When you change the setting, it takes effect in the next overnight data calculation. You can run the numbers under all three settings over the course of three days. Given the time lag, it’s a good idea to plan your reporting well in advance as much as possible.


Behind the numbers

If you design your Marketo correctly and apply rigor to process and data every day, you will get revenue analytics that tell you a lot about your marketing that will help you be a smart marketer. If you don’t, you may expose yourself to revenue analytics that will tell an incomplete, or inaccurate story – and you may not even realize it. Even one bad list import can have a detrimental impact on your reports. Here is my attempt to list the data and process areas you need to consider:

Analytics_Issues.jpgThere may well be more I have not listed here. What’s the biggest room in the world? Room for error when it comes to revenue analytics (not just Marketo, any revenue analytics). It is possible to control these challenges, but you need to be aware of them, monitor for them, and get everyone on board to keep your data clean.

A final word about attribution reporting

It isn’t enough to understand how this all works. It isn’t enough to get your data right and keep it right.  If you understand Marketo revenue attribution, and you get your data right, and you build the standard set of reports -- you still have the most formidable challenge ahead of you: getting your organization to understand, buy into, and use the reports to continuously optimize marketing ROI. If marketing needs its own language – and it does – it’s marketing analytics. There’s a lot more to making marketing analytics the common language of marketing in an organization than I can cover here. It’s a subject I have a lot to say about in the forthcoming May 2018 issue of Applied Marketing Analytics: "How organizations can establish marketing analytics as the common business language to drive continuous improvement."


Originally posted on LinkedIn on April 24, 2018

When choosing a Thank You/Follow Up URL, Form Editor presents 3 largely self-explanatory options:


Choosing Landing Page will give you a nice dropdown of all your approved Marketo LPs.

Choosing External URL and entering the full URL of a Marketo LP seems to work, too — but it's a bad move. Form Editor won't tell you why, but I will.

The problem

If you choose Landing Page or Stay on Page, Marketo uses a special read-your-write feature to ensure Pre-Fill on the next pageview.[1]

If you choose External URL, Marketo looks up the URL in your known LPs but it doesn't look in your Redirect Rules.  If the URL is not seen as a core LP URL, even if it does lead to a Marketo LP in reality, then the read-your-write Pre-Fill cache is not enabled.

What is “read-your-write” and why should I care?

“Read-your-write” or RYW is a desirable, but not always supported, feature of distributed systems. (A distributed system is any system that processes data at multiple tiers, like we all know Marketo does.)

RYW, in a nutshell, means:

If a user thinks they've made a change to the back end, then show them the new data as if it's been fully saved, regardless of whether system(s) may be still filtering and processing — or even discarding! — the data in the background.

It doesn't mean anybody else sees uncommitted data (there are some cases in which it won't be saved, so you don't want to propagate bad info more widely than necessary).

It means that for a less confusing user experience, you let a user see their own requested update immediately, instead of forcing them to see older saved data for a few seconds/minutes (even though showing saved data would technically be more accurate end-to-end).

Marketo implements read-your-write via the special query parameter aliId. It's a numeric identifier for a set of submitted form values, and it's usable regardless of whether the values are actually saved. When a page has an aliId in the URL, it's capable of (for the most potent example) filling a second form using the data from a first form, even if it was submitted mere milliseconds before.

Back to Form Editor

When you choose External URL in Form Editor, Marketo tries to append the aliId intelligently, but it can't know about your redirects (especially 3rd-party redirects) or anything that might disguise the Marketo-ness of the destination URL.  As a result, the next LP someone views may show their session's old data (maybe including someone else's email) or no data at all (the session still being effectively anonymous). You don't want that! So choose Landing Page if it's indeed a Marketo LP.

Other complications

When you use the Forms JS API onSuccess method to choose a Thank You URL dynamically (a very powerful option) set the form to Stay on Page in Form Editor, as this will ensure the aliId is appended.

Then clip out and append the original query string, which will include the aliId, to the dynamic URL.

A quick-and-dirty way to append the original query is like so:

    var originalThankYouDoc = document.createElement("a");
    originalThankYouDoc.href = tyURLFromFormEditor;
    var dynamicThankYouURL = "" +;
    document.location.href = dynamicThankYouURL;
    return false;

If you're using a fuller-featured URI parser/builder already, like uri.js, use that instead of the Link/Location method (though the breakdown works perfectly, since it's a key browser function).

Just don't write your own parser… I don't want to have to holler at you again!


[1] When you use Stay on Page the aliId is attached even if you're using the form embed and the page is a non-Marketo page. It's not fully honored in this case (since Pre-Fill isn't supported with the embed) but it's better to have it there than not.

University Day.png


It's not too late to register for University Day!


Accelerate your Marketo learning at the Marketing Nation Summit 2018! Join us at University Day on April 29, 2018, where you can take your Marketo skills to the next level and build expertise will focused instruction, practical demonstrations, and inspiring presentations by our Marketo experts.


Register Now and add to your Summit registration!


In the Bay Area but can't make it to the full four days of Summit?

Check out our University Day Single Day Pass. Register Now

Little-publicized, to the point that even Support might not be in the loop: the Visibility Rule Contains operator does allow multiple values.

The Contains value is interpreted as a full-fledged regular expression, so use the pipe character | to separate sub-values:


In fact, because of this unexpected/undocumented power, you must anchor an expression to avoid surprises:


Otherwise, it's doing a partial match of the full value against each of your pipe-delimited alternatives. This might not be a problem on your forms right now, but if it becomes a problem, you wouldn't know about it immediately.

For example, if you were using uppercase state names, then

     Contains KANSAS|IDAHO

would match both ARKANSAS and KANSAS, and you should use

     Contains ^(KANSAS|IDAHO)$


You can also of course do more fancy matches with regexes if you want. And Not Contains is also a regex!

There are 2 ways to access references (i.e. variables) in Velocity.


(1) Simple/shorthand notation, prefixed with a $:




(2) Formal/longhand notation, prefixed with $ and wrapped in {} (curly braces):




Simple notation should be your default. Formal notation should only be used when there's no alternative. As the Velocity docs say:


In almost all cases you will use the shorthand notation for references, but in some cases the formal notation is required for correct processing.


I'd go further: when used unnecessarily, formal notation can make your code confusing and fragile. Unfortunately, Marketo's script editor doesn't encourage best practices, because when you drag a field from the field tree to the editor canvas, it's automatically  wrapped in ${}:



My recommendation: remove the curly braces right away, and only restore them if it proves necessary during development.


What can go wrong?

A recent Community post shows how confusing things can get when you use formal notation unnecessarily (note how the poster titled it "... tokens behave unexpectedly" when it's actually established behavior, if not well-circulated).


The catch: a reference enclosed in ${formal.notation} cannot be chained with a method/property outside the curly braces.


OK, that probably didn't make sense unless you're fluent in OO-speak! Let's look at some examples.


This does work with simple notation:


#if( $lead.FirstName.isEmpty() )


It doesn't work if you only enclose part of the expression in formal notation:


#if( ${lead.FirstName}.isEmpty() )


Sure, it would work if you happened to enclose the entire expression in curlies


#if( ${lead.FirstName.isEmpty()} )


but you should just use simple notation instead, because it's harder to mess up during refactoring.


Don't believe me? (You probably do, or I'd like to hear who's more authoritative about Velocity.) Consider what happens when you move from a not-best-practice, but syntax-error-free, comparison using double-equals ==:


#(if ${lead.numberOfProducts} == 0 )


to a more forward-looking equals():


#if( ${lead.numberOfProducts}.equals(0) )


Suddenly, your Velocity token stops working because you can't have a . after a }. It may be clear, to you as a human, what you meant, but VTL doesn't allow it. If you'd used simple notation from the start you wouldn't have to make such adjustments.


When do you need to go formal?

Formal notation should only be considered inside strings or output, and only used when the separation between variables and static text would otherwise be unclear.


If you're in a line of code starting with #if or #set chances are very slim that you should be using formal notation.


Here's one case where it is necessary...


Read the full post on

You've seen before that the Forms 2.0 input mask plugin can be tweaked to do some more elegant stuff.

Here are a few more things you might want to do. I only recommend masks for things that have an explicit standard (like phone numbers, credit cards, etc.). But if you're going to use them for more than that, use them wisely!

The code is bundled at the bottom of the post.


By default, the mask shows a _ character in every empty position up to the max length.

This can look ugly, especially if there's already a hint in the field telling people it's limited to N characters.


You can remove the placeholder character entirely or replace it with something else (though I'm not sure anything is better than underscore or blank in this case, maybe a cursor block like ░ if you want to be retro-cool?).


When you select the a shortcut in the Form Editor…


… you're actually blocking all but the slimmest subset of alphanumeric characters.Even the e-with-acute-accent in Grégoire is blocked! That's not good when words contain these slightly out-of-the-American-ordinary characters.

It works this way because, under the hood, input masks use the regex character class [A-Za-z] to implement a. And that's ASCII unaccented letters only.

The better move is to use a class like [A-Za-z\u00C0-\u024F] which includes Latin accented letters, i.e. those from Romance languages. (You'll still be blocking people who spell their names using other glyphs, but that's another matter.)

Better still, if you're in the world of names, allow hyphens, apostrophes, periods, and spaces: [A-Za-z\u00C0-\u024F'. -].


When you set a mask to **********, even if you don't want non-ASCII, non-accented characters, do you really mean that spaces aren't allowed? Sometimes yes, sometimes no.

In the case of ISBN-10s, for example, no spaces are allowed per the international standard, and you might want to block spaces in the Phone field for standardization.

But in the case of First or Last Name, Company, or tons of other cases, you certainly don't want to block spaces.


All of the above functions can be enabled with the helper JS below.

First, in Form Editor, give each field you want to re-mask an initial mask (any mask pattern will do). (If it isn't masked to begin with, we can't tweak the mask setup.)

Then add your custom patterns to the inputMasks array as below: the name property is the Marketo form field name and the rest is explained in the comments.


(function() {
   MktoForms2.$("body").on("mkto_inputmask_polyfilled", function(e) {
      var inputMasks = [
            name: "Title",
            maskPattern: "ssssssssss", // 10 [A-Za-z ] letters or spaces
            maskCharPlaceholder: "" // no placeholder char
            name: "Nickname",
            maskPattern: "nnnnnnnnn", // 10 [A-Za-z0-9 ] chars or spaces
            maskCharPlaceholder: "" // no placeholder char
            name: "Interest",
            maskPattern: "cccccccccc", // 10 Latin letters or (some) Latin puncuation marks
            maskCharPlaceholder: "░" // alternate placeholder char to show how it's done

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

      MktoForms2.whenReady(function(form) {
         var maskCharExtensions = {
            c: "[A-Za-z\u00C0-\u024F'. -]",
            s: "[A-Za-z ]",
            n: "[A-Za-z0-9 ]"

            .forEach(function(char) {
              MktoForms2.$.mask.definitions[char] = this[char];
         }, maskCharExtensions);

            .map(function(field) {
               field.el$ = form
                  .find("[name='" + + "']");
               return field;
            .forEach(function(field) {
               var mask =
                     typeof field.maskPattern != "undefined"
                        ? field.maskPattern
                        : field.el$.data("mktoInputMask"),
                  placeholder =
                     typeof field.maskCharPlaceholder != "undefined"
                        ? field.maskCharPlaceholder
                        : MktoForms2.$.mask.placeholder;

               field.el$.mask(mask, { placeholder: placeholder });

Filter Blog

By date: By tag: