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

Products

383 posts

Hello Marketing Nation,

 

Thanks for tuning into the second edition of our limited Sneak Preview series, where we preview all the exciting changes coming to Community in mid-December. Today, we’ll be discussing all search related improvements - please note that these designs are not final and may change prior to release. Marketing Nation Community’s new search capabilities will include:

 

Universal Search: Suppose you were on Community browsing for best practices on Web Personalization. You’re able to find some content, but it’s not the exact information you’re looking for, so then you visit Marketo Product Docs, Knowledge Base, Help Center…

 

Wouldn’t it be great if there was a combined search for all things Marketo? Well, now there is! With a newly integrated universal search engine, you’ll be able to perform keyword searches across all Marketo properties, including: Marketing Nation Community, Marketo Product Docs, and the Marketo Help Center.

 

New Search Menu: Of course, not everyone is interested in searching across EVERY Marketo property, all the time. We’ve built a robust search menu so you can easily toggle which platforms and asset types you’d like to display so that searching is always a breeze.

 

searchfacets

 

Improved Recommended Content: In the months following our transition, our new search engine will start anonymously analyzing user page view history. This data does not use any personal information and will only be used to generate models that return recommendations by comparing a user’s current navigation pattern with other user patterns in the past.

 

Recommendations are made based on the number of user visits – so the more content that is clicked on, the better the recommendations become. Our goal is to better recommend community content to you in a confidential manner, which will hopefully provide you a more holistic view on the topics you’re most interested in!

 

Better Search Suggestions: Search suggestions, when done correctly, vastly improve search user experience by reducing the amount of time spent scrolling through search results. With our new search engine, query suggestions will utilize relevancy scores, query frequency, performance, and the degree of character matching to provide accurate suggestions for your questions.

 

querysuggestions

 

Grouped Search Results: In our quest to provide a better search experience, we wanted to ensure that search results were presented to you in an organized manner. We’ve implemented result folding, which will display logical parent-child relationships for search results. This way, when your search query returns multiple results from the same conversation thread, all related child results will be grouped in a way that makes it easy to distinguish the parent post from its subsequent replies.

  

foldedsearchresults

 

That wraps up our second Sneak Preview! I hope you are as excited as I am about these search improvements – coming soon to the Community you know and love! Stay tuned for our next week's Sneak Preview, where we'll cover all things Support. If you have any questions or concerns, please comment in the section below or reach out to me at jonchen@adobe.com.

 

If you haven't checked out our first Sneak Preview on platform and UI improvements, you can read it here!

Some small signals from end users stressing some can be disproportionately meaningful.[1] One such signal: that they started to fill out a form, so you can compare/intersect with people who eventually submitted the form.

 

The delta between the 2 is, of course, people who were distracted/discouraged/disengaged (or, in the interest of completeness, people who couldn’t submit the form due to forces outside their control).

 

Of course, we need to define our terms.

 

Does “started” mean “focused – either by clicking, tapping, or tabbing – a visible form field”?

 

Or does “started” mean “changed a value” in a visible form field? This definition might seem more exact at first, but the problems are (a) there’s no need for someone to change a value in order to submit a form – remember Pre-Fill – and (b) there’s no single DOM event that can account for all types of net changes, making the accompanying JavaScript very complex.

 

There are other directions you can take with “started,” and all manner of JS hooks you can build (veering quickly into way-too-much-to-be-useful). In a later post, I may feature some of those takes. But today I’m going to stick with the simple definition: Did someone move focus to the form?

 

Here’s how that’s done:

MktoForms2.whenReady(function(form){
var formEl = form.getFormElem()[0],
formId = form.getId();

formEl.addEventListener("focus", logFormStart, true);

function logFormStart(e){
formEl.removeEventListener("focus", logFormStart, true);
Munchkin.munchkinFunction("visitWebPage",{
"url" : document.location.pathname + "#!/inPage/startedForm/" + formId
});
}
});

 

Then you’ll see a hit one supplementary hit per pageview, naturally in the Activity Log:

 

 

/startedForm also logs if they submit the form without touching any fields, because clicking Submit also fires a focus event. (This is A Good Thing.)

 

Note the hit will stay in the Anonymous Activity Log if the person never converts, but moves to the standard (Known Lead) Log after their session is associated. As with all Visit Web Page activities, there’s ongoing backfill.

 

I’ll leave it to you to come up with meaningful Smart Lists using this info.

 

Why log Visit Web Page instead of Click Link?

Because Munchkin clickLink blocks the browser UI thread. And if you don’t know what that is: trust me, it’s A Bad Thing.

 

I’d go far as to say all custom Munchkin code that uses clickLink for in-page events (non-unloading events) instead of visitWebPage is lowkey broken – admittedly including some of my own older code. Someday I’ll get to a blog post specifically about this, but for now: trust me.

 

 


 

 

NOTES

[1] I feel strongly that logging every minor-to-meaningless page interaction is wrong. That not only creates chaff on the server side, it’s also disrespectful to users with low-quality connections. I’m in a big city, but still stuck on slow DSL, people!

 

In addition, Munchkin’s append-only logic is particularly ill-suited for storing a continuous spray of superseding events. The proper back end for that is one that can consolidate superseding events into one. Example: video tracking. If someone watched 60s of a video, they also watched 30s, so only the superseding value should be stored. Of course, I still use Munchkin to log YouTube plays... but I know it’s wrong in the grand scheme.

Hello Marketing Nation,

 

With 2020 just around the corner, we wanted to give you a peek at some changes that are coming to the Marketing Nation Community. This post is the first of a series of posts that will detail all improvements we have planned, including significant changes to the design, search capabilities, navigation structure, gamification, and more!

 

As usual, these changes were reviewed in close collaboration with several of you - thanks to everyone who participated. We hope that these improvements make the Community easier to navigate, more organized, and most importantly, more fun!

 

Without further ado, here’s your first sneak preview of the upcoming Community changes. Please note that these designs are not final and may change prior to release. All improvements will be rolled out mid-December 2019:

 

Future Marketing Nation Community Home Page

 

Future Marketing Nation Community Feed

 

Here's some additional context for the design changes above:

 

A Completely Redesigned Community Interface

 

  • Quick access to search bar: Search is important – we know that it’s the first thing most of you look for when visiting the Community. In our new Community, the search bar has been magnified to make it even easier to access.
  • Lighter dropdown navigation structure: Keeping it simple! We’ve changed the navigation bar to drop down automatically and hyperlinked key areas in our Community so that you can visit more places with less clicking.
  • Cleaner forums interface: Notice anything different about the main page? We have significantly changed our Community interface for a better browsing experience. All of our pages have been re-designed with an improved forum layout, more visual iconography, and improved font.

 

Upcoming Adjustments to Community Structure

 

  • Subscriptions: Though the move to the new Khoros platform comes with a collection of very exciting improvements, unfortunately user subscriptions will not be carried over. This means that bookmarks, subscriptions to threads and places, inbox messages, and friend connections will be erased during the migration.

    If you have any content in your inbox and/or connections that are crucial to you, please take precautionary steps to save your subscriptions and who you’re following to ensure that they are not lost in the migration. Note that only the content in your INBOX will be erased – the content you previously followed and/or posted will still remain and be searchable on Community.
  • Ideas: As many of you are aware, there are various development stages associated with an idea in the Ideas Space of Community (13 to be exact!). Though we intended for these labels to add transparency to the progress of an idea, having too many stages makes submitting an idea more confusing than it needs to be.
    Moving forward, all ideas stages will be consolidated in the following buckets: 
    • Stage 1: Under Review
      • Stage 1a: No Plans to Build
      • Stage 1b: Planned for Build
      • Stage 1c: Already Have It
    • Stage 2: In Process
    • Stage 3: In Production
  • Labels: What were previously known as Category Folders (Marketo-defined tags) will be known as “Labels” moving forward.
  • Events and Polls: Khoros does not support certain Jive interaction styles. As a result, polls will not be migrated over and events will be migrated over as regular discussions/questions.
  • Restructuring Our Community: We took this opportunity to reorganize select areas in our community where we felt were redundant or misplaced. The following areas have been reorganized:
    • The Japan Products Space will be consolidated into the Japan MUG.
    • The Japan Partners subspace will move to the Partners category.
    • All Champion-related content will be located in the Champion Program category (blogs, discussions, and documents).
    • All blogs by Marketo Employees will be placed in “Employee Blogs” moving forward.
    • All “discussion” posts will be consolidated as questions.

 

