Skip navigation
All Places > New York User Group > Blog
1 2 3 Previous Next

New York User Group

80 posts

Hello! GLG is hiring a New York based Demand Generation Manager focused on the client. If you've worked in demand gen and are interested in working on programmatic campaigns geared towards increasing product usage, this is the role for you!


Job Summary

The Manager, Client Demand Generation will be responsible for developing strategies and campaigns that support GLG’s client-facing demand generation strategy across all sectors – financial services firms, professional services firms and corporations. Reporting to the VP, Marketing Operations you will develop, execute and measure initiatives that drive revenue by generating usage of GLG’s services along the client lifecycle.  Demand generation generally, and client engagement in particular, is a newly-created function within GLG Marketing. We are looking for a strategic thinker who can roll up his/her sleeves and that has the know-how, experience, curiosity, and ability to drive results. Success in the role will require close partnering with business unit marketing VPs, Product Management and Engineering, Sales and Customer Solutions to drive strategy and campaigns. Functional orchestration in strategy, planning, execution, cross-team collaboration, data and analytics and reporting are key.


The ideal candidate will know how to cross-functionally understand what it takes to build a client engagement strategy, be accountable for critical pipeline and conversion targets, and work collaboratively with key stakeholders across marketing, business units, Sales and Client Solutions teams to successfully align with company objectives.


  • Construct a best-in-class practice and supporting processes to identify and address opportunities to drive client activity by deploying 1-to-many strategies along the client lifecycle.
  • Build a multi-faceted strategy across business units, client types and content types – starting with recommendations – to deliver a high degree of relevance and stimulate demand.
  • Create and deploy always-on and targeted automated campaigns to deliver relevant and actionable content to clients – either directly or in collaboration with Client Solutions or Sales – with clear goals, objectives, strategies and tactics.
  • Grow marketing contribution to revenue by creating and executing the client-facing demand generation campaigns.
  • Develop benchmarks, track and report on metrics to capture conversions and resulting revenue driven from marketing activities, and develop key indicators to track expected delivery of growth.
  • Collaborate across Marketing to ensure client-facing demand generation activities integrate with and align to other marketing efforts targeting clients.
  • Collaborate across GLG to contribute to the optimization and maintenance of Salesforce and our various databases in order to ensure a strong data foundation for all client-facing engagement.



  • 5-7 years hands-on experience in a B2B demand generation and/or client engagement environment
  • Analytical and willing to roll up the sleeves, with the ability to capture and share key data, insights and use data to drive key strategies and decisions
  • Hands-on experience using Salesforce (or similar CRM software) and Marketo and other marketing and sales technology; familiar with the principles of design and management of a customer database
  • Should be highly organized and detail-oriented with the ability to juggle multiple projects; must be comfortable setting priorities and communicating to others
  • Proven track record of expanding engagement through innovative strategies and execution
  • Working knowledge of client profiles and workflow across financial services, professional services and corporate markets
  • Collaboration and communication, including strong relationship skills and focus on team building
  • Able to deal with ambiguity, cross-functional and multitask projects
  • Proven ability to drive for results, in a fast paced, highly-matrixed work environment



job post demand generation manager demand generation

The {{system.viewAsWebPageLink}} token always points to your Primary LP domain. But you want View As Web Page (VAWP) to use the Domain Alias for each brand or other subtenant in your instance.


Though the path to the VAWP service is clearly exposed – it’s /index.php/email/emailWebview and it exists under any domain – you can’t simply hard-code the domain of your choice and be on your way.


The problem is the specific type of mkt_tok value that must be in the query string. It’s a weird quirk: until the redoubtable Courtney Grimes discovered it, there was no (public) knowledge that mkt_tok came in different types!


Say your Primary LP domain is and the Domain Alias you’re going for is, your non-profit side.


If you include this link:


<a href="">View as Web Page</a>


Then Marketo will add a mkt_tok to the link automatically (like any tracked link) but it’s the wrong type of mkt_tok. Quoting from a Marketo case:


the mkt_tok value generated for view as webpage tokens is slightly different than the mkt_tok value generated for links in emails and Velocity scripts


