CX Works

CX Works brings the most relevant leading practices to you.
It is a single portal of curated, field-tested and SAP-verified expertise for SAP Customer Experience solutions

Dynamic Personalized E-mail Image Rendering with External Headless CMS

13 min read

How to enable dynamic image rendering in e-mail personalization with external headless CMS

It is a known fact that personalized e-mail content increases your e-mail open rates, click-through rates, and ultimately drives your revenue.

Personalizing text with customer profile data, like a first name, is relatively easy as the information is available within your marketing platform. But what about personalized images? As the saying goes ‘A picture is worth a thousand words’, an image can have more emotional appeal and it can encourage a recipient to act on call to action.

To manage vast numbers of images for the different marketing attributes can be very challenging. At the same time one could face the requirement of a centralized media access for web or mobile applications.
A more specialized Content Management System (CMS) solution would be ideal to manage media assets more efficiently and to provide access from a central repository to every client application.

As a SAP Marketing Cloud customer you might already have a CMS solution in place. In this article, you will learn how to consume images from an external CMS to drive e-mail personalization.

Table of Contents

Use Case

To demonstrate an external e-mail image use case, we take an example of a café owner who wants to improve his e-mail promotion campaign based on gender information.

In this use case Headless CMS by Contentful will be used to demonstrate the external repository usage. This solution was randomly picked and does not suggest any preferences compared to similar CMS solutions.


CMS solutions have been around for quite some time. In a traditional CMS setup, the content is tightly coupled with the application structure and the UI such as WordPress, Joomla & Drupla to name a few.

The key feature that differentiates 'headless' from 'traditional' Content Management Systems is the content decoupling from a specific application UI. Media content can be accessed mainly through Application Programming Interface (API) calls.

However, the API doesn’t usually return media content directly. Instead of that, the API returns media asset metadata which also contains the actual media endpoint URL. Therefore, the Content Studio in SAP Marketing Cloud won’t be able to consume the image assets directly.

As a workaround you need to provide a lightweight wrapper middleware/application to wrap the API call and to redirect the image endpoint.
In the described use case the wrapper will expose the entry point with URL pattern including a gender value as shown below.


Gender information is provided by the respective contact attribute with the following possible values: male, female, or unspecified.

Generally, the implementation of a wrapper logic can be achieved with a few steps:

  1. Map the gender value to the CMS asset ID. In our example the mapping is hard coded but it could also be maintained in a database. 
  2. Trigger a headless CMS API call by using the asset ID to extract the media asset metadata.
  3. Perform a URL redirect to the metadata image endpoint.

Contentful provides API SDK snippets in various languages like node.js, Java or PHP. In the described use case, the wrapper will be implemented as node.js application.

SAP Business Technology Platform (SAP BTP), Cloud Foundry runtime will be used as the wrapper runtime environment since the trial account can be set up quickly to support node.js deployment.

The choice of wrapper coding language and runtime environment can differ based on the headless CMS solution vendors and depending on the supported API coding language.


The goal of this section is to showcase dynamic e-mail content in SAP Marketing Cloud based on an external image repository using Contentful as a reference.

The same concept can still be applied for integrating any other content repository solution with SAP Marketing Cloud.


  • Basic understanding of node.js development.
  • Application deployment on SAP Business Technology Platform on CF with cf CLI interface. Refer to this link on setup steps.

Contentful Setup

Sign up for your free Contentful account to gain access to sample images. 

Click on Media on the top menu, in this use case the following images will be used: 

  • The images "Women with black hat" and "Man in the fields" will be used depending on the respective gender value. 
  • Image "City" will be used as fallback image when gender is unspecified.

In case you don't find those images, you can upload your own sample images for testing purposes.

After choosing an image, select the "Info" tab on the right to identify the asset id.

This image asset id will be used at a later stage to map to the respective gender attribute in the wrapper application development.

Sample Wrapper Application

In this section, a sample wrapper application will be developed and deployed on SAP BTP, Cloud Foundry runtime.

Develop Wrapper  Application

Start from any project parent directory (e.g.  project-parent-directory ) of your choice.

Run mkdir command to create a new project folder name e.g.  CX1166Marketinge-mailDynamicImages.