Please keep in mind that consolidating or moving spaces or content will only affect the location of content. It will NOT result in deletion of posts or reset group permissions.

 

That’s it for this week’s Community Sneak Preview! I hope you're as excited as I am for the upcoming changes. As always, please reach out to me at jonchen@adobe.com if you have any questions, or just comment in the section below. You can also check out our next Sneak Preview here, where we’ve covered all improvements coming to Search.

While you should be loading embedded forms from your Marketo LP domain, it turns out just changing the URL is not enough.

 

Using a non-Marketo-owned domain to load forms2.min.js and call loadForm() seems to avoid collateral damage from Tracking Protection (Firefox’s built-in feature or the equivalent plugin for another browser). The form will at least show up on the page if you do that.

 

But – I’m sure you will be dismayed to learn – the form still won’t post under TP! You also need to upload a file to Design Studio and add a tiny bit of JavaScript. Then and only then are you good to go.

 

First, download this file:

 

marketo-xdframe-relative.html

 

(Note: the Marketo Nation site gates files through an “Offsite Link” popup. So click above, then right-click the link + Save Link As. Make sure you get the .html file itself, and don’t end up with a snapshot of this blog post!)

 

Next, upload the file to your Design Studio. To be clear, you’re not creating an LP, you’re uploading the file as static HTML. This may not be something you’ve done before (you probably upload images, CSS, and PDFs) but Marketo actually supports any kind of file.

 

Screenshot after it’s been uploaded to Design Studio:

 

 

Then replace your embed code with this ever-so-slightly different version:

 

<script src="//pages.example.com/js/forms2/js/forms2.min.js"></script>
<form id="mktoForm_9999"></form>
<script>
MktoForms2.setOptions({
formXDPath : "/rs/123-ABC-456/images/marketo-xdframe-relative.html"
});
MktoForms2.loadForm("//pages.example.com", "123-ABC-456", 9999);
</script>

 

Where:

 

  • 123-ABC-456 represents your Munchkin ID
  • pages.example.com represents your Marketo LP domain
  • 9999 is o’course your form ID

 

A live demo (though all the code is above):

 

MktoForms2 :: XDFrame relative path for Tracking Protection

 

The why

Using your LP domain enables forms to show up. But there’s a special asset, only used upon submission, that will still be blocked by default. Naturally, this only reveals itself when you run end-to-end tests.

 

See, embedded forms use an IFRAME message relay (XDFrame) for cross-domain posts (there are other ways to do it, but they’re not as backward-compatible and the IFRAME method works fine + fast in all browsers, even back to IE8).

 

But the IFRAMEd document loads forms2.min.js from an absolute URL with your instance hostname, i.e. app-xxnn.marketo.com:

 

Oops! Tracking Protection ain’t gonna let that load, for the same reason it doesn’t like a Marketo domain in the main document. (You can’t avoid TP by using an IFRAME, that would be silly.) As you can see, the replacement XDFrame file uses a relative URL (/js/forms2/js/forms2.min.js), so it loads from your Marketo LP domain like the rest of the assets. Presto! No marketo.com for TP to get all paranoid about.

 



NOTES

forms2.min.js inside the IFRAME is only necessary because of its bundled  jQuery library, specifically the MktoForms2.$.ajax wrapper method. While not technically necessary, using jQuery keeps parity with the Forms 2.0 API.

***Posted on behalf of Kimberly Galitz, Marketing Automation and Attribution Specialist at Bandwidth.***

 

Getting Started with Bizible Reports 

When I first started using Bizible, the reports that I used most frequently were 'Bizible Persons with Bizible Touchpoints', 'Bizible Attribution Touchpoints with Opportunities', and 'Leads with Bizible Touchpoints'. The 'Bizible 101' reports were a great place to start. 

 

Pro Tip: I always advise people to focus on building out their top 3 basic reports. Having those basic reports ready to go is critical, it allows you to answer questions quickly to stakeholders. It is very common for people to come up to your desk and ask a very specific question such as “how’d this one campaign do?” It is extremely valuable to be able to quickly pull up those reports, instead of having to go back and rebuild them. 

 

Interpret the Data

When it comes to interpreting the data, take the question being asked and then use the data to answer the question. We use different data sets to answer different questions. We do not use different data sets to make us look the best, but to answer the question in the best way possible. For example, if you’re focused on what drives opportunity creation, we would use the W-Shaped model. If you’re looking at what drives closed/won deals or revenue, look at a Full Path or Custom Model. It is critical to know when to use different models and for what questions. 

 

When my team first started using Bizible, we first looked at all the opportunity reports. Initially, it was hard to interpret the data because it is difficult to analyze data in an attribution model if you have not done so before. 

 

Pro Tip:  If you are not familiar with looking at a weighted attribution model, look at the data that you are used to seeing next to the Bizible model you are using. 

 

For example, we always put the opportunity amount into a column, and then the W shape and full path model data next to it. This enabled us to see what the opportunity percentage looked like with our previous method and the Bizible method. Once you get used to viewing it the Bizible way, it all starts to make sense. 

 

What To Do Next

Use the data to optimize your marketing efforts and make informed decisions.  

 

Example 1: Using the data to optimize email strategy.

Certain channels are meant to be middle touch. If you are just measuring first touch or last touch, you are not going to see those middle touches receive credit. For us, email is one of those middle touch channels. Since the purpose of email is typically to nurture along the journey, not to create leads, it is very important to understand how it is performing further down the funnel. Before Bizible, we did not see how email was influencing revenue. Adding Bizible data into our mix allowed us to understand how certain email tracks were performing and their importance at certain points in the journey. 

 

Example 2: Using the data for events. 

Before implementing Bizible, we were making decisions for events based on how we felt about the event. For example, if reps didn’t think they had meaningful conversations at an event then we would not attend that event the following year. Once we started using Bizible to report on events, we started seeing that a lot of those events were actually very meaningful. Events have a very long tail. For example, there could be a touchpoint for a deal that would close 2 years later. 

It also helps us make additional strategic decisions for events. For example, if most of our touchpoints were from a meeting room instead of a badge scan, that helps inform us that maybe we don’t need a booth next year, just a meeting room. With Bizible data, we are able to make more informed decisions about events.

 

Scaling Your Attribution Initiatives

The number one piece of advice I can give is to start small. For example, I started by introducing Bizible reporting to the events team. I built reports for them and then set up a few meetings to talk to them about the data so they could get comfortable with it.  Now they are running with the Bizible reports on their own. They know how to interpret the data and speak to it, which is the key to being able to scale your attribution initiatives. Once you have that first team up and running with Bizible, you can move onto the next team. Giving everyone the power to run with the reporting and analysis themselves is super helpful. If the team adopts Bizible as a team instead of just one person running it, it will really help with your ability to scale your attribution initiatives.

The Launch Point portal has gone through a makeover recently! Apart from the new look, the following features were also rolled out:

      
  • Improved search navigation
  •   
  • Integration with G2 Crowd, a peer-to-peer review aggregation service
  •   
  • Categories, a new way to search the LaunchPoint partner applications, with a 2x2 grid to highlight popular applications
  •   
  • Stack Builder - an interactive tool to model and visualize MarTech stacks

 

The Stack Builder allows you to explore sample stacks curated by us, visualize your current MarTech stack or conceptualize a brand-new stack from scratch.

Here is a brief walk through the salient features of the Stack Builder. Please check it out and let us know what you think.

 

Using the Stack Builder

Launch the Stack Builder by visiting this page here. The page opens up with the Featured Apps engagement stack. This stack is a collection of featured applications from the LaunchPoint ecosystem.


