When AMPscript Isn’t Enough: Heavy Personalization with JavaScript in Salesforce Marketing Cloud
AMPscript is incredibly powerful for personalization in Salesforce Marketing Cloud (SFMC). It handles token replacement, date functions, conditional logic, and lookups with ease. But there are limits.
When you need to:
- Group records by categories or subcategories
- Sort items dynamically before displaying
- Build complex HTML tables with conditional formatting
…AMPscript alone quickly becomes hard to manage.
This is where Server-Side JavaScript (SSJS) inside Content Builder can be a lifesaver.
The Problem
Imagine you’re pulling records from a Data Extension. You don’t just want to loop over them and print them out – you want to:
- Group them by Category and Subcategory
- Sort them alphabetically
- Do some “ETL”
AMPscript can’t easily handle nested loops, object manipulation, or sorting arrays.
The Approach
The trick is to let JavaScript do the heavy lifting:
- Use SSJS to fetch, group, and sort your data.
- Build your HTML as a string.
- Store that string in an AMPscript variable.
- Print it out anywhere in your email.
This hybrid approach combines AMPscript’s personalization features with JavaScript’s flexibility.
Personalized Product Summary with cross-sell
You’re a sportswear retailer — let’s call the brand ActiveGear. Every month, you want to send customers a personalized account summary email that does two things:
- Reminds customers what they already own
- Products are grouped by category (Shoes, Apparel, Accessories).
- Active items are shown normally.
- Expired items (for example, products out of warranty) are displayed in gray with a strikethrough.
- Suggests complementary products they don’t own yet
- If a customer owns running shoes, you recommend shoe spray, performance socks, or a sports bottle.
- If they own a gym bag, you suggest a towel or fitness gloves.
- If they own apparel, you recommend matching accessories like a cap.
This approach makes the email more than just a static summary — it becomes a personalized shopping assistant that both informs and encourages new purchases.
Data Extension: <strong>UserProducts</strong>
| UserEmail | ProductName | ProductCategory | Status | SKU | SKU1 | SKU2 | SKU3 |
|---|---|---|---|---|---|---|---|
| alice@test.com | Nike Air Zoom | Shoes | Active | SH001 | ACC001 | ACC002 | APP001 |
| alice@test.com | Puma Gym Bag | Accessories | Expired | ACC010 | ACC003 | ACC004 | |
| bob@test.com | Adidas Hoodie | Apparel | Active | APP020 | ACC005 |
Data Extension: <strong>ProductCatalog</strong>
| SKU | ProductName | Price |
|---|---|---|
| ACC001 | Shoe Spray | 12 € |
| ACC002 | Running Socks | 18 € |
| APP001 | Sports Bottle | 15 € |
| ACC003 | Gym Towel | 10 € |
| ACC004 | Fitness Gloves | 22 € |
| ACC005 | Cap | 20 € |
%%[
var @htmlProducts
set @htmlProducts = ""
]%%
<script runat="server">
Platform.Load("Core","1");
// Current subscriber
var email = Attribute.GetValue("EmailAddress");
// Lookup owned products
var owned = Platform.Function.LookupRows("UserProducts", "UserEmail", email);
var html = "";
if (!owned || owned.length === 0) {
html += "<p>You have no registered products.</p>";
} else {
html += "<h2>Your Products with ActiveGear</h2>";
for (var i=0; i<owned.length; i++) {
var r = owned[i];
var name = r["ProductName"] || "";
var cat = r["ProductCategory"] || "";
var status = r["Status"] || "";
var style = (status === "Expired")
? "color:#888;text-decoration:line-through;"
: "color:#333;";
html += "<h3 style='margin-top:20px;'>"+ cat +"</h3>";
html += "<p style='"+style+"'>"+ name +" ("+ status +")</p>";
// === Related SKUs ===
var relatedSKUs = [r["RelatedSKU1"], r["RelatedSKU2"], r["RelatedSKU3"]];
var hasSuggestions = false;
for (var j=0; j<relatedSKUs.length; j++) {
var sku = relatedSKUs[j];
if (!sku) continue;
var prod = Platform.Function.LookupRows("ProductCatalog", "SKU", sku);
if (prod && prod.length > 0) {
if (!hasSuggestions) {
html += "<p style='font-weight:bold;margin:10px 0 5px;'>You may also like:</p><ul style='margin:0 0 20px 20px;padding:0;'>";
hasSuggestions = true;
}
var p = prod[0];
var pName = p["ProductName"];
var pPrice = p["Price"];
var pUrl = p["Url"];
html += "<li><a href='"+pUrl+"' style='color:#0096d5;text-decoration:none;'>"+ pName +"</a> - "+ pPrice +"</li>";
}
}
if (hasSuggestions) html += "</ul>";
}
}
Variable.SetValue("htmlProducts", html);
</script>
%%=v(@htmlProducts)=%%
Other examples
Subscription & Renewal Management (Telecom / SaaS)
- Scenario: A telecom company sends an account summary email listing all services (Internet, Mobile, TV) for each subscriber.
- Challenge: Customers may have multiple products in the same category (2 mobile numbers, 3 set-top boxes, etc.). They want expired contracts highlighted, and soon-to-expire ones flagged in orange.
- Why JS? Sorting subscriptions by product type and status requires grouping, which is clunky in AMPscript.
- Outcome: The customer gets a clean table of all their subscriptions, encouraging renewals or upgrades.
Travel Itinerary with Activities (Travel / Hospitality)
- Scenario: A travel agency sends an itinerary email with grouped activities: Flights, Hotels, Tours, Car Rentals.
- Challenge: Activities must be grouped by day and time, sorted chronologically. AMPscript can’t easily reorder things once fetched.
- Why JS? JavaScript can sort by datetime, group by “Day X,” and output a clean schedule.
- Outcome: The traveler gets an itinerary email that looks like a mini dashboard, boosting customer satisfaction.
Insurance Policy Overview (Insurance)
- Scenario: An insurer sends an annual account summary with all active policies (Car, Health, Property, Life).
- Challenge: Policies should be grouped by category, sorted by renewal date, with lapsed policies shown in red.
- Why JS? Handling multiple nested categories and date sorting in AMPscript would be painful.
- Outcome: Customers clearly see which policies are active, which need renewal, and can click straight to “Renew Now.”
Sorting the arrays or objects in SSJS
In a normal JavaScript environment, you’d rely on 🔒 This content is for Premium Subsribers only. Please log in to preview content. Log in or Register
You must log in and have a Premium Subscriber account to preview the content.
When upgrading, please use the same email address as your WordPress account so we can correctly link your Premium membership.
Please allow us a little time to process and upgrade your account after the purchase. If you need faster access or encounter any issues, feel free to contact us at info@martechnotes.com or through any other available channel.
To join the Discord community, please also provide your Discord username after subscribing, or reach out to us directly for access.
You can subscribe even before creating a WordPress account — your subscription will be linked to the email address used during checkout.
Premium Subscriber 19,99 € / Year
Share With Others