> cd ../project-parent-directory
> project-parent-directory $ mkdir CX1166Marketinge-mailDynamicImages

Afterwards run the command to initialize the node.js project within the wrapperapp folder. 

Switch to your project (CX1166Marketinge-mailDynamicImages) directory to create another folder e.g. wrapperapp.

> project-parent-directory $ cd CX1166Marketinge-mailDynamicImages
> CX1166Marketinge-mailDynamicImages $ mkdir wrapperapp
> CX1166Marketinge-mailDynamicImages $ cd wrapperapp
> wrapperapp $ npm init

A sample npm init run is shown below. The server.js (instead of index.js) should be specified as entry point.

Install the required express and contentful dependence module.

Also create a blank javascript server.js which we have specified during npm init.

> wrapperapp $ npm install express --save
> wrapperapp $ npm install contentful --save
> wrapperapp $ echo > server.js

See the resulting project folder structure as below:

Open server.js in text editor and enter sample code implementation.

Inside this .js file:

  • Gender values are mapped to the respective target image asset id from Contentful.
  • The Contentful API is called to receive the image end-point and to perform a URL redirect.

In the described use case the attribute mapping between gender and image asset id is hard-wired. It is recommended to avoid a hard-wired mapping, as an example the mapping could be configured in a separate XML.