The stack is partitioned into six buckets or phases – Plan, Engage, Measure, Content, Data, and Platform. A tile under each bucket represents an application from the LaunchPoint ecosystem. Mousing over each application tile brings up a small popover which shows the prominent highlights about the application. Clicking on the application opens up the application listing page, where further detailed information is available including a form to get in touch with the partner directly.


Plan, Engage, Measure, …:

The Stack Builder is partitioned in to 6 buckets: Plan, Engage, Measure, Content, Data & Platform. The Launch Point ecosystem uses categories as a way to group applications. The Stack Builder builds on top of that, by further grouping application categories into partitions. This helps to visualize the stack in the order of data/process flow during the various phases of marketing.

 

For example, the following application categories are grouped together under the “Plan” bucket: Budgeting & Finance, Conversion Optimization, Customer Intelligence & Data Science, Optimization & Testing. The application filter tool provides an easy one-click button to limit all the application categories and the applications that belong to those categories, which can be used to narrow down the choice of applications to consider for the stack.

To learn more about the Plan, Engage, Measure framework, check out this webinar.

 

Creating stacks with the Stack Builder requires an account on LaunchPoint. If you don’t have an account already, please create one by clicking on the Sign-Up button.

Login:

Once logged in to the LaunchPoint site, additional navigational items show up on the side bar.

My Stacks is where you will find all the stacks you created using the Stack Builder.

 

My Inquiries is a handy tool to keep track of messages sent to partners using the “Get Started” form available on each of the partner listing page.

 

Create a new stack from template:

The quickest way to create a new stack, is to use one of the pre-made templates as a starting point.

      
  • Select one of the existing Engagement Stacks or from one of your own from My Stacks and open it.
  •   
  • Click Save As

  •   
  • Fill out the details of the new stack (name and description)

    The new stack will now show up under “My Stacks”.

Edit Stack:

Once created, a stack can be edited to modify the applications in it. The edit mode uses the application filter as a quick way to search, select or remove applications from the stack.

The application filter has partition filter buttons on the right-hand side corner. Selecting one of the buckets, limits the listings in the filter as well as the buckets shown below.

 

This is very useful to focus on one partition at a time, to search and select applications. 

Click on the checkbox to add/remove an application to your stack. 

 

The application partition buckets are listed below the filter in order (Plan, Engage, Measure, Data, Content & Platform).

 

Unlike the view mode, in the edit mode the tiles are rendered as larger tiles, with details about the application in the tile. Mousing over the tile, brings up the popover with more details about the application (just like the view mode).

 

In addition, while browsing through the applications in the partition, it is easy to remove an application, by clicking on the cancel button on the popover.

 

With the improved search and grouped categories added in the latest Stack Builder update, companies will be able to build their marketing stack in a more efficient and organized manner. If you’re interested in checking out these new changes, you can visit the LaunchPoint Stack Builder here.

 

Have any questions or comments? Let us know your thoughts in the comment section below.

 

This article was written in collaboration with the Marketo Web Development team

In this edition of the How 2B Fearless series, we sat down with Emily Poulton, who is a Marketo Champion Alumni, London MUG Leader, and Fearless 50 2018 member. Emily shares what being a fearless marketer means for her and what it has meant for her career.

 

What does fearless marketing mean to you?

Fearless marketing means always doing what is right for the customer and for your team, however hard the battles may be. It is all about resilience and doing the right thing. When you know that a certain direction will not succeed in the long run due to an oversight in planning, catch it there and then find solutions I had a recent experience where a campaign was not planned for measuring success in Marketo. During the campaign building, I had to explain why this would not work to record campaign attribution. For me, it was tough to speak up at that point in time as everyone was excited to launch the campaign. It may seem hard to speak up in the moment, but in the long run it’s the right thing to do for the business and the customer.

 

Who is a fearless marketer you look up to and why?

Jamie Kirmess, Principal at LeadMD, is my fearless marketer. She was my first boss in the world of Marketo and her career has rocketed since we last worked together, so much so that I cannot keep up. She has so many ideas and will take the time and patience to explain the reasons behind a campaign or logic, from senior stakeholders to the people executing. She is smart and savvy and she doesn’t know this, but when I am stuck, I think, WWJD – what would Jamie do? Jamie was never afraid to push back on projects. She always explained her reasoning and offered solutions and alternatives to ensure all needs were met by having one campaign built smarter. This goes back a few years now since we worked together in 2014, but Jamie would always ask why I was building a campaign for someone, and if I couldn’t answer that, the project would not be done. As simple as that!

 

How did your career start out in marketing?

My first marketing job was actually during a placement year at University back in 2007, where I did a year-long internship producing marketing collateral and supporting events. I then had a series of generalist roles working for start-ups where I discovered my passion for automation and inbound marketing, I just didn’t know it at the time. I discovered the world of Marketo in 2014 and loved how it brought together creativity, logic and customer journeys. Marketo has opened many doors for me professionally, and it is one of the reasons I am in my Senior Marketing Operations role today. Marketo touches all parts of the business: sales, marketing, legal, operations, IT, technology and communications; It’s a great way to encourage cross-collaboration in the workplace, and can put you in front of key people within your organization as you start having those strategic conversations.

 

How did you get to the point you are at in your career today?

Back in 2018, I shared one of my biggest fearless moments on stage with the Marketing Nation. This was the moment when I walked into a senior meeting between my CMO, my CIO and Marketo’s CEO, CPO and EMEA VP because I wanted to meet them. That moment made me a Fearless 50 member and gave me the courage to keep striving for more. Since then, continuing my fearless journey, I took the leap of taking a career break to fulfill my personal ambitions: traveling Central and South America.

 

It was scary because I was leaving everything and everyone I knew behind (apart from the boyfriend as I dragged him with me) and I didn’t know if the employment market would change, or if I would find a great job on the other side. I planned the break 1 year in advance and it was very important to me to able to go and experience different cultures and explore the world. I was very honest about my intentions when I accepted the contract role at The Adecco Group, since integrity and trust are very important to me.

In the end, believing in my own abilities (not losing all my knowledge at the top of Machu Picchu) and being honest to employers about the break (I have a gap in my CV as I have just come back from a sabbatical) opened so many more doors than expected. At the time, it was scary, but luckily the world of Marketo and Operations has plenty of work opportunities across London.

 

Leaving full time employment to go explore the world could be seen as a dumb move, but in the long run, it was worth it, both personally and professionally because a great new challenging role found me on the other side.

 

What have you learned from other members of the Fearless 50?

The Fearless 50 members that I have met so far are all successful in their careers, humble and friendly. I learned from them that picking your battles is the key to being fearless. I find that focusing your efforts on the causes you believe in most at work means that you are able to achieve your goals, where you feel it matters most. Oh, and lots of passion.

 

What are three pieces of advice you would give to the next generation of fearless marketers?

1. Be honest and respectful of others around you.

It can be easy to want to hide the truth if it makes you look bad or go against others’ opinions. However, in my experience, admitting you don’t know or owning up to your errors makes you brave, which creates an environment of trust and respect around you.

 

For example, I found out that some of our records were being deleted in Marketo (oh no!) and in the end, it was because I set up a dodgy flow. I owned up to the marketing manager it was affecting and our CMO. Luckily, we ended up restoring the records, but it was worth explaining the reasons and the situation to everyone involved before they heard the news from someone else.

 

2. Trust yourself and explain why you believe a certain route is the right way to go.

When colleagues who shout the loudest are the ones being heard, taking a passive approach in meetings can seem like the easiest option. It can be difficult to be fearless and ensure your voice is heard, even when you truly believe that you have the solution, but people will appreciate you taking the initiative to explain your reasons – especially when you have data and examples. 

For example, I had a situation recently where I had two competitive and passionate colleagues who were trying to convince me that Marketo was broken, when I knew very well that it was the way they had chosen to setup our website reporting tool. It was very hard to go against what two people were saying but I had to be brave and explain the rationale, get some advice from other sources to prove my theory, and find the articles and data to back it up. In the end, my voice was heard and we’re now working on a collaborative solution.

 