More confusing, the generic mkt_tok may appear to work fine when you’re spot-checking emails, but apparently some types of personalization (segments? COs? I don’t know the details) will fail.


So, tempting as it is, “just” hard-pointing to the Domain Alias, though it works perfectly for LPs themselves, won’t work for the VAWP service.


The workaround

Luckily, we have a perfect storm of hackability, if you will.


  •  {{system.viewAsWebPageLink}} always includes the correct mkt_tok, regardless of where it appears in the email (hint, meaning it is pre-mkt_tok-enized even if it's not the direct target of an <a href>)
  • {{system.viewAsWebPageLink}} doesn’t include any characters that would need to be URL-encoded if they appeared in the hash part of a URL.


As a result, we can append the {{system.viewAsWebPageLink}} to the URL of a dedicated redirector page  (a Marketo LP).


The redirector page is visited via the desired Domain Alias. Then it immediately redirects to the VAWP service under that same hostname.


Since that was probably opaque, I mean a URL like this:


Gets redirected to one like this:


OK, OK. The Primary LP domain still appears somewhere in the first URL. So in technical terms, yes, it’s still there. But the hostname shown when you hover over the link is the Domain Alias.


The redirector page HTML

The entirety of the redirector page (Guided LP template) is:

<!DOCTYPE html>
<meta charset="utf-8">
<title>Loading Web View...</title>
if( !redirectTarget ) return;

var systemVAWP = document.createElement("a"),
customVAWP = document.createElement("a");

systemVAWP.href = redirectTarget;
customVAWP.href = document.location.href;

customVAWP.pathname = systemVAWP.pathname; =;
customVAWP.hash = "";

document.location = customVAWP;



Publish an LP based on that template (here brandView.html), then link to that LP under the preferred Domain Alias, using the no-track class:

<a class="mktNoTrack" href="{{system.viewAsWebPageLink}}">View as Web Page</a>



Completely unnecessary enhancement

You can animate the document title while redirecting, even though it should take a fraction of a second:



Here’s the JS for that:

// Optional, just animating the ellipsis
var numDots = 0,
maxDots = 10,
dotDelay = 250;

function appendDot(){
if ( numDots++ < maxDots ){
document.title += ".";
setTimeout(appendDot, dotDelay);
} else {
console.log("Please-wait dots exceeded, possible timeout of next page?";



This is merely a frill and doesn’t change the actual logic.


Choose your Primary LP domain wisely

This situation should be a reminder: choose a Primary LP domain that’s okay for everyone in your instance to see – even though we can minimize its exposure, there are still places where it’s visible, even if just for a half-second, or via view-source:, or until someone remembers to write some Velocity to switch it out.


If you’re running a “concierge” instance serving multiple sub-clients, you don’t want to dedicate the Primary domain to any one client. And if you want to obscure your own agency’s name, you should register a generic domain name along the lines of


If you’re running multiple brands then the Primary can be the umbrella company, or if that’s not supposed to be shown, a generic domain like the above.

This article outlines the setup options and the expected behavior for Partitions. Please reference the official product docs for step by step instructions on how to set partitions and workspaces up.


Partition Assignment Options


  1. Partition Settings in Admin Interface:
    • Applies to:

      All NEWLY created records EXCEPT those created via CRM sync or SOAP API.

    • Where to access setting:

      Navigate to “Admin” > “Workspaces and Partitions” > you can update the settings via either the “Workspace” or the “Partitions” tab. “Workspace” tab allows you to pick primary partition and this is where you initially assign a partition to a newly created workspace.

    • How it works:

      Create all potential partitions within the “Partitions” tab. In the “Workspace” Tab, create a workspace. Enter the workspace name and description. The rest of the fields (below) define partition assignment rules.

      • All Person Partitions: clicking this allows this specific workspace to access ALL partitions that exist within your instance (even if you have new partitions coming in in the future). A good use case for this is to reference all partitions in the “Operational” workspace.
      • Person Partition Checkbox (Required): this consists of all available partitions. The partitions that you select will be accessible via the selected workspace. Note that if only one partition is selected it automatically becomes the “primary partition”. You must select at least one partition per workspace.
      • Primary Partition (Required): This specifies which primary partition a record will be associated with if the person is introduced to Marketo via an asset within this workspace. (e.g. if a record is created via form fill out, and the form lives within workspace A, the contact will be created under the partition set as “PRIMARY” for “workspace A” unless there is a smart campaign rule specifying otherwise).Note: In the workspace tab, if a workspace has access to multiple partitions, the default partition is denoted with an asterisk (*). If the workspace only has access to one partition you don’t see the asterisk.
    • How are duplicates handled?

      Consider this scenario: Record X exists in Partition A only. If you attempt to add the same email address to Partition B, the following behavior should be expected (by the source of lead creation):

      • Creation via Manual Input: A new record Y with a duplicate email will be created in Partition B. Record Y will be available for reference in all workspaces that have access to Partition B.
      • Creation via List Import: The list import will NOT create a new record with a duplicate email in Partition B UNLESS you create custom duplication rules with Marketo support.
        • Creation via Form Fill Out: If Record X fills out a form that exists within Partition B, a new Record Y with a duplicate email will NOT be created in Partition B UNLESS you create custom duplication rules for form fill outs with Marketo Support. Any smart campaign associated to the form will not run in Partition B (because the record lives in Partition A). The form fill activity will be logged for the record in Partition A.
      • Creation via REST API: If the partition name is defined within the REST API request, a new record with the same email will be created within Partition B.
      • Notes:
        • All of these scenarios do not account for any smart campaigns updating partitions (only referring to default settings) and all of the scenarios mentioned rely on the creation activity occurring in a workspace with Partition B set at Primary Partition.
        • Custom duplication rules can be requested for each source of lead creation (e.g. I can set up a rule that uses email address AND primary partition as the duplication rule for list imports only. This will allow me to create a new record Y with duplicate email in Partition B via list upload even though Record X already exists within Partition A.
          I recommend that you thoroughly vet out and understand the use case, functionality, and activity tracking for various scenarios prior to requesting a custom duplicate rule (esp. for Form Fill Outs). Remember, since this creates a NEW record with a duplicate email address, all activity associated with this record will be specific to only activity within the workspaces where the partition is made available. In the instance where both records are accessible in one workspace (e.g. operational), you have to understand and consider who the activity (emails, web, etc.) in the shared workspace is attributed.                     
  2. Assignment Rules in Admin Interface:
    • Applies to:

      ALL NEWLY created contacts or leads via CRM sync OR SOAP API.

    • Where to access setting:

      Navigate to “Admin” -> “Workspaces and Partitions” -> “Person Partitions” Tab -> Click “Assignment Rules”

    • How it works:

      The default value will be the initial partition the contact is assigned to if being synced from the CRM.

      You have the ability to add basic logic (example below) - if the add choice functionality is not sufficient for your business use case (e.g. you wish to use a combination of filters to assign to each partition) you can assign a partition via a smart campaign.

  3. Smart Campaign:
    • Applies to:

      Newly created OR existing records. (e.g. If the partition assignment logic for the CRM sync is too complex and requires a combination of filters, use this feature to update the partition from the default partition).

      Note: Smart Campaigns partition rules have the ability to overwrite rules assigned via partition settings in the admin tab. Ensure that the smart campaign logic is thoroughly tested and is required for your use case prior to setup.

    • Where to access setting:

      Set up a campaign within Operational workspace (has access to all partitions). Create a smart campaign within Marketing Activities > Within the flow, select the “Change Person Partition” flow step.

    • How it works:

      The conditional logic can be applied within the smart list of the smart campaign (you will need multiple campaigns) OR you can create bucketed smart list assets that can be referenced within the "add choice" flow step of a single smart campaign. The campaign can be triggered or set as a batch based on your business use case.


Still have questions? Email your questions (for quicker response) or respond in the thread below (for community assistance).

You might not have noticed that {{lead.token}} values are HTML-encoded in Landing Pages. (Upfront note: this is A Good Thing, since it’s the secure-by-default approach.)


So if you have a Text Area field (same goes for String):



And you just drop that into an LP:



You’ll see the exact textual representation in the approved page — that is, in this case, you will not have a clickable link:



Of course, this is because the underlying HTML source is a “dead” text representation of the value, not a “live” <a>:



There isn’t anything wrong with this default behavior and it’s far less dangerous than rendering HTML by default.


But if you need to “resuscitate” such tokens — only if they’re from a trusted source — first make sure they’re placed inside easily identifiable HTML containers. In this case I’m taking a page from the Marketo Email Editor and setting the data-allowhtml attribute on a surrounding <span>:



Then add the related JS to the <head> of the LP:


var arrayify =[].slice);
htmlable.innerHTML = htmlable.textContent;


And now it’s a true-and-living <a>!



You might want to hide (display: none) the elements until they’re resuscitated but I leave that to you.


Only Built for Trusted Linx

Be careful with this workaround, though: only use it if you’re 100% sure the data and the output context can be trusted. If the value was populated via form fillout and never sanitized, it is definitely not trusted! While if it was synced over via a managed integration, it might be trusted.

Using the Lead ID from Velocity is trickier than you'd expect ($lead.Id is not set automatically, even though there's a {{lead.Id}} token outside VTL).


And there are other interesting fields that are handy to have at send time a couple of which aren't accessible any other way, giving Velocity a leg up.


Here's a quick snippet that creates a $marketoSend object with properties for Campaign ID, Campaign Run ID, and more:


#set( $marketoSend = {} )
#set( $delim = ":|-" )
#set( $values = $mktmail.xMarketoIdHdr.split($delim) )
#set( $numParts = $values.size() )
## Campaign Run ID not relevant in Preview, Test Variant not always present, etc.
#set( $keyTemplatesBySize = {
   13 : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::CampaignRunId:LeadId--TestVariantId",
   12 : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::CampaignRunId:LeadId-",
   11 : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::CampaignRunId:LeadId",
  "*" : "MunchkinId-+MunchkinId-+MunchkinId::CampaignId:StepId::AssetId::LeadId"
} )
#set( $marketoIdKeys = $display.alt($keyTemplatesBySize[$numParts],$keyTemplatesBySize["*"]) )
#set( $keys = $marketoIdKeys.split($delim) )
## loop interesting keys
#foreach( $key in $keys )
#if( !$key.isEmpty() )
#if( $key.startsWith("+") )
#set( $finalKey = $key.substring(1) )
#set( $marketoSend[$finalKey] = $marketoSend[$finalKey] + "-" + $values[$foreach.index] )
#set( $finalKey = $key )
#set( $marketoSend[$finalKey] = $values[$foreach.index] )
## append Program ID
#set( $marketoSend.ProgramId = $program.values().iterator().next() )
Munchkin ID is ${marketoSend.MunchkinId}
Program ID is ${marketoSend.ProgramId}
Campaign ID is ${marketoSend.CampaignId}
Campaign Run ID is ${marketoSend.CampaignRunId}
Step ID is ${marketoSend.StepId}
Asset ID is ${marketoSend.AssetId}
Lead ID is ${marketoSend.LeadId}
Test Variant ID is ${marketoSend.TestVariantId}

You can then add the variables to a URL (also built in Velocity) like so:


<a href="${marketoSend.CampaignId}&cr=${marketoSend.CampaignRunId}">Click me</a>




Make sense? I'm sure it does.


But you might wonder why you can't include the <style>, or a remote <link> for that matter[1] directly in the mktoText area, like so:




Test and you'll see why Text {{my.tokens}} are necessary.



Another take

A twist on the same approach gives something superior, if a little strange(r).


Set up just 2 tokens, one for opening <style> and one for the closing </style>, at the top level of Marketing Activities. Then you can reuse those tokens everywhere and write CSS directly in the mktoText.









[1] You can include a <link> tag in the {{my.token}}, of course. I'm not saying you can't use external stylesheets, just that they need to be passed in via tokens.

Spinning this off the earlier Part I (on number types) because it's an equally important topic.

Not necessarily a rivetingly interesting topic, but since some of you are trying to move into more technical roles, these are good things to know to geek up your conversation. Knowing these things before I became a full-time developer, having learned them on the SQL database side, made coding more comfortable for me.

So: very bad things happen when number field types are used to store values that aren't truly numbers, but actually numeric strings.

What's a numeric string?

Numeric strings are sets of digit characters (0-9) that do not actually represent a single number, or perhaps we could say are not actually equal to their apparent number value.

Those seemed vague, I know. Examples are better teachers…

Credit card numbers are numeric strings

Credit card “numbers” (formally, payment card numbers) are numeric strings. They are a set of independent codes mashed together (MII digit + network number + your number + checksum digit), like:


But they do not represent a 16-digit integer: this is not the value 4,110,144,110,144,115 (4 quintillion something) and should never be thought of that way.

Yet some people still ***** up and store CCs as numbers, thinking it'll be more space efficient. It certainly would be — if it worked — since a char(16) takes 16 bytes while a 64-bit int takes only 8 bytes. The ability to reduce your storage requirements by 50% is really tempting. But it's wrong in more ways than one.

The most basic way it's wrong is if you don't actually have a big enough datatype everywhere this value will be used. In the previous post on number types, you saw that the maximum safe value of an SFDC Double is 9,007,199,254,740,991. OK, so 9 quintillion. The above credit card (mis)represented 4 quintillion will fit into that. But what about this card number:


Nope! That's outside the Double range. And that's not surprising, as card numbers are not in any way calibrated to fit in a certain data type.

Note I chose a 16-digit credit card number above. In fact, a Visa number, among others, can be 19 digits. So that blows a Double out of the water completely and might make you contemplate a 64-bit Integer (as long as you have end-to-end support for that type). Sorry, won't work either:


That value, if you attempt to store it as a gigantic integer, won't even fit in a 64-bit int, which has a max value of 9,223,372,036,854,775,807.

James Bond's agent number is an alphanumeric string

While daydreaming about, er, “real-world” examples, 007 came to mind.


Read the full post on

I have a need to add disclosures to my forms to be compliant with GDPR in providing information on the use of the data being collected. And going through a developer isn't always ideal as Marketing team and legal may need to update more than once. Also, would prefer to insert on one form used in multiple pages instead of each page. The way I solved it was using a combination of display, :nth-of-type, and order, all CSS that can be done in Marketo.


Edit Form > Form Fields > Click Next > Form Themes > Edit Custom CSS

Screen Shot 2018-05-28 at 12.04.28 AM.png


1) First, I made all my div rows, columns, and wraps as display: flex so I can use the order attribute later below.