const express = require('express');
const contentful = require('contentful')
const app = express();
var urlPathSuffix = '/marketing/images/';
// Gender to image assetId hard coded mapping here is for illustration only. 
// It is recommended to avoid hard coded mapping, example you can configure mapping in XML separately
var genderImageMap = {
 'Male': {
 // description: 'Woman with black hat',
 assetId: '4shwYI3POEGkw0Eg6kcyaQ'
 'Female': {
 // description: 'Man in the fields',
 assetId: '6Od9v3wzLOysiMum0Wkmme'
 'Fallback': {
 // description: 'City pictured from the sky',
 assetId: '4NzwDSDlGECGIiokKomsyI'
app.get('/', function (req, res) {
var urlHostPrefix =;
 var tableStyle = '<head><style>table {font-family: arial, sans-serif;border-collapse: collapse;width: 50%;}td, th {border: 1px solid #dddddd;text-align: left;padding: 8px}tr:nth-child(even) {background-color: #dddddd;}</style></head>';
 var htmlPage = tableStyle+ '<body><H1>Hello world! ';
 var imageUrl;
 htmlPage = htmlPage + '<h2>' + 'Click one of the floowing link to show image' + '<table><tr><th bgcolor="#3498DB">Gender</th><th bgcolor="#3498DB">URL</th></tr>';
 for ( var key in genderImageMap) {
 imageUrl = 'https://' + urlHostPrefix + urlPathSuffix + key;
 htmlPage = htmlPage + '<tr><tr><th>'+key+'</th><th>' + '<a href="'+imageUrl+'">'+imageUrl+'</a></tr></th>';
 htmlPage = htmlPage + '</table><body>';
var findImageByGender = function (gender, callback) {
 if (!genderImageMap[gender])
 return callback(new Error('No matched for gender '+ gender)
 return callback(null, genderImageMap[gender]);

app.get(urlPathSuffix+':gender', function (req, res, next) {
 var gender = req.params.gender;
 findImageByGender(gender, function(error, genderImage) {
 if (error) return next(error);
// call contentful API and redirect to image end-point
 const client = contentful.createClient({
 space: '2y5dummy017',
 accessToken: 'MykBKLm1WvhcFictitiousGDf8eLyoA20RsVjkwd2o'
 .then((asset) => res.redirect('https:' + asset.fields.file.url))
const port = process.env.PORT || 3000;
app.listen(port, function () {
 console.log('Application is listening on port' + port);

Together with the space ID, the access token is required as API call authentication method. Replace your application client space and accessToken which can be obtained by going to Contentful menu Settings→API key.

An "Example Key 1" has already been generated after the trial account registration.

 If you don't see the generated "Example Key 1", simply create your own new key.

Click into "Example Key 1" key details to copy both client space and accessToken and then apply to the server.js coding.

Deployment on SAP Business Technology Platform, Cloud Foundry runtime

Create Cloud Foundry manifest.yaml deployment file in the project directory CX1166Marketinge-mailDynamicImages.

Refer to the prerequisite at beginning of this this article for more details on how to deploy the application on SAP CF.

> wrapperapp $ cd ..
> CX1166Marketinge-mailDynamicImages $ echo > manifest.yaml

Add the following content to the file. For host name, simply choose any short name without any special character. This host name will become part of the cloud foundry application URL host name.

If you choose a different application name other than wrapperapp, ensure that you also adapt the correct name and path value accordingly.

- name: wrapperapp
  host: pslim
  random-route: true
  path: wrapperapp
  memory: 64M

To deploy, login with cf CLI by command cf login. You will be prompted for your CF e-mail account and password.

Once the login was successfully, deploy by cf push.

> CX1166Marketinge-mailDynamicImages $ cf login
> CX1166Marketinge-mailDynamicImages $ cf push

Sign into  your CF trial account to verify that the  wrapperapp  application has started successfully, and that the "Instance" column shows the value "1/1".

Click on  wrapperapp  to navigate to the application overview page.

In the overview, launch the application "Application Routes" URL. 

Click on each link to test that all images can be displayed correctly.

SAP Marketing Cloud 

SAP Marketing Cloud by default doesn't have a standard field to store the dynamic image URL for a gender specific use case. Hence, a custom field needs to be created.

In order to access this custom field in the content studio for e-mail personalization, this custom field must be assigned to segmentation profile.

Create New Custom Field and Assign to Segmentation

To enable a personalised attribute to hold the dynamic image URL information, create a contact custom field "YY1_CPG_IMGURLBYGENDER" (Campaign Image URL by gender).

During the contact best record update, the BADI "Update Interaction Contact (best record)" with the implementation shown below will ensure that the dynamic image URL will always be updated based on the gender value or use the fallback URL whenever gender is not 'Male' or 'Female'.

 lr_ic_root_new TYPE REF TO cuan_s_ce_ic_rt_badi.
 DATA: lv_image_by_gender_url_prefix type string value 'https:///'.
 DATA: lv_url_gender_suffice like lr_ic_root_new->SEX_FT.

 LOOP AT new_contact_root REFERENCE INTO lr_ic_root_new.
 if lr_ic_root_new->SEX_FT = 'Male' or lr_ic_root_new->SEX_FT = 'Female'.
 lv_url_gender_suffice = lr_ic_root_new->SEX_FT.
 lv_url_gender_suffice = 'Fallback'.
 concatenate lv_image_by_gender_url_prefix lv_url_gender_suffice into lr_ic_root_new->YY1_CPG_IMGURLBYGENDER_MPS.
 condense lr_ic_root_new->YY1_CPG_IMGURLBYGENDER_MPS no-gaps.

Afterwards assign the custom field to segmentation via the 'Marketing Extensibility' app. Refer to help documentation for details.

Content Studio

In the SAP Marketing Cloud Content Studio edit your e-mail content, click on the "Setting" tab to choose the respective segmentation profile in the "Personalization" field, which has the custom field assigned.

Enter the fallback image URL into "Fallback Source" field. While "Personalised Source" points to contact's custom field "YY1_CPG_IMGURLBYGENDER" (Campaign Image URL by gender).

Segmentation, Target Group and Campaign

Create a segmentation model that contains at least one 'Male' and one 'Female' contact. Based on this, create new target group that contains the 2 contacts for the e-mail campaign execution.

The sample campaign shows the target group and the e-mail with the dynamic image selected. 

Ensure that the e-mails are already allowlisted, then execute this campaign.

As the result of the campaign execution, the e-mail content should show the personalized images depending on the respective gender of the contact person.


With a very simple and lightweight wrapper application implemented to enable image endpoint URL redirect, this article has demonstrated how dynamic e-mail images from an external repository can be used to send personalized campaigns via SAP Marketing Cloud.

Besides the described use case in this article, further scenarios could be implemented. For example:

  • Display dynamic promotional  discount  images based on different age groups.
  • Depending on the loyalty membership status, campaigns can be launched with different images which would better reflect the recipient loyalty status.

Technically, there are no limits how SAP Marketing Cloud can support dynamic image e-mail content regardless of (traditional or headless) the CMS solution vendor,  as long as the image repository is accessible via either direct http, or indirectly via web service or API.