Skip navigation
All Places > Products > Blog > Author: Sanford Whiteman
1 2 3 4 Previous Next


60 Posts authored by: Sanford Whiteman

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 });

Boolean type fields enjoy a strange place in Marketo's Velocity implementation.

On the Marketo UI side and via most Marketo APIs, Booleans hold consistent true/false values (presumably using SQL bit type fields under the hood).

But in Velocity, as I've explored before, a Boolean field on the Lead/Person is presented as one of two Strings:


"1" for Boolean true

"" (empty string) for Boolean false


That conversion is quite confusing in its own right (I don’t know of any other system that does that particular stringification) but even more confusing is how Velocity deals (or not) with it.


Remember, Velocity gets a lot of things from Java.


But in Java, there’s no such thing as a Boolean-ish value with truth-iness and false-iness qualities. There are only real Booleans. If you try to use a non-Boolean in a Boolean context, you get a fatal error.


Velocity strives to be an easier-to-use dialect of Java which throws fewer errors. Its way of avoiding this particular error is a simple rule: anything other than null or the real Boolean false is truthy. The empty String and any non-empty String are both truthy!

So if you do:

#if( $lead.someBooleanField )
Field is true
Field is false

You will always get “Field is true” even if you've unchecked the corresponding checkbox in the Marketo UI.

If Marketo Booleans were Velocity/Java Booleans, on the other hand, this would've worked fine. But alas.

So the question is what to do with these sort-of-Boolean-like-Strings (whose meaning we understand, but the VTL language doesn't). What do we convert them to to make them more usable?

You can do a step-by-step conversion to Boolean by doing a String comparison:

#if(  $lead.someBooleanField.equals("1") )
#set( $lead.someBooleanField == true )
#set( $lead.someBooleanField == false )

After that conversion, #if($field) will work as expected.

But I've been thinking lately that maybe converting those Strings to Numbers — 0for false and 1 for true, as the standard goes — gives us more juice within Velocity than if we were to go with true Booleans.




Read the full post on TEKNKL :: Blog →

    Velocity is the only language where Brevity for Brevity's Sake is OK.

    In other languages, reducing total lines of code (LOC) with show-offy shortcuts just means your code will make no sense (to you or anyone else) after a few months away.

    But in Velocity, the shortest possible implementation is a smart move, since:

    • VTL is verbose (only one major operation permitted per line, temp variables required even for void methods)
    • it offers limited reusability (especially Marketo's flavor of Velocity, where #macro and reused $references don't work consistently)
    • it's completely whitespace-preserving (indenting would help readability, but messes up output — so the fewer lines, the less to mess up)
    • the more lines of code in your template, the more distraction from the “meat,” which is the output

    Imagine you had a few Lead fields using the JETDS naming system: installationEnvironment, typeOfEquipment, and purpose.

    There are 10-20 options for each of these fields, so the number of total combinations is huge. Even if you're only interested in a subset, the conditions get loooooong...


I'm being generous with whitespace here, 
but it's *still* hard to read/maintain! 
VTL is whitespace-preserving, so indents are true spaces.
#if( $lead.installationEnvironment == "amphibious" )
 #if( $lead.typeOfEquipment == "radar" )
   #if( $lead.purpose == "receiving" )
   do something...
   #elseif( $lead.purpose == "detecting" )
   do something else...
   #elseif( $lead.purpose == "navigation-aid" )
   do something else entirely...
 #elseif ( $lead.typeOfEquipment == "sonar" )
   #if( $lead.purpose == "receiving" )
   do yet another thing...
   #elseif( $lead.purpose == "detecting" )
   do still another thing...
   #elseif( $lead.purpose == "navigation-aid" )
#elseif( $lead.installationEnvironment == "ground-mobile" )
 #if( $lead.typeOfEquipment == "radar" )
   #if( $lead.purpose == "receiving" )
   #elseif( $lead.purpose == "detecting" )
   #elseif( $lead.purpose == "navigation-aid" )
## etc., etc.


Read the full post on TEKNKL :: Blog →

Y'all have seen this type of double-confirmation popup, I'm sure:


It's helpful if you want to give end users a little additional nudge, for example to use their legal name, a working email address, and so on — stuff that you can't force them to do but maybe can guilt them into.

No one but me would claim it's “easy” to add this functionality to a Marketo form. It only took an hour to whip it up, but as you know, I waste spend more time in Forms 2.0 API-land than anybody else. I've provided a big leg up: a working CodePen you can spin off for your own site.

Most of the heavy lifting is done in CSS: overlaying the background, positioning the popup bar, changing the color of the Cancel/Go Back button. Aside from managing the visibility of the bar (display: block or display: none) there's not a huge amount happening in JavaScript.



Read the full post on TEKNKL :: Blog →

Sanford Whiteman

Fixing Marketo pURLs

Posted by Sanford Whiteman Nov 30, 2017

It's well-known that Marketo's pURL feature, out-of-the-box, has a fatal (and kinda fascinating) shortcoming. If someone has visited your site before — meaning either an anonymous or associated Munchkin session— then pURL-enabled pages aren't functional.

When there's an existing anonymous session, the server ignores the personalized part of the URL (the Marketo Unique Name /JillSmith02 or Unique Code /XYZPDQ) and serves up default/empty content.

On the other hand, if the person has never been to your site on their current device, pURLs work fine.

Unfortunately, then, pURLs work only for people who either [a] just got a new PC or phone or [b] have been living under a relative rock. Not exactly the widest audience!

Luckily, it's always been possible to fix the strange session behavior with a little JavaScript.



Read the full post on TEKNKL :: Blog →

With an assist from Velocity, your emails can have time-responsive content.  (And I don't just mean Happy ${day_of_week}, ${first_name}!)


  • When the same email is resent with different primary content (e.g. a weekly newsletter), Velocity can customize secondary content based on the day: like reader PW, you can show a special promo only in the first send of the month, every month.
  • Like user GM, you can pre-create a series of different content blocks, in effect creating a drip campaign from just one asset.
  • If a triggered email is sent off-hours, you can notify the recipient that they'll hear from Sales on the next business day.
  • Or lots of other adventures!


To run the snippets below, set common variables at the top of the script token or in your global {{my.includes}} token (you do have one, right?) like so:


#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/New_York") )
#set( $defaultLocale = $date.getLocale() )
#set( $calNow = $date.getCalendar() )
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )
#set( $calConst = $$calNow) )
#set( $ISO8601 = "yyyy-MM-dd'T'HH:mm:ss" )
#set( $ISO8601DateOnly = "yyyy-MM-dd" )


I cannot stress enough that setting the IANA time zone for your location is critical when using Date or Calendar objects in Velocity.  If you do not do this as is the case, unfortunately, with some old code snippets on Marketo's blog your code is broken. I won't rehash the reasons why here (happy to in the comments) but believe me you must set the timezone!


Now, for some examples...


Check the current day of the month

#set( $calNow = $date.getCalendar() )
#if( $calNow.get($calConst.DAY_OF_MONTH) <= 7 )
It's one of the first 7 days of the month!


Check the current week

#if( $calNow.get($calConst.WEEK_OF_MONTH) == 1 )
It's the first calendar week of the month!


#if( $calNow.get($calConst.WEEK_OF_MONTH) == $calNow.getActualMaximum($calConst.WEEK_OF_MONTH) )
It's the last week of the month!


Check if it's the Nth Something-day of the month

#if( $calNow.get($calConst.DAY_OF_WEEK) == $calConst.WEDNESDAY &&
     $calNow.get($calConst.DAY_OF_WEEK_IN_MONTH) == 3 )
It's the 3rd Wednesday of the month!


Is it during business hours?

#set( $calStartOfBusiness = $date.getCalendar() )
#set( $ret = $calStartOfBusiness.setTimeZone($defaultTimeZone) )
#set( $ret = $calStartOfBusiness.set(
) )
#set( $ret = $calStartOfBusiness.set($calConst.MILLISECOND,0) )
#set( $calCloseOfBusiness = $date.getCalendar() )
#set( $ret = $calCloseOfBusiness.setTimeZone($defaultTimeZone) )
#set( $ret = $calCloseOfBusiness.set(
) )
#set( $ret = $calCloseOfBusiness.set($calConst.MILLISECOND,0) )
#if( $calNow.compareTo($calStartOfBusiness) >= 0 && $calNow.compareTo($calCloseOfBusiness) <= 0 )
It's during business hours!


Note here that 8,0,0 and 17,0,0 are setting the hour, minute, and second respectively, in 24-hour time.


8,0,0 means 08:00:00 or 8 a.m. and 17,0,0 means 17:00:00 or 5 p.m.


Is it a business day?

#set( $businessDays = [
] )
#if( $businessDays.contains($calNow.get($calConst.DAY_OF_WEEK)) )
It's a work day!


You can of course combine this business days check with business hours above.


Is our timed promo active?

#set( $calStartOfPromo = $convert.toCalendar(
) )
#set( $calEndOfPromo = $convert.toCalendar(
) )
#if( $calNow.compareTo($calStartOfPromo) >=0 && $calNow.before($calEndOfPromo) )
The promo is active!


Note again the mandatory use of defaultTimeZone when initializing new Dates.


And check out how I'm using compareTo and before to see if we're currently greater than or equal to the start date and less than the end date.


You could substitute compareTo($calEndOfPromo) < 0 for before($calEndOfPromo) as it's the same logic, just a tiny bit longer.


Promo expires 7 days from today (whenever that is)

#set( $FRIENDLY_24H_DATETIME_WITH_FRIENDLY_TZ = "MMM dd, yyyy HH:mm z" )
Our promo expires 7 days from today, which is


This quickie shows how to shift a certain number of days from today. To go backwards 7 days, use add($calConst.DATE,-7) (there's no explicit subtract method).


How long will the promo last?

#set( $ret = $calNow.set(
) )
#set( $ret = $calNow.set($calConst.MILLISECOND,0) )
#set( $calEndOfPromo = $convert.toCalendar(
) )
#set( $diffRemaining = $date.difference($calNow,$calEndOfPromo) )
#set( $daysRemaining = $convert.toInteger($diffRemaining.getDays()) )
#set( $weeksRemaining = $convert.toInteger($diffRemaining.getWeeks()) )
The promo
#if( $weeksRemaining > 0 )
ends in ${weeksRemaining} ${display.plural($weeksRemaining,"week","weeks")}!
#elseif( $daysRemaining > 0 )
ends in ${daysRemaining} ${display.plural($daysRemaining,"day","days")}!
#elseif( $daysRemaining == 0 )
ends today!
already ended!


Unlike adding and subtracting days to/from today, differencing dates is relatively complex.


First, you usually[1] want to align dates to midnight (yyyy-MM-ddT00:00:00.000) boundaries. That's why I set the hours, minutes, seconds, and milliseconds of $calNow all to zero, to create a Calendar object representing "Today at midnight."