3. Always be yourself.

Don’t go against who you are as a person and the values you believe in, it’s not sustainable long term. Your work will always be a reflection of you, so do the best you can, and if you think it will make a bigger impact, do it your way.


I always like to perform a task in my time and my way as I know that I will be able to explain it in future, especially when it comes to reporting and spreadsheets!


About Emily Poulton

 

 

LinkedIn: https://www.linkedin.com/in/emilypoulton/ 

Community Profile: Emily Poulton

In this edition of Marketo Master Class, we're partnering with Marketo Champion Ann Marie Gastineau to demystify best practices around scaling a nurture in Marketo. In this article we'll take a look at the time and place to implement a nurture, best practices around applying a tokenized strategy to nurtures, and more. If you have additional insight for running a best-in-class nurture program, let us know in the comments!

 

1. Why nurture? (How do you determine if a nurture is the right strategy for your business?)

 

Nurture is the right strategy for any lead who isn’t ready to buy from you immediately – which will be most everyone. Certainly, there’s bound to be a few sales-ready leads who fill out your Contact Us (or Get a Quote, or Free Trial, or whatever) form the first time they visit your site, but they’re rare. More likely, your target audience has no idea who you are (depending on how strong your brand is), or what you have to offer. They may not even realize they have a need.

 

 

66% to 90% of a Buyer’s Journey today is self-directed (Forrester) – which means your prospective buyers are out there right now roaming around the internet trying to decide what to buy and who to buy it from.

 

 

The goal of lead generation campaigns is to get as many people in your target audience into your database as cheaply as possible. This means offering something valuable for very little (an email address). Conversely, the goal of lead nurture is to convert them into sales-ready leads.

 

            2. What’s the most important aspect of a successful nurture? 

 

The key to nurture is to present the right type of content at the right time. Having campaigns in place that address each stage of your buyers’ journey is crucial. Then, use demographic and behavioral data to move leads or customers between campaigns at the right time.  

 

 

The easiest way to visualize your campaigns during planning is in a linear email flow, where leads receive each email at a set cadence in a set order. But a good nurture strategy isn’t just limited to email. Once you have a nurture campaign built, you can easily promote that content in additional channels, driving traffic into the nurture flow.  You could boost engagement experience in a mid or bottom-of-funnel campaign by remarketing to your audience in SEM (search engine marketing).

 

 

3. How would you execute a simple drip?

The simplest form of nurture is one email touch leading to the next in a series. Some may be gated (behind a form) and some ungated. Stringing together a series of email touches in a linear layout is a drip campaign, one after the next with no change in cadence. This is where most “nurturing” starts.

 

 

Drip campaigns are a great way to begin building your nurture. No matter how complex the structure eventually becomes, the first few pieces were probably built in a linear progression. And frankly, sometimes you just need to get stuff in market! A drip is a place to start.

 

 

 

 

 

 

4. What are the essential building blocks of a nurture?

 

 

Content Sets: Many marketers seem to like 3-touch nurture programs (a series of 3 emails) - I’m not really sure why. I suppose it could be aligning to the 3 stages of the funnel (TOFU, MOFU, BOFU), but you cannot possibly address the buyer’s journey in 3 touches. That said, you can think of these small series as a “set” (though it doesn’t have to be 3, it could just as easily be 4, 5, or 6 depending on you resources), a starting point. Build in chunks like this with each piece of content flowing into another like a conversation.

 

 

  

 

 

Persistent CTA: Now let’s make the flow more interactive. We already know buyers want to navigate their own journey. If you give them the opportunity, they will continue clicking and consuming. An important step to capturing that interest  is to introduce a “persistent” call to action – a hander-raiser CTA somewhere that’s always available. This way, buyers don’t have to wait for you to “lead” them to the bottom of the funnel, they can self-select their way there.

 

 

Follow-on Content: Next, build in routes to more content using follow-on content. This is the reason for building in “chunks.” If you build 4 touches (see: Content Set) at once, the assets in the first touch can include thumbnails with links to the next 3. I like to include these on the thank you pages and thank you emails rather than before the form – in this way, your gated page can remain still be streamlined and uncluttered with extra content.

 

 

Connected Content: Each piece of content that buyers engage with should lead to more content. If nurture is a game of chutes and ladders – consider building in as many chutes as possible.

 

 

 

 

 

5. What is layering?  

 

 

Once you have a few nurture tracks in place of decent length (depending on your buyer’s journey and time-to-buy, begin routing between them and creating more intricate flows. For example, let’s say you have a top-of-funnel nurture campaign in place that uses the follow-on content approach described above. Mid-way through your campaign, begin placing follow-on content from your mid-funnel campaigns on your thank you pages and emails. Set up trigger campaigns that move leads from one engagement program to another based on consumption.

 

 

 

 

 

 

 

 

 

 

Set up scoring campaigns that watch for buying signals on your site and with your content. If a lead interacts exclusively with content from one solution – route them to a bottom-of-funnel campaign focused on that.

 

 

 

 

 

 

6. Are you leveraging tokens in your nurture? If so, how do you connect your token strategy with your nurture strategy?

 

 

Building this many campaigns, each with multiple touches, and each of those having 2+ assets (emails and landing pages) a piece, is a seriously heavy lift. Using tokens makes building more manageable as well as consistent across multiple users.

 

 

A few tokens that I like to use when mass producing nurture touches are:

 

      
  • Persistent CTA copy and URL – this is the hand-raiser call to action in the footer of emails and landing pages (get a demo, free trial, contact, etc.); this one lives at the Engagement Program level to carry through each nested touch program
  •   
  • Banner images – a gated touch will have 2+ emails and landing pages each, a token saves you from adding it in 4 places, or worse, missing one
  •   
  • Primary CTA copy and URL – this is the button copy and link for whatever piece of content you’re highlighting in the touch
  •   
  • Follow On Content images, descriptions and URLs – tokenizing these components streamlines the follow-on content set up process

 

 

 

 

 

 

7. What different kinds of nurture do you use?

 

 

General Nurture: This is the place to put everyone you don’t know what to do with. This nurture would contain top-of-funnel content that’s highly engaging. There could be a great length of time where the right people just aren’t ready to buy, so this is an opportunity to keep them engaged until they are – all while establishing your brand as a trusted source in your space.

 

 

Persona Nurture: Once you’ve established who someone is, you may move them to a campaign focused on their pain points. This still isn’t the right place to sell – but you can start honing your message.

 

 

Bottom-of-Funnel Nurture: This nurture may be product-, service-, or solution-focused. This is where you finally get to talk about yourself. You’ve had lots of time to lay the groundwork – proving your authority, laying out resolutions to their pain points at a high level – now you can spell out just how your solution is the best choice.

 

 

Recycle Campaigns: Direct leads that have been rejected by sales here, it may be worth having one for each reason they may be rejected, aligning to BANT (budget/authority/need/timing) or closed lost for any number of reasons – competitor-focused campaigns fall under this category.

 

 

Onboarding Campaigns: Campaigns for new customers – training, adoption, etc.

 

 

Cross-Sell/Up-sell: Campaigns for existing customers, especially if there’s particular value in having a combination of multiple offerings. These can be created by adjusting the messaging in your bottom-of-funnel campaigns.

 

 

8. When do you use one nurture with multiple streams vs multiple nurture programs that function as a singular marketing strategy?

 

Typically, one Engagement Program with multiple streams makes the most sense if you’re marketing to a single defined audience that’s moving through stages. A general nurture top-of-funnel campaign, for example, might have content stages that you want leads to progress through. You’d build multiple streams and create transition rules that move them from one to the next as they engage.

 

When you learn something new about a lead – their persona, or their product of interest – it’s time to route them to a new nurture track that caters to the segment they’ve just qualified for.

 

 

 

 

 

9. Results: Integrated Nurturing ecosystem

 

 

Pulling all of these components and tactics together results in an integrated nurture ecosystem. A place where your prospects can move through your content at their own pace - as quickly, or slowly, as they like - like chutes and ladders. Where triggered routing smart campaigns move them between nurture tracks to address their needs and pain points.

 

Keys to success – integrated nurture ecosystem:

 

1.     Build in “chunks” rather than all at once

 

 

2.     Create routes to continued consumption with follow-on content

 

 

3.     Use triggered campaigns to route leads between engagement programs. Trigger on:

 

 

a.     Content engagement

 

 

b.    Persona and interest scoring

 

 

4.     Include a persistent CTA to harvest sales-ready leads faster

 

 

 

 

New developers (and non-developers) often mistake values that might be converted to, converted from, or treated sort of like Booleans for a language’s actual Boolean type

 

This leads to internal contradictions like:

 

I have a Boolean with field with values “on” and “off”.

 

and

 

If my Boolean is set to 1, I want to do this. If it's set to 0, do this other thing.

 

If the value is really a string like “on” or a number like 1, you’re not (or no longer) working with a Boolean.

 

Boolean vs. sorta-boolean-ish-sometimes

In general, if a programming language includes a Boolean type, then a Boolean variable can have one of two canonical[1] values:

 

    one value will be the unquoted keyword true

    the other will be the unquoted keyword false

 

The keywords might be lowercase (true/false), uppercase (TRUE/FALSE), or mixed-case (True/False); some languages are case-insensitive.

 

But the quoted strings true” and “false”, the numbers 0 and 1, the numeric strings “0” and “1”, let alone strings like “Yes” and “No” or “Y” and “N” or “opt-in” and “opt-out” or the empty string “” — with rare exceptions in the world of SQL[2] (but not in Marketo) none of those are Boolean values.

 

Sure, those pairs of values might be a lot of useful things:

 

    labels for radio buttons on a form that map to a Boolean on the back end…

    values associated with a custom checkbox-lookalike field

    workarounds because some aspect of an app doesn't support Booleans…

    values that convert predictably to/from Booleans in a specific framework or language…

    the way an app displays Booleans in emails or web pages (hint, hint)

 

But not real Booleans, and the difference matters.

 

Just one of the reasons it matters in Marketo-land

A recent Velocity question from a client brought the difference into high relief:

 

  

With our Product Registration CO, how come the condition #if($product.isRecalled)always seems to match, even if isRecalled is set to false?

 

I immediately looked at their Custom Object setup in Admin, and check it out:

 


 

Bingo! The field isn’t a Boolean. It’s an Integer, presumably sent to Marketo exactly as it’s stored in their in-house SQL database.  And they were trying to use it in a Boolean context (an #if() condition) and expecting 1 and 0 to be truthy and falsy respectively. 

 

But Velocity doesn’t work that way. (Java doesn’t work that way, either: in Java, a strictly typed language, if something ain’t a Boolean, you’ll get a fatal type mismatch error, truthiness and falsiness do not apply.)

 

Note even real Boolean fields (CO or Person fields) require special treatment once they get into Marketo’s Velocity, as I’ve written about before. But here the field wasn’t even a Boolean to begin with! I’ll show you the slickest way to do the required conversion in Velocity in a moment.

 

But first, a little more about real Booleans vs. boolean-ish values, to help you code and/or talk to coders.

 

Truthiness doesn’t change the truth

I don’t blame folks for thinking 0 and 1 are low-level Boolean values at first. There are a bunch of things that can lead you down that path.

 

MISLABELING

Many SaaS apps don’t do you any favors by calling anything vaguely yes-ish/no-ish a “boolean” to sound cool (I guess?). It would be better to call such things “YepNopes” than to be misleading about the real type.

 

THE STRING-CENTRIC WEB

Bedrock web technologies operate exclusively on strings. Take a standard x-www-form-urlencoded form post or a URL query param: those are all characters on the wire.[3]

 

TRUTHINESS

This is a big one. Some languages assign “truthiness” or “falsiness” to certain non-Boolean values, so they don't have to be manually converted to the Boolean datatype to be used in a Boolean context. This can make for less complex-looking code (for what that’s worth) but with a cost of risk and just bad learnin’.

 

Make no mistake: just because a language considers if(1) or if("1") to be true, that doesn’t mean 1 and “1” are Booleans. They’re merely treated, on-the-fly, as Boolean true or false. And a value that’s temporarily truthy in an if() condition may not even be true when permanently converted using toBoolean(), bizarre as that might seem!

 

Moreover, truthiness rules are totally different across languages. One language’s truthy is another language’s falsy — or another language’s fatal TypeError. I’m not harping on hard-to-find cases: 0 and empty “” are falsy in JavaScript; they're both truthy in Velocity.

 

THE ELUSIVENESS OF SQL BOOLEAN

SQL databases are probably the main way people come to think 0 and 1 are Booleans, when in fact they’re just a very common way of storing things that were/will be Booleans.

 

SQL — the ANSI SQL standard, not all products based on that standard — does have a column type BOOLEAN. However, of the big 5 SQL products, only Postgres supports it. Microsoft SQL, MySQL, Oracle, and DB2 don’t. So, to avoid database vendor lock-in, you must use some other SQL type to store values that will be converted to/from Booleans in your app code. The narrowest possible INT, with values restricted to 0 and 1 via a constraint, is very widely used. (Though it’s not the only choice.)

 

Back to Marketo

You know I couldn’t resist that technical dive. But let me flip modes and get back to the matter at hand.

 

Velocity is more forgiving than Java, so it doesn’t throw an error[4] when non-Booleans are used in a Boolean condition. Velocity uses a very simple rule: anything other than null or the real Boolean false is truthy. That means an empty String, a non-empty String, an Object or List (regardless of whether it has any  properties/items), and 0 and 1 and all other Integers are all going to match #if($something)!

 

Now it’s clear why the client’s attempt to use #if($product.isRecalled) was doomed to fail.

 

So what’s the simplest way to turn 1/0 to true/false?

 

There’s clearly a plodding way to do it:

 

#if( $product.isRecalled.equals(0) )
#set( $product.isRecalledBoolean = true )
#else
#set( $product.isRecalledBoolean = false )
#end

 

But that’s going to be cumbersome to call every time. What about converting the values to Booleans using ConvertTool?

 

#set( $product.isRecalledBoolean = $convert.toBoolean($product.isRecalled) )

 

Nope. ConversionTool’s toBoolean() only treats the String "true" as convertible to Boolean true. Numbers 0 and 1 will both convert to Boolean false. (Notice how that’s the opposite of 0 and 1’s truthiness? That’s the kind of unpredictability I’m talking about!)

 

But there’s a secret weapon we can use to keep the conversion to a (slightly longer) one-liner: DisplayTool’s plural() method.

 

DisplayTool.plural() would be better called DisplayTool.notOneOrMinusOne(), because 0 is considered plural:

 

 

This quirk is great for our case, because now we can do:

 

#set( $product.isRecalledBoolean = $convert.toBoolean($display.plural($product.isRecalled,"true"))

 

 Here’s how this one-line contraption works:

 

      
  •  plural checks if the first argument ($product.isRecalled) is what it considers “singular” or “plural”   
          
    • if the argument is singular (that is, for our purposes, if it’s 1 – since we’re saying it’s never anything other than 0 or 1) then it returns the second argument, the string "true"
    •     
    • if the argument is plural (that is, if it’s 0) then it returns the second argument appending the letter “s”, to make the string "trues"
    •    
  •   
  • $convert.toBoolean then processes the returned value, which is always "true" or "trues"   
          
    • toBoolean("true") converts to Boolean true
    •     
    • toBoolean("trues") converts to Boolean false
    •    

 

So we’ve implemented the desired 0→false, 1→true conversion. Cool, eh?

 

 

 


 

Notes

 

[1] “Canonical” means a value that can be both written to and read from the variable. I use this qualifier because some languages allow alternate values to write a Boolean, but you’ll never see those values when you read a Boolean. For example, in XML, 0 and 1 are synonyms for true and false when setting an xs:boolean. But the values read back out are always true or false.  And the Postgres example below applies as well.

 

[2] Postgres goes bizarrely further than just the standard TRUE/FALSE keywords (though those are the preferred values for setting SQL BOOLEANs). They allow lots of aliases to be used for setting values: the strings “true”/“false” and “t”/“f”, even “yes”/“no” and “on”/“off” and a few more. Stranger still IMO is that the output values are the strings “t” and “f”. So you would say the canonical values are “t” and “f” in that particular dialect (since those are the only values that can be both written and read). Weird exception.

 

[3] Funny to call XML “newer” than anything as it’s so ancient, but both XML and JSON reserve special Boolean values (as well as distinguishing numbers from numeric strings) even though the overall encoding is still character-based. But regular form POSTs and GET params are strings through and through, until/unless the server chooses otherwise.

 

[4] This forgiveness is not necessarily a good thing. As I’ve droned on about before, Velocity suppresses a lot of fatal errors to keep your app running, but not all types by any means: you can trivially cause a fatal error by .get()-ing the 1st item in an empty List!

*** Posted on behalf of Marketo Champion Ajay Sarpal, Consultant - Martech and Sales Operations.***

 

In my 20+ years of professional experience with big brands, mid-tier and start-ups, just like each marketer I was also asked by my previous CMOs on:
• How to find out our best performing campaigns or channels?
• Which channels or programs are influencing top of the funnel, bottom of the funnel and middle of the funnel?
• What channels are driving the most leads?
• What channels are driving the most opportunities?
• What channels are ultimately driving the most revenue?
• Sales team keeps complaining that Marketing is not influencing the opportunities. Lead quality is questionable and lead volume is low. How to generate qualified leads? Which channel or marketing source is best in winning the opportunities? How to addresses the misalignment between sales and marketing teams?
• How much revenue is being generated from our offline channels/online channels/landing pages/content?
• Allocate marketing funds to top-performing channels
• How to forecast marketing goals in relation to revenue?

 

Bizible helps us in getting all the above stated questions answered. Although there are many Marketing Attribution tools available in the market but with my experience Bizible is one of the best attribution platform because you can automate many tasks by efficiently using UTM parameters and it helps in streamlining the process by avoiding creation of multiple campaigns for each and every channels for the same program. Bizible has an edge over competitors as not many attribution providers share the details of “First Touch” which is before lead creation touchpoint. It is scalable and builds meaningful and insightful reports for better aligning sales and marketing teams. Bizible helps in bringing transparency, finding out top or low performing channels or programs and in the end provides insights in generating more qualified leads for your organization. Bizible automatically tracks and reports on channel performance, providing visibility into which channels are driving the most customer engagement and allowing you to optimize your marketing spend accordingly.


Touchpoint Details: The structure of Bizible’s attribution models reflects the four major touchpoints that occur in the customer journey:
• First Touch (FT)
• Lead Creation (LC)
• Opportunity Creation (OC)
• Closed-Won deal (CW)

 

Single-touch Attribution Models which attributes 100% of the attribution Credit:

The First Touch model only focuses on the very first interaction a lead has with your organization. This model attributes 100% of the attribution credit to the first time the lead became aware of your company, the First Touch (FT).

 

The Lead Creation model attributes 100% of the attribution credit to the LC touchpoint, when a prospect provides their contact information and becomes a lead.

 

Multi-Touch Attribution Models – Position Based models are explained below:

U-Shaped Model: The U-Shaped model focuses on both the FT and LC touchpoints.

W-Shaped Model: Three of the milestone touchpoints are included in the W-Shaped model. In this model, the FT, LC, and OC touchpoints are each attributed 30% of the attribution credit. The remaining 10% is attributed proportionally to any intermediary touchpoints that occur between the three milestone touchpoints.

Full Path Model:  The full path model includes all four milestone touchpoints. FT, LC, OC and CW are each given 22.5% of the revenue credit, and the remaining 10% is distributed equally among the intermediary touches.

Marketing Attribution Insights after implementing Bizible:

The above picture shows the complete Sales Cycle starting with FT, LC, OC and CW marketing touches. The picture explains:
Anonymous First Touch is from Google Adwords.
Lead Creation Touch is from LinkedIn Blog Post.
Opportunity Creation Touch is from Pricing Page
Closed-Won Deal Touch is from Sales Dinner.

 

Various ways to get the insights from Bizible: 

Bizible Dashboards (Discover)

  • Bizible Boards:
  • Overview provides the most comprehensive view of your marketing performance, helping marketing teams make the right decisions when growing your team, budget, or revenue.
  • Growth displays a quick overview of all key marketing metrics in aggregate as well as a charted trend over time, allowing you to easily see the peaks and valleys in your performance.
  • ROI
  • Account-Based Marketing is made for marketers who focus their efforts on a list of targeted accounts. See how well you're hitting those persons, opportunities, and accounts.
  • Marketing Spend displays a thorough and aggregated view of your team's marketing spend across channels and campaigns, so you can use it to determine your upcoming budget.
  • Role- Based Views:
  • CMO targets the head of the Marketing team, displaying a comprehensive view of your team's marketing execution and can be used to determine which channels or teams are over or under performing.
  • Marketing Ops
  • Journey:
  • Velocity can help both Marketing and Sales determine how long the overall sales cycle is taking or visually see which stages or channels are slowing down your pipeline to optimize velocity for future opportunities.
  • Snapshot gives you a view that no other platform can provide. View the state of your CRM at any given point in time, with the distribution of records across Lead/Contact and Opportunity stages.
  • Passport presents a view of all your Leads/Contacts and Opportunities that have gone through each pipeline stage within a given time frame.
  • Engagement Path

 

CRM Reports:

The beauty of Bizible is that you can build custom reports within the CRM Systems. In Salesforce, we can build following reports and if needed, we can customize the reports by adding the fields from your CRM by creating or editing Custom Report types:

 

Salesforce Reports:
• Leads with Bizible Touchpoints (Custom)
• Bizible Person with Bizible Touchpoints (Custom)
• Opportunities with Bizible Attribution Touchpoint (Custom)

 

You can also create a few cool reports:
o Top 5 Keywords converting to Sales Opportunities
o Top 5 Channels for Closed Revenue
o Top 5 Landing pages by revenue won or opportunity created
o Top 5 Social campaigns

 

 

With Bizible set up, you can now unlock the power of Marketing Attribution!

Community user JW asked a surprisingly unprecedented (AFAIK) question:

 

I am looking to send out a communication to our customers that includes that persons Policy Number. Unfortunately our legal department will not let us send the entire {{lead.Policy Number}} digitally. We are permitted to send the last 4 digits however...  with Velocity Scripting can I get it to show as *****3253?

 

Let’s be clear: this isn’t relevant to credit card numbers or SSNs because those fields would never be in Marketo in the first place! (Right, guys?)

 

But other fields – like Policy Number here, or other account/lead info like the person’s phone number – legitimately would be present in Marketo and your CRM, yet might be nice to partially redact in emails. In the unlikely event that an email is intercepted, the person’s privacy is protected, but there’s still enough data to know they’re the right recipient.

 

So here’s a quick one-liner to mask out 1234567890 to ******7890:

 

${lead.PolicyNumber.replaceAll(".(?=.{4,}$)","*")}

 

In brief, it’s a regex lookahead and replace. The pattern matches any character that has 4 or more characters between it and the end of the string, ergo, all characters except the last 4.

 

(Yes, there are ways to do this with indexOf and substring instead, possibly more efficiently than the regex… if you’re timing to the microsecond, that is. But as always with Velocity, saving a few lines of code is worth a few μs.)

***Posted on behalf of Kimberly Galitz, Marketing Automation and Attribution Specialist at Bandwidth.*** 

 

One of the (many) advantages to using Bizible is the ability to track your marketing efforts from both online AND offline channels, giving visibility into the performance of channels where it may not have been available before.

As cut and dry as online vs. offline may seem, it can be complicated to determine when things should be associated with an offline vs. online channel, and beyond that - which actual engagement should count as a touchpoint.

Most simply put, online channels are any channels that would be directly associated with your website or any integrated site – any display advertising, paid search, paid social, organic search, organic social, email, chatbots, etc.

Offline channels are associated with initiatives like direct mail, trade shows or hospitality events, marketing or sales research, and pretty much any other channel where a person’s engagement cannot be tracked digitally. The best way to begin is to write out all the marketing channels you have, and bucket them into online or offline.

 

Pro tip: It is important to understand that offline channel logic in Bizible is actually determined by the Campaign object, specifically (when using Salesforce), the Salesforce Campaign Type - that is how all the touchpoints are mapped into channels and subchannels. So be sure to have all of your “SFDC Campaign Type” ducks in a row before you implement!

 

Sounds pretty simple, right? There are always some curveballs in this game, so let’s look into just a few examples of challenges you might face.

 

Scenario 1:
We did a third party webinar, where all the leads registered for the webinar on the third party website—but this was a paid webinar where we received the list of leads after the webinar. How can we track the ROI here?

 

Tricky. Since it’s a website, you immediately think “online!” which is true - however, since the registrations were done on a third party website where the registrants were not tracked with the Bizible javascript code, the registration touchpoint won’t show up for you. For this instance, I suggest using a campaign in your CRM where you can enable touchpoints, adding all the leads you acquired for that third party webinar to this campaign. By adding all these leads to the campaign with touchpoints enabled, they will all get a touchpoint for that particular campaign/third party webinar.

Pro tip: You can update the touchpoint date inside the SFDC campaign to be the date of the webinar, to be most accurate

 

Scenario 2:
We sent out a direct mail postcard, asking people to visit a webpage to sign up for a demo at a tradeshow. We’d like to track the ROI of that postcard, what’s the best way to do this?

 

For instances like this it may be a personal preference, and may also depend on the tools you have in place. If you sent the postcard out with a vanity URL such as “https://www.mywebsite.com/meetus” you can use data from something such as Google Analytics to track page visits, and since there is a form on this page, you may also be able to track the form fills on that particular page, or from that referrer if the vanity URL initiates a redirect to a page that exists. Personally, I want to be able to track the conversions in an SFDC campaign where I can also input the costs to send the postcard out. So I would enter all the people we sent the invite to as “invited” (or sent, or whatever status you have!) and then I would have a trigger campaign in Marketo (or any other tool you use) that would change their status in that campaign to “registered” if they fill out the form. This way, we can see the ROI in the campaign because we are only adding touchpoints to “responded” members, or “registrants”. Of course, you can put referrer parameter criteria in place so that you are only counting those that filled out the form that came from the postcard.

 

So there you have it. Online vs. Offline channels are not too terribly hard once you think it through, but it is certainly helpful to sit down and make sure you know which of your channels play in each space before you get started.

Hello Marketing Nation,

 

It's that time of the year! Marketo is now inviting all Marketo Certified Experts to apply to become a 2020 Marketo Champion. Marketo Champions are an essential part of our advocate community, often providing content for all ranges of expertise. Whether you've seen some of their content here on Community or you've seen them speak at one of our global events, the Champions are deeply devoted to helping our customers achieve their goals, both personally and professionally, with Marketo. The application will be open until November 29th.

 

For more details around becoming a Marketo Champion, check out the Champion Info page listed here: Requirements & Benefits of the Champion Program 

 

If you're ready to apply today, you can find the application here: 2020 Marketo Champion Application 

 

If you have any further questions about the Champion Program’s criteria & requirements, please email us at customermarketing@marketo.com

 

Good luck to all the 2020 applicants!

For arithmetic in Velocity, the only safe approach is the so-called “tools-ish” one: use MathTool methods like $math.add, $math.sub, and  $math.mul.

 

The rule

Aside from the one exception later in this post, you should not use the Java-like math operators (+ and -).

 

The core reason is the plus sign + and minus sign - have different syntax rules in Velocity! You can easily create fatal errors – or worse, lines that are silently ignored – by forgetting that - must be surrounded by spaces.

 

That’s right, only one of these is correct Velocity syntax for subtraction:

 

#set( $a = 99 )
#set( $b = $a - 1 ) ## correct
#set( $b = $a -1 ) ## fatal ParserException!
#set( $b = $a- 1 ) ## fatal ParserException!
#set( $b = $a-1 ) ## non-fatal, but doesn’t subtract!!!

 

So I always teach people to use #set( $b = $math.sub($a,1) ). It’s longer, but you’ll never break your code by switching sub and add.

 

The exception

Despite the above, in the course of developing my Base64 encoding in Velocity code, I discovered a unique requirement that could only be met by purposely using the + operator instead of $math.add.

 

This is the only exception I’ve found, and it doesn’t override my recommendation above. But it is – to me and I hope to the couple of people like me out there – fascinating.

 

So.

 

In Base64 encoding, one of the steps requires Integers to be converted to binary Strings, 42 → “00101010”. (If this is complete gibberish to you, you can reread this post when you have a couple more years of development under your belt!)

 

To get those Integers, you need to convert signed Bytes (-128 through 127) to their equivalent all-positive Integers (0 through 255).

 

To convert signed Bytes, you need to use a bitwise AND. But the traditional AND operator, the & symbol in Java and most other languages, isn’t available in Velocity.

 

Since you can’t use notation like #set( $c = $a & $b ) in Velocity (that’s a fatal parser error) you need to find a method-based way – a “tools-ish” way, somewhat ironically – to do a bitwise AND: something like #set( $c = $a.and($b) ).

 

Miraculously, such a method does exist (after you do some hunting) so long as $a is a Java BigInteger.  

 

Hmm. So now you need to be able to construct a BigInteger from an Integer.

 
Getting a BigInteger

A BigInteger isn’t something you can explicitly create in Marketo’s Velocity (post-June 2019, that is), only implicitly. There’s one way to cheat it, and that is to set a variable to just outside of the range of a Long. If you know off the type of your head that the max positive Long is 9,223,372,036,854,775,807 (~9 quintillion) then you can add 1 to that:
    

#set( $aBigInteger = 9223372036854775808 )
${aBigInteger.class} ## will be class java.math.BigInteger

 

 

Skipping the magic

But let’s say you don’t know that magic number – you only know that Java’s

concrete integer Number types  are, in order of increasing width, Byte → Short → Integer → Long → BigInteger. (I think it’s reasonable for a developer to know that progression, but not reasonable to expect someone to remember that 9,223,372,036,854,775,807 is special.)

 

If you didn’t know the magic Long-BigInteger boundary, how would you make sure Velocity allocated a type big enough for a BigInteger? You’d want to say:

 

Gimme a Number that can hold Long.MAX_VALUE + 1.

 

Of course to do that, you‘d have to get a handle on a Long (in order to refer to the constant Long.MAX_VALUE). Since Velocity doesn’t give you a Long unless you’ve asked for something bigger than an Integer, you have to say:

 

First, gimme a Number with the value 0. I know that’s an Integer, since that’s the default type for whole numbers in Velocity.


Then, gimme another Number that can hold ThisMustBeAnInteger.MAX_VALUE + 1. That’s gonna be a Long.

 

Finally, gimme yet another Number that fits ThisMustBeALong.MAX_VALUE + 1. That third number must be a BigInteger.

 

And here is where + acts differently from $math.add.

 

MathUtils vs. MathTools

Hidden in the doc for Velocity’s MathUtils (part of Velocity’s internal API, not for public consumption) you’ll find this gem:

 

So now we know Velocity can do – in fact is explicitly documented to do – exactly what we need. If we add 1 to the max value of a Long, the result will be a BigInteger (and will also have the correct value, obviously).

 

Except this only applies to the literal + operator. MathTools’ $math.add also does addition, but doesn’t implement the “overflow correction” logic.

 

Let’s compare.

 

Using the + operator:

#set( $Integer = 0 )
Integer $Integer.class.getName() = $Integer
#set( $Long = $field.in($Integer).MAX_VALUE + 1 )
Long $Long.class.getName() = $Long
#set( $BigInteger = $field.in($Long).MAX_VALUE + 1 )
BigInteger $BigInteger.class.getName() = $BigInteger

 

The output will be:

Integer java.lang.Integer = 0
Long java.lang.Long = 2147483648
BigInteger java.math.BigInteger = 9223372036854775808


Exactly what we need. $BigInteger is a BigInteger we can use as a factory to make more BigIntegers ($BigInteger.valueOf) and then do fancy stuff like .and() and .or() and  .xor() which would otherwise be impossible in Velocity.

 

Let’s try using $math.add:

#set( $Integer = 0 )
Integer $Integer.class.getName() = $Integer
#set( $Long = $math.add($field.in($Integer).MAX_VALUE,1) )
Long $Long.class.getName() = $Long
#set( $BigInteger = $math.add($field.in($Long).MAX_VALUE,1) )
BigInteger $BigInteger.class.getName() = $BigInteger

 

 

The output will be:

Integer java.lang.Integer = 0
Long java.lang.Long = 2147483648
BigInteger java.lang.Long = 9223372036854775807

 

 

Whoa. We attempted to add 1 to the largest Long, but the attempt silently failed. $BigInteger (despite the name) remains a Long, and it never gets incremented: it’s still Long.MAX_VALUE or 9,223,372,036,854,775,807.

 

It’s notable, but doesn’t change the rule

The likelihood that you’d otherwise need to perform BigInteger-range arithmetic within Marketo is basically zero.

 

I mean, it would have to be something like… you’re using a webhook to take the squares of lead scores, and you have a score in the millions and must raise it to the power of 2 in a Velocity token. Let’s face it, relative to the chances you will at some point forget the spaces around -, that’s just not gonna happen.  The risk/reward overwhelmingly favors $math.add and $math.sub.

 

In this case we needed a BigInteger not because of its infinite range of values, but because we need to get at the cool methods it has – which Integers and Longs do not. (The underlying value is tiny, between -128 and 127 then converted to between 0 and 255.) So for this particular purpose, deliberately using + makes sense.

The removal of Reflection-based features from Marketo’s Velocity environment in June 2019 was a disappointment — however inevitable — to those of us who push email scripting to the limit.

 

Requirements like encoding, hashing, encryption, flexible rounding and randomization, XPath queries, and advanced filters/sorts on Custom Objects could no longer be met with Marketo alone. (Webhooks can fulfill a subset of those needs, but with nowhere near the performance and simplicity of Velocity.)

 

At the prodding of Community user TM, I set out to see if Base64 encoding, one of the Java methods that used to be callable from Velocity, could be written with “pure” Velocity and public Java methods, without relying on the Reflection API.

 

I was indeed able to do it... though unlike most of my Velocity work, this one wouldn’t File Under Fun!

 

You can call the #userlandBase64_v3 macro below in 4 ways:

 

#1: Basic usage

#userlandBase64_v3("some string")

The result uses the standard RFC4648 Base64 character set (A-Za-z0-9+/). (That is, the characters used/expected by browsers’ native btoa()/atob(), not a “URL-safe” variant.)

 

#2: Automatic URL encoding

#userlandBase64_v3("some string",true)

This option URL-encodes the input string (using $esc.url) before Base64-ing it, which is critical if (a) you have non-Latin-1 characters in the string and (b) you want to use the browser’s native atob() to decode the result.[1]

 

#3: Basic + URL-safe characters

#userlandBase64_v3("some string",false,"RFC4648URLSafePadding")

This result is always safe to embed in URLs directly, even in the path, because it uses - and _ in place of the more sensitive + and /.

 

#4: URL encoding + URL-safe characters: The works

#userlandBase64_v3("some string",true,"RFC4648URLSafePadding")

The safest route, as it accounts for both non-Latin-1 in the input string and for possible URL conflicts.[2]

 

Get the code

I don’t want to digress into how the code works in this post, but perhaps there’ll be a Code Anatomy follow up... my version of the code has lots of comments!

 

Enjoy...

 

#**
* Base64 encoding in Velocity without Reflection
* @version v3 2010-10-11
* @author Sanford Whiteman, TEKNKL
* @license MIT
* @param $inputString String String to transform
* @param $alsoURIEncode Boolean [default false] URI-encode the String first (support browser-native atob())
* @param $charset String ["RFC4648" | "RFC4648URLSafePadding" | default "RFC4648"] 64-character set
*#
#macro( userlandBase64_v3 $inputString $alsoURIEncode $charset)
#set( $Base64PaddingChar = "=" )
#set( $Base64ByteChunkSize = 3 )
#set( $Base64BitGroupSize = 6 )
#set( $Base64Charsets = {
  "RFC4648" : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  "RFC4648URLSafePadding" : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
} )
#set( $Base64Charset = $display.alt($Base64Charsets[$charset],$Base64Charsets["RFC4648"]) )
#set( $uriEncode = $display.alt($alsoURIEncode, false) )
#set( $Integer = 0 )
#set( $Long = $field.in($Integer).MAX_VALUE + 1 )
#set( $BigInteger = $field.in($Long).MAX_VALUE + 1 )
#set( $String = "" )
#set( $Base64Final = [] )
#if( $uriEncode )
#set( $inputString = $esc.url($inputString) )
#end
#set( $inputBytes = $inputString.getBytes() )
#set( $inputList = [] )
#set( $void = $inputList.addAll( $inputBytes.subList(0,$inputBytes.size())) )
#set( $inputSizeB = $inputList.size() )
#set( $padding =  $math.mod($inputSizeB, $Base64ByteChunkSize) )
#if( $padding > 0 )
#foreach( $pad in [1..$math.sub($Base64ByteChunkSize,$padding)] )
#set( $void = $inputList.add($null) )
#end
#end
#foreach( $byteGroup in [0..$math.sub($math.idiv($inputList.size(),$Base64ByteChunkSize),1)])
#set( $startOffset = $math.mul($byteGroup,$Base64ByteChunkSize) )
#set( $endOffset = $math.add($startOffset,$Base64ByteChunkSize) )
#set( $bytes = $inputList.subList($startOffset,$endOffset) )
#set( $integerList = [] )
#foreach( $byte in $bytes )
#if( $byte )
#set( $currentInteger = $convert.toInteger($byte) )
#set( $void = $integerList.add($convert.toInteger($BigInteger.valueOf($currentInteger).and($BigInteger.valueOf(255))) ))
#else
#set( $void = $integerList.add($null) )
#end
#end
#set( $binStrings = [] )
#foreach( $currentInteger in $integerList )
#set( $void = $binStrings.add($String.format("%8s",$Integer.toBinaryString($currentInteger)).replace(" ","0")) )
#end
#set( $allBinStrings = $display.list($binStrings,"") )
#set( $bitGroups = [] )
#foreach( $bitGroup in [0..$math.sub($math.idiv($allBinStrings.length(),$Base64BitGroupSize),1)] )
#set( $startOffset = $math.mul($bitGroup,$Base64BitGroupSize) )
#set( $endOffset = $math.add($startOffset,$Base64BitGroupSize) )
#set( $void = $bitGroups.add($allBinStrings.substring($startOffset,$endOffset)) )
#end
#foreach( $group in $bitGroups )
#if( !$group.contains("null") )
#set( $Base64CharsetPos = $Integer.parseInt($group,2) )
#set( $Base64Char = $Base64Charset.charAt($Base64CharsetPos) )
#else
#set( $Base64Char = $Base64PaddingChar )
#end
#set( $void = $Base64Final.add($Base64Char) )
#end
#end
$display.list($Base64Final,"")
#end

 

 


Notes

[1] Related browser-side decoding:

decodeURIComponent(
  atob(myEncodedString).replace("+"," ")
);

 

[2] Browser-side hint for this format:

decodeURIComponent(
  atob(
    myEncodedString.replace("-","+").replace("_","/")
  ).replace("+"," ")
);

 

Filter Blog

By date: By tag: