Project · Iterable · Salesforce MC · Marketo

One Block Written for Three ESPs

The iD Tech Pathway progress block, written three times: Iterable Handlebars, SFMC AMPscript and Marketo Velocity. The data and the rendered email are the same on every platform, so the only thing that changes is the platform itself. One parent, Priya, has two Pathway students: Maya at level 4 and Theo at level 10.

  • Iterable
  • Salesforce MC
  • Marketo
  • Handlebars
  • AMPscript
  • Velocity
  • Personalization

Switch platforms and the number of students on file. The rendered email, the data as each platform stores it, and the code all change together.

Platform
Students on file
Your Pathway progress · iD Techto priya.k@example.com

Iterable · Handlebars
{{#each profile.students}}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
  <tr>
    <td class="student">
      <h2>{{studentName}} · Level {{pathwayLevel}}</h2>
      {{#if (eq pathwayLevel 10)}}
        <p>Level 10. {{studentName}}'s letter of recommendation is ready.</p>
        <a href="{{letterUrl}}">Download the letter</a>
      {{else}}
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
          <tr>
            <td width="{{progressPct}}%" height="8" bgcolor="#AB9353"></td>
            <td height="8" bgcolor="#E9E5DC"></td>
          </tr>
        </table>
        <p>{{numberFormat pathwayXP groupingUsed=true}} XP ·
           {{math nextLevelXP '-' pathwayXP}} to level {{math pathwayLevel '+' 1}}</p>
        <p>Next on the path: <a href="{{nextCourseUrl}}">{{nextCourse}}</a></p>
      {{/if}}
    </td>
  </tr>
</table>
{{/each}}

The block

One card per student. Each card shows the student's level, a progress bar toward the next one, the XP still to earn and the next course on their path. At level 10 the bar gives way to the letter of recommendation, which is where the Pathway ends.

The block goes in the level-up email and in the parent newsletter, so it has to handle a family with one student, several, or none.

Progress is sent, not calculated. The Pathway XP service owns the level thresholds, so it writes progressPct alongside the XP. The email, the app and the camp badge always agree, and each template only does the small math: XP to go and the next level number.

FieldMayaTheo
Level410
XP3,6509,850
Next level at4,000null
Progress65%100%
ShowsBar + next courseLetter

The same values sit on all three platforms. Names, URLs and numbers are examples.

Where the data lives

PlatformShapeWhat to know
IterableAn array of objects on the parent's profile, profile.studentsSet the field to the nested data type so a segment can match level and pathway on the same student. The XP service writes the whole array each time, since arrays replace rather than merge.
Salesforce MCA child Data Extension, Pathway_Students, one row per studentParentKey holds the parent's Subscriber Key. The template reads it by Data Extension name with LookupOrderedRows.
MarketoA custom object, student_c, linked to the parent's lead recordVelocity sees it as $student_cList, holding the ten most recently updated records, and only the fields checked in the script token's object tree.

Iterable · Handlebars

Handlebars is built for display. The loop and the level check read straight off the profile, and the documented math and numberFormat helpers cover the small arithmetic.

Rather than leaning on a template error to stop a send, the "no students" case is handled upstream, with a Journey filter before the send step.

SFMC · AMPscript

AMPscript fetches its own data. One LookupOrderedRows call pulls every student row by Data Extension name, and the FOR loop prints a card per row.

RaiseError with its second argument set to true skips this one subscriber and lets the rest of the send continue. RedirectTo keeps click tracking on links built at send time.

Marketo · Velocity

Velocity in Marketo can hand field values over as strings (booleans arrive as "1" or empty), so the script converts XP and level to numbers before comparing or subtracting.

Links are written with https:// outside the variable and the whole tag output in one piece, which is what lets Marketo track them. The smart list, not the script, decides who gets the email.

TaskIterableSalesforce MCMarketo
Print a field{{studentName}}%%=v(@name)=%%${s.studentName}
Fallback when empty{{defaultIfEmpty firstName "there"}}IIF(Empty(@firstName), "there", @firstName)#if( $lead.FirstName.isEmpty() )there#else${lead.FirstName}#end
Condition{{#if (eq pathwayLevel 10)}}IF @level == 10 THEN#if( $level == 10 )
Loop{{#each profile.students}}FOR @i = 1 TO @count DO#foreach( $s in $student_cList )
Arithmetic{{math nextLevelXP '-' pathwayXP}}Subtract(@next, @xp)$math.sub($next, $xp)
Format a number{{numberFormat pathwayXP groupingUsed=true}}FormatNumber(@xp, "N0")$number.format("#,##0", $xp)
Related recordsAn array on the profile, or a CatalogLookupRows, LookupOrderedRowsA custom object list
Skip a sendJourney filter before the sendRaiseError("why", true)Smart list filter
Link built at send timeMerge field in the hrefRedirectTo(@url)Literal https:// plus the variable

First name with a fallback, five platforms

The token I rewrite most in a migration. Three migrations (Mailchimp to Iterable twice, Marketo to Salesforce MC once) means a lot of these.

Iterable{{defaultIfEmpty firstName "there"}}
Salesforce MC%%=IIF(Empty(AttributeValue("FirstName")), "there", AttributeValue("FirstName"))=%%
Marketo{{lead.First Name:default=there}}
Mailchimp*|IF:FNAME|**|FNAME|**|ELSE:|*there*|END:IF|*
HubSpot{{ personalization_token('contact.firstname', 'there') }}

Marketo's form is the email token; inside a Velocity script the same field is $lead.FirstName.

Back to Projects

© 2026 Cheyne Mosher. All rights reserved.

Back arrow to go back to main home page.