.mktoFieldWrap {

display: -webkit-flex;

display: -ms-flexbox;

display: flex;



I need to locate the correct row(that holds the disclosures text) that I'll be using. I placed it above the the button originally but when using progressive profiling it doesn't allow rich text above the button and trying to select the correct row isn't great when a number of fields can show up in progressive profiling depending on the individual, form fields, and setup.


Edit Form > Form Fields


1.5) So to fix this setback, I just kept the disclosure rich text as the first row on the form. This allows me to know exactly where it is so I can select it.


Screen Shot 2018-05-27 at 11.24.31 PM.png


2) Second, I give the button row an order.


.mktoButtonRow {

  order: 10;

  -webkit-order: 10;



3) Lastly, I search for my disclosures row with nth-of-type, I know it'll always be the first row(based on step 1.5) and can make the order 11. This will place it below the button(which is 10 from step 2). This should work if you are using progressive profiling or not.


form .mktoFormRow:nth-of-type(1) {

order: 11 !important;

-webkit-order: 11 !important;



Hope the above made sense. You can also use tokens and add them to your program or folder if you'd like to easily make edits through there instead of always needing to go inside the form. The tokens will only work on Marketo landing pages though. You'll have to edit the rich text manually on any form hosted outside of Marketo.


Final Result Preview


Screen Shot 2018-05-27 at 11.35.16 PM.png

Abacus is a NYC Fintech Startup and we're looking to hire our first Demand Generation Marketing Manager to build out our strategy! If you're interested or know anyone who might be, please apply here: Abacus - Demand Generation Manager


About the Role

The Demand Generation Manager is responsible for Abacus’ customer acquisition strategy. You will develop a strategy that focuses on crafting and executing campaigns with the objective of generating high quality sales pipeline that will ultimately contribute to revenue growth for Abacus.


The ideal candidate will be a blend of creative and analytical, working alongside our content and business development teams to create attention-grabbing ads, emails and landing pages. Working with marketing and sales operations to track and measure success and ROI. This is a great role for someone looking to hone their strategic skills by developing a fresh demand generation strategy.



Who is Abacus?

Abacus is reimagining the way businesses move money, starting with the first real time employee expense system. We’re excited about building a solution that is not only effective but enjoyable for everyone to use. Creating the best customer experience drives us - we love hearing everyday how what we're working on is saving our customers time and headaches. Making expense reports obsolete is only the beginning - join a team that is leading the charge in how businesses manage their cash flow. Check us out at

After keeping this code close to the vest since 2015 (!), it's time to share the knowledge.

On the Community, I periodically refer to “my method” and to how other all other approaches are horribly hacker-friendly (true!). But I realize it's been a ludicrously slow reveal.

I'm still keeping my advanced method (for those who can't meet the below prerequisites) closed-source, but the simple method here will work for the vast majority of users.

The prerequisites

Here are the prereqs to use the open-source code:

❶ Your external site must share a private parent domain with one of your Marketo instance's LP domains or domain aliases. That is, if your external site is then you'll need to have (or newly create) a Marketo domain like Those share the private parent

Such a domain is usually already available. A domain like doesn't share a common parent with so you can't use that. You can either use an existing domain or create an alias for this specific task, so is fine to use instead of your primary

If and only if your external site runs over SSL (https://) then the Marketo LP domain must also run over SSL. But there's no requirement in reverse: it's fine if your LP domain runs over SSL but the external site doesn't. And it's also fine if both of them are still plain http://.

❸ Almost not worth mentioning: you need to have a server that can host a single JS file. You can't upload JS to your Marketo file library, because Marketo still (!!!) doesn't serve the .js extension correctly for all browsers. But, pretty much by definition of needing external site pre-fill, you must have another server, right?


Read the full post on

When choosing a Thank You/Follow Up URL (wish there were only one term in use!) 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.

You should already know that variables don't work in the Text version of Marketo emails. And you've likely dismissed this as a head-shakingly short-sighted mostly harmless detail.

But it has one wider consequence, as user JK noticed in this Community thread, that's firmly a bug — and one which impacts the email as a whole.

The documented (if suboptimal) behavior

Variables on the Text side are typically replaced with blank output. This can either:

(a) mangle the Text version, if variables happen to contain critical content, or
(b) be fine and dandy, if variables hold colors or other styles that pertain only to the HTML side anyway

Outcome (a) is not great, but at least you know to plan for it.

The undocumented (and bad) behavior

But when a ${variable} is used as a link, like…

<a href="${link}">Click for fun in the sun</a> 

and uses one of the 20 or so Velocity reserved words, ya got problems. Because of the additional layer of processing for links (to be rewritten to the branding/tracking domain) you get everybody's favorite WTF:


And this happens regardless of whether you used any Velocity tokens!

(Of course I mean any userland {{my.tokens}} because Velocity is always used to assemble Marketo emails, even if you don't write any code yourself. Didja know that? Or maybe suspect it?)

Anyway, the solution is to not use any of these words as${variable} names (and tell your template designer, too):


And that's about all I have to say about that.

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 help you learn best practices, because when you drag a field from the field tree to the editor canvas, it's automatically  wrapped in ${}:



You should 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 is presented as one of two Strings:

  • "1" for Boolean true
  • "" (empty string) for Boolean false

That conversion is actually really confusing.

See, Velocity inherits a lot of things (variable handling things) from Java.

In Java, a non-null String object, on its own, is always evaluated as Boolean true. Regardless of whether it has any characters in it (that is, regardless of whether it's empty).

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 →