Then the end date is also anchored at 00:00:00 (you could use any time of day, as long it's the same for start and end, but midnight is easiest).


Once aligned, you won't get any fractional-day surprises with $date.difference(). difference() returns a robust Comparison object, from which you get the count of days, weeks, and so on between the dates.


I also took this opportunity to use $display.plural(), which isn't a date-related feature but just another cool Velocity thing you should know. plural() works with Integers, though, while Comparison.get accessors return Longs. That's why $convert.toInteger() is in there.


Times like this, you can see why I say Velocity is anything but simple. So if you're really a newbie, you shouldn't say, "I'm not too advanced with Velocity" as if you're just a couple scripts away from mastery! To truly add Velocity to your skill stack is a slow grind.


Add an ordinal indicator to the day, like “1st” or “15th” instead of just “1” or “15”

#define( $enUSDayOrdinalIndicators )
1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st
#set( $indicatorList = $enUSDayOrdinalIndicators.toString().trim().split("\s?\d+") )
Today with a friendly ordinal indicator (is
  "EEEE, MMMM d'${indicatorList[$calNow.get($calConst.DAY_OF_MONTH)]}'",


This add-on will give you a super-friendly date display, which isn't natively supported by Java or Velocity:


Wednesday, August 15th


(The "th" is the non-native part.)


It should be clear that this is very culturally-specific stuff, and since it isn't part of the localized parts of Java Calendar (which I mean to write a dedicated post on soon) you would have to add your own localized Nth and Nst for locales other than en-*, some of which never use indicators at all, like Swedish.



Learning More

Get confused enlightened by the Java Calendar docs.





[1] Exception is when a period ends at, say, exactly 5:00 p.m. on a certain date and you do want to take hours (fractional days) into account. Sweepstakes laws might require such precision. Or if you're trying to do something like hours-until-webinar, you'd also want to align to hours (still zero out minutes/seconds/millis for sanity).

Twilio offers a pretty awesome Lookup API to enrich phone numbers with metadata: it can distinguish mobile and landline numbers (Sales will thank you!), determine carrier (which has demographic significance, like it or not), and even do reverse Caller ID lookup.

It's reasonably priced, and if anyone knows this stuff, it's Twilio.

The weird thing is that even though the Lookup API is 100% webhook-compatible, their docs don't show it being called as a simple webhook request. They only show it in the context of Java, PHP, or other code.

Anyway, here's what's needed to call it as a webhook.

Lookup requests are authenticated using HTTP Basic Auth. Your Twilio Account SID is the username; your Account Token is the password.

    So you just need to create an HTTP Authorization header and then add it to the webhook definition in Marketo. (Marketo supports Basic Auth just fine, but it doesn't present you with distinct username and password boxes.)

    An Authorization header for Basic auth looks like

Authorization: Basic <base64(<username>:<password>)>

where <base64(<username>:<password>)> means simply

  • concatenate the username and the password with a colon : in-between
  • Base64-encode the concatenated string



Read the full post on TEKNKL :: Blog →

A Community question from Marketo user Randall Tam about generating personalized PDF certificates (for Continuing Education classes and such) sent me back to some old bookmarks to see which service would be quickest to integrate with Marketo LPs.

The idea here is to use LPs as your PDF templates. LPs are inherently personalizable via tokens and segmentations, so they're a great base for generating per-lead PDFs. (Your designer will be using HTML and CSS, so the output may not be as awesome as what you'd get from InDesign, but it'll work great for most scenarios provided you have a skilled designer.)

Read the full post on TEKNKL :: Blog →

     It's easier to customize a Marketo form if it's completely destyled to start. A float or margin-left that doesn't seem to respond to !important can drive a designer crazy, and time spent on CSS specificity & cascade behavior isn't well spent.

My li'l snippet for removing Marketo's built-in styles has seen a lot of use via the Community and a few code versions (now at the venerable v1.103) but it hasn't appeared on the blog before.

Here's a sample form with the Simple theme:

And here's that same form after running destyleMktoForm to reset it to browser defaults (check out the Times New Roman):


Let's dive into the destyleMktoForm function a bit, since I always insist on a teachable moment


Read the full post on TEKNKL :: Blog →

This was a nice catch by user AD on the Community and something I somehow missed before! It has significant effects on tracking and on branding in general.


When you set up Domain Aliases in Marketo (and corresponding CNAMEs in DNS) you can access your Marketo-hosted LPs via a choice of different URLs. The path/page name stays the same, but you can use a different domain.


      For example, you can use for the bulk of your pages, but publish some pages under for special products. The pages will actually be accessible under any of your Aliases as well as your Primary (i.e. if somebody manually changed the URL in their browser) but by taking care to link to the preferred domain from ads, social, and email, you can keep content/layout/logos/etc. totally different.


    If you're an agency, you might even use Domain Aliases to service multiple clients at the same time, the only restriction being you can't use the exact same /pagename but would have to mix it up like /jiffylube_coupons_2017 and /spiffylube_coupons_2017.


Cool concept, so... ?

Problem is, siloing Domain Aliases from each other falls apart if you select a Thank You page from the dropdown of Marketo LPs.



Read the full post on TEKNKL :: Blog →

A good question from a reader today who asks, My client claims you can't have a From: or Reply-To: domain that's exactly the same as your click tracking domain (what Marketo calls the branding domain). Is this true?


Yep, within the confines of Marketo this is true.


That is, you can't send with the From: header:


From: All Eyez on Me <>


and have your rewritten tracked links use the exact same domain:


<a href="">Learn more</a>


You can of course use a subdomain


<a href="">Learn more</a>


… which is what we're used to (branding domains are click-dot-whatever, landing page domains are pages-dot-whatever).


But why? It's not merely a tradition but based on a longstanding technical rule.



Read the full post on TEKNKL :: Blog →

Newbie Q: Where do I click to see X?


Oldie A: You don't.


the least happy type of Community thread


New users may search in vain for a log of all Filled Out Form events plus their point-in-time form data. (That is, even if field updates were blocked or values were later overwritten, you see the values that were on the form when each person clicked Submit).


First: it's not correct to say that Marketo doesn't store the original data from form posts: in fact, the REST Activity API includes historical POST payloads, including since-overwritten values. But it's true that it's not easy for a non-developer to see form data history across leads.


When seeking such a report outside of Marketo, users tend to say a Google Sheet (as opposed to XLS/CSV, Excel 365 online workbook, or something else). I'm not a Sheets user myself unless forced, but I know how it's done, and it's reeeeeally easy. So that's what we'll use today.


Read the full post on TEKNKL :: Blog →

A pesky problem with Munchkin is that you can't selectively turn it off based on visitor characteristics.


You can choose Disable Munchkin Tracking on Marketo LPs, but that's for every visitor, and on your corporate site you're unlikely to have access to an on/off switch. So internal visitors show up in your stats (okay when testing, but bad after go-live) and it would be great to be able to exclude them, wouldn't it?


Although the best place to check IPs would of course be on the Munchkin server itself (thus no extra requests) we can ping a remote service to get the end user's IP, then use a l'il JS wrapper to conditionally load Munchkin based on whether the IP appears in allowed/disallowed Access Control Lists (ACLs).


Read the full post on TEKNKL :: Blog →

Little-known and therefore little-hated! this bug applies only to Marketos email-specific Velocity setup, and not to the language in general.


     If you mentioned it to someone whos built webpages (as opposed to emails) using VTL, theyd rightly look at you like youre crazy. And the necessary workaround also goes against every coding principle I’ve ever endorsed.


     In other words, please dont take any general-purpose Velocity guidance from this post, but do use the code for this specific goal.


What's the problem?

Even when you follow the prescription for generating tracked links in Velocity (namely, output fully-formed <a> tags from VTL, as opposed to outputting bare URLs and trying to plug them into <a>s in the outer email content) you still wont be able to output multiple tracked links in some common cases.



Read the full post on TEKNKL :: Blog →

Filter Blog

By date: By tag: