Optimizely SaaS CMS: Balancing TCO and ROI in Your CMS Hosting Decision

With Optimizely SaaS CMS coming soon, I’ve been talking with companies about the tricky business of picking the right core system software for their business.

These chats really got me thinking, so I decided to jot down some thoughts to tackle the tricky topics of Total Cost of Ownership (TCO) and Return on Investment (ROI) when it comes to CMS and software selection in general.

Full article here: https://www.linkedin.com/pulse/optimizely-saas-cms-balancing-tco-roi-your-hosting-johnny-mullaney-cuh0e/

Opticon 2023:The Launch of “Optimizely One”

After another excellent Opticon event, I took time to distil my thoughts and write up some key takeaways which can be summarised in two words:

Choice and Instructions!

Check out my LinkedIn article here:

https://www.linkedin.com/pulse/opticon-2023the-launch-optimizely-one-johnny-mullaney-jmdle

Overriding Optimizely CMS Approval Sequences

Optimziely CMS Approval Sequences are an important tool for organisations that use CMS to translate, review and quality check content before publishing. A typical Optimziely CMS Approval sequences configuration involves multiple stages of approval, each requiring actions such as click to Approve/Decline and Commenting.

The Problem

I recently worked with a client who needed to bypass the administration overhead that Approval Sequences adds but only for certain CMS users , whose role within the organisation was to coordinate the large bulk publishing operations sometimes associated with releases.

While there is the option for administrators to use the “Approve Entire Approval Sequence” button, the consensus for this use case was that using this still added overhead for the editors.

Specifically, we needed to force the approval of Approval Sequences for these editorial users, reducing the number of interactions required. The key requirement is to streamline operations and minimise the effort involved in the publishing process for these CMS Editors.

The Solution

We settled on a solution to customise the Approval Engine using the IApprovalEngine interface:

When a CMS Editor clicks ‘Ready for Review’, use the Approval Engine events to:

  • Check is this user is part of the relevant User Group to override the approval sequence
  • If so, use the Approval Engine to force the approval of this sequence

Additionally we decided to give this Editorial team the option of reducing their clicks even more by Publishing content directly after clicking ‘Ready for Review’. As this isn’t necessarily recommended, but may be useful for this team in some scenarios, we put this feature behind a Site Setting feature toggle.

Technical Implementation

The following code demonstrates setting up an InitializationModule to override the approval sequence and publish. I’ve hard coded the admin email address in this example so you will should swap that out for your desired business logic.

        [InitializableModule]
        [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
        public class CustomApprovalsInitialization : IInitializableModule
        {
            private IApprovalEngineEvents _approvalEngineEvents;
            private ICustomerService _customerService;
            private IContentRepository contentRepository;
            private ISettingsService _settingsService;

            public void Initialize(InitializationEngine context)
            {
                _customerService = ServiceLocator.Current.GetInstance<ICustomerService>();
                _settingsService = ServiceLocator.Current.GetInstance<ISettingsService>();

                var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>();
                contentEvents.RequestedApproval += ContentEvents_RequestedApproval;

                _approvalEngineEvents = context.Locate.Advanced.GetInstance<IApprovalEngineEvents>();
                _approvalEngineEvents.StepStarted += _approvalEngineEvents_StepStarted;
            }

            private Task _approvalEngineEvents_StepStarted(object sender, ApprovalStepEventArgs e)
            {
                var user = _customerService.UserManager().FindByEmailAsync("admin@example.com").GetAwaiter().GetResult();
                if (user != null)
                {
                    var approvalEngine = ServiceLocator.Current.GetInstance<IApprovalEngine>();

                    Task.Run(() =>  {
                                        approvalEngine.ForceApproveAsync(e.ApprovalID, "admin@example.com", "Auto-approved by Admin.").GetAwaiter().GetResult();
                                    }).GetAwaiter().GetResult();

                }

                return Task.CompletedTask;
            }

            private void ContentEvents_RequestedApproval(object sender, ContentEventArgs e)
            {
                var settingsPage = _settingsService.GetSiteSettings<ReferencePageSettings>();
                if (settingsPage.ApprovalSequenceOverrideAutoPublish)
                {
                    var user = _customerService.UserManager().FindByEmailAsync("admin@example.com").GetAwaiter().GetResult();
                    if (user != null)
                    {
                        contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();

                        var content = contentRepository.Get<PageData>(e.ContentLink).CreateWritableClone();

                        contentRepository.Publish(content as IContent);
                    }
                }
            }

            public void Uninitialize(InitializationEngine context)
            {
                //Add uninitialization logic
            }
        }

High Value Data: Reshaping Brands and Driving E-Commerce Revolution

I’ve just published a new article on how high-value data is reshaping brands and driving the e-commerce revolution to the FTT blog.

In this piece, I delve into the importance of customer data as the fuel that propels your e-commerce revolution discussing how companies like Netflix and Spotify leverage comprehensive customer views and tailored content delivery for success, and how this is just the tip of the iceberg.

I also share insights on how to own your data using tools like Optimizely’s Data Platform (ODP) and how to formalize your data strategy to guide your company’s direction and ambitions as a data-driven organization.

At FTT, we believe in automating the execution of strategic thinking with test and learn cycles to grow quicker and liberate employees to focus on ever-higher value initiatives.

Check out the full article here and let me know your thoughts.

Hight Value Data: Reshaping Brands and Driving E-Commerce Revolution (ftt.ai)

Optimizely Data Platform (ODP) -> Commerce Cloud: Product Attribute Connector

The most powerful E-Commerce segmentation is possible when your data platform knows everything about your products. Then you can segment, personalise, experiment and sell to customers who are interested in various types of products.

This post explains how you can easily extend your product catalog data in ODP with First Three Things ODP Product Attribute Add On.

Optimizely’s ODP Commerce Cloud Integration

Optimizely’s Commerce Cloud integration is a no-code solution that uses Service API to sync Contact, Product and Order data to ODP.

https://docs.developers.optimizely.com/digital-experience-platform/v1.5.0-optimizely-data-platform/docs/import-data-from-optimizely-commerce-cloud

In terms of Product data, the integration sends universal catalog data such as product & variant codes, product/variant relationships, product name, image & price.

However every catalog is different with each company having their own metadata that provides additional context around each product. In an Optimizely product catalog, this data will be managed as properties attached to Products and Variants.

Sending this data to ODP allows you to create much more powerful customer segments and this is what our ODP Product Attribute Connector is responsible for.

Installation

Code is open source and available at: https://github.com/first3things/ODP-ProductAttribute-AddOn

Install the package directly from the Optimizley Nuget repository.

dotnet add package First3Things.ODPProductAttributeConnector

Add the following to your StartUp class. This extension will register the necessary dependencies.

using First3Things.ODPProductAttributeConnector.DependencyInjection;

services.AddOdpProductAttributeConnector(_configuration);

Add your API credentials to the appSettings.json file

  "ODPConnector": {
    "apiHost": "<-- host name e.g. api.zaius.com -->",
    "apiKey": "<-- your public api key retrieved in the admin area -->"
  }
}

Configuration

Create fields in ODP for product attributes you want to sync to ODP by logging in as an administrator and proceeding to:

Settings -> (Data Management) Objects & Fields -> Products

image

Additional information on that step is available in this blog post:

Update your Content Types

Add the ODP Sync attribute to any Product or Variant content type properties you want to sync.

Set the ODP Field Name within the attribute.

[OdpProductSync("brand")]
[CultureSpecific]
[BackingType(typeof(PropertyString))]
[Display(Name = "Brand", GroupName = SystemTabNames.Content, Order = 15)]
public virtual string Brand { get; set; }

Scheduled Job

An “ODP Product Attribute Connector” scheduled job will be installed in the CMS Administrative area.

This job uses reflection to retrieve your commerce content types and send property values tagged with the ODPProductSync attribute to ODP.

Additional Notes

Retrieve Catalog Business Logic

Multiple catalogs are not supported out of the box.

The business logic executed by the Scheduled Job picks the first Catalog.

If you need to overwrite this logic, inject a new implementation for

ICatalogService.GetCatalogRoot()

Useful Optimizely Documentation

Product Batch Request API: https://docs.developers.optimizely.com/digital-experience-platform/v1.5.0-optimizely-data-platform/reference/batch-requests

Recommended Product Fields: https://docs.developers.optimizely.com/digital-experience-platform/v1.5.0-optimizely-data-platform/docs/usecase-products#recommended-fields

A Marketing Managers Guide to a modern DXP Solution Architecture

As a Solution Architect, I nerd out on new technologies, plugging systems together, good code, solving problems and the general process of designing software.

And that is why I have been writing technical blog posts for a few years. 

This time I decided to write an article that was aimed at a non-technical audience. I wanted to give Marketers a primer on their role in DXP Solution Architecture and delicately explain why Optimizely can be a good fit.

I underestimated the challenge. I soon realised my new audience’s expectation for the structure of content, tone of voice and level of detail were now very different. The process of crafting something of value took more time than expected as I iterated over draft after draft.

Finally I have an article I am happy to put out into the world.

https://www.linkedin.com/pulse/marketing-managers-guide-solution-architecture-modern-johnny-mullaney

Creating Behavioural based Customer Segments in ODP- Post 2 of 2

In Blog Post 1, I talked about the foundations of delivering a super personalised experience to customers using Optimzely products.

In this post we’ll discuss the first pillar – Segmentation. Specifically, the technical implementation which sets the foundations for the super powerful Segmentation of your customer base.

Use Optimizely’s Data Platform (ODP) to harmonise data creating a 360 view of the customer from all channels (online, in-store, historical, real time, etc) and your extended Product data.

With ODP you can create Segments of customers who have similarities with regard to their characteristics, preferences, site engagement, behaviours and brand interactions.

When you reconcile your Customer data with your Product Catalog to indicate what type of interests your visitors have based on their behaviour – you have a very powerful Segmentation tool.

Integrate ODP with Optimizely B2C Commerce

There are two core ODP data entities you should enhance in ODP with data from your B2C Commerce system:

  • Products
  • Customers

Products

ODP contains a number of pre-built product connectors, one of which is the Commerce Cloud connector. It is a no-code connector that communicates with your Commerce Cloud website via the Service API.

The Commerce Cloud connector will synchronise your Core Product Data between Commerce Cloud and Product entities stored in ODP. By core product data I am referring to Product Code, Name, Image, Variants.

To enable more advanced Segmentation capabilities based on the type of products your customers are interacting with, you should enhance your product data with additional attributes stored against the products in your Catalog.

Consider creating a scheduled job to push your enhanced attribute to ODP via batch requests to the Products API.

https://docs.developers.optimizely.com/digital-experience-platform/v1.5.0-optimizely-data-platform/reference/batch-requests

https://docs.developers.optimizely.com/digital-experience-platform/v1.5.0-optimizely-data-platform/reference/update-products

Customers

I have covered an approach to integrating Customers in a previous blog post so won’t go into detail here but feel free to check this article out:

https://johnnymullaney.com/2022/06/14/integrating-optimizely-data-platform-for-gdpr-compliance/

ODP Set Up

This section assumes your technical integration is in place and we will now proceed to create a Segment based on our customers interaction with the enhanced Product Catalog.

In our example, we will create a Segment of Customers who have viewed Products where the product has a custom attribute called “Team” and the value of that property is equal to “Manchester United”

Add “Team” Product Attribute Field

Log into ODP as an Administrator and proceed to

Settings -> (Data Management) Objects & Fields -> Products

Click Create New Field and Add your Team field to the Product object

Segment Set Up

Go to the Segments interface and create a new Segment.

First set customers who have done the Product Detail View event.

Then add a Where Filter on the Product associated with this event so only products with “Manchester United” team values are associated with the segment.

As long as there are products in your catalog whose Team is “Manchester United” – auto complete will suggest this value. Super Easy! 🙂

Conclusion

The ODP integration explained above gives you a super flexible system to enhance Segmentation capabilities based on the type of products your customers are interacting with.

In the next post in the series, I’ll talk about using a Customers Segment Profile to provide a personalised experience across Web and Email channels.

Pillars of delivering a Tailored Customer Experience on Optimizely DXP- Post 1 of 2

New Era Cap has been manufacturing baseball caps for American sports teams since the early 1930s. They are a heritage brand with their roots firmly laid in Baseball, one of America’s most popular sports. Over their considerable history they have expanded to new sporting domains and evolved into a popular culture lifestyle brand with their products worn by some worlds most famous celebrities.

A brand like New Era Cap can mean a lot of things to a lot of different people whether you are a passionate New York Yankees baseball fan or somebody who likes to keep up with the latest fashion trends. Because of that, it is so important that the brand E-Commerce experience and communications talk to customers in a personalised and familiar way.

This blog series will demonstrate how brands like New Era Cap are using Optimizely DXP technologies to provide their customers with consistent familiar omnichannel experiences.

The Three Pillars of Tailored Customer Experiences

Working in collaboration with New Era, we used Optimizely DXP technology to implement the three pillars to delivering a Tailored Customers Experience.

Pillar 1 – Segmentation

To effectively personalise a customer experience, you need to understand who individual customers are – their profile, interests and the behaviours while interacting with your brand.

ODP (Optimizely Data Platform) is used to captures unified customer profiles and behaviours. Using ODP you can create Segments grouping customers based on engagement with your brand or any other profile/behavioural criteria you define.

Pillar 2 – Personalisation

ODP Segments can be used to personalise web and email content for your customers using Optimizely Content Cloud Visitor Groups. There are a number of methods that ODP segments can trigger personalised Email campaigns with the most appropriate depending on your Email Marketing solution provider.

Pillar 3 – Experimentation

How do you know the personalised content you are serving your visitors is converting? The next step is to Experiment with variations of your content.

This will inform you strategically what is working best within customer Segments and allow you to iterate effectively. Optimizely Experimentation has prebuilt integrations with the Content Cloud to test what exactly is converting with your real world customers.

Next Post

In the next post, I will delve into Pillar 1 – Segmentation using Optimizely technologies. We’ll look at how to build effective customer profiles and use that to effectively segment your audience.

Integrating Optimizely Data Platform (ODP) for GDPR compliance

The most common concern I’ve seen raised in Europe is how best to integrate ODP with your website in accordance with GDPR compliance data protection regulations.

Some ODP Background

ODP consists of the following key data entities that will be synced between your website and ODP.

Products Product catalog structure and master data

Customers The Visitors profile data telling us who this customer is such as Name, DOB, Language etc

Events A visitors various interactions with the website such as page views, login, add to cart, orders

Orders Orders that the visitor has placed in your E-Commerce system

Out of these 4 entities, all but the synchronisation of the Product feed is typically subject to GDPR compliance regulations.

Commerce Cloud Connector

Optimizely’s Commerce Cloud connector is a no code add on that can be used Optimizely Commerce Cloud’s Service API to synchronise Product, Customer Contact and Order data entities.

In Europe you would be best advised to enable Product imports but disable Contacts and Orders so that you can do the relevant checks to make sure that your customers have first agreed to the relevant cookie policies.

Note: ODP’s Commerce Cloud Connector is due for release in EU H1 2022

GDPR Friendly Integration

My advice is to separate your ODP tracking implementation from your websites code base by using a Tag Manager that forwards data to ODP only for requests that adhere to the relevant cookie policies.

I’ll assume you are using Google Tag Manager for the rest of this post.

Use your Cookie Opt In Trigger

You will likely already have an Opt-In performance cookie trigger set up in GTM to manage the execution of your Tags. The set up of your trigger will depend on the product you use to manage cookies but it should look like something similar to the below.

All ODP Tags should use this trigger to make sure that only customers who agree to share their data are synced to ODP.

Load the ODP JS Tag

As you are tracking a session, you need to load the ODP JS tag on each page. The JS tag will include the PageView event by default.

Log into your instance of ODP and copy the ODP Integrations -> Javascript Tag option .

Then in Google Tag Manager you can simply add a custom HTML Tag using the Opt-In Performance trigger

This will load the ODP JS script and fire the “pageview” event for customers who have accepted the configured cookie policies.

Using the Tag Manager Data Layer

Using this approach you can push any Tag Manager data layer events you are already firing to ODP and push new data layer events for any other data you want to track.

Customer Profile

As you learn new profile information on your customer, push a “CustomerProfile” event to the data layer with the customer profile object.

Then create a Tag to push that data onto ODP as demonstrated below.

Note the timeout wrapping the ODP API push. This is because of an intermittent issue with a race condition that sometimes caused this event to fire before the JS script was loaded resulting in the event failing to fire.

Event Tracking

Events are actions the customer takes on your website. They can be anything from page views to keyword searches or completing a checkout.

The below example triggers the Tag when a “Search” event is pushed to the data layer and the customer has accepted the relevant cookie policies.

Conclusion

Using a Tag Manager is a great low code option for integrating ODP into your website while maintaining your GDPR compliance.

Manually Importing Products from Commerce Cloud to Optimizely Data Platform (ODP)

ODP has a turn key Integration app called the Commerce Cloud Connector which can be responsible for the synchronisation of Contact, Order and Product data between your Optimizely Commerce Cloud instance and ODP.

However in Europe the Commerce Cloud Connector is not due to be released until the end of June 2022.

In Europe, due to GDPR guideline compliance you may not ever be able to turn on the connector for Customer Contact or Order data . I can cover that in a separate blog post but you will need to make sure that the relevant cookie policies have been selected by the customer.

While waiting for the connectors release in Europe, a manual product data export and import to ODP is a low cost interim solution. The key is to make sure that the schema you choose to save the products in ODP will be maintained when you turn on the connectors product import.

Key Product Data Schema Import Considerations

ODP currently supports 1 currency with that currency in the UI showing as USD. It is on the product roadmap to support additional currencies but for the moment you will have to choose whatever you determine to be the default currency on your website for the product export.

ODP is flexible with regard to your product data structure. However if you intend to use the connector, it’s best to follow a Product->Variant model to match your Commerce catalog so that the consistency of your data will be maintained.

Exporting Product data

Assuming your default market is GB, the following script will export your products from the catalog in GBP£.

SELECT
CN.[Name] as category,
CE.[Name] AS [name],
(
       CASE
              WHEN Charindex('_',CE.Code)> 0 THEN CE.Code
              ELSE CE.Code
       END
)AS sku,
min(PD.UnitPrice)AS price
,
(
       CASE
              WHEN CEChild.Code IS NOT NULL THEN CEChild.Code
              ELSE CE.Code
       END
)AS parent_product_id
FROM [dbo].[CatalogEntry] CE
		INNER JOIN NodeEntryRelation NR ON NR.CatalogEntryId = CE.CatalogEntryId 
		INNER JOIN CatalogNode CN ON CN.CatalogNodeId = NR.CatalogNodeId
       LEFT OUTER JOIN [dbo].[CatalogEntryRelation] ER ON ER.ChildEntryId = CE.CatalogEntryId
       LEFT OUTER JOIN [dbo].[CatalogEntry] CEParent ON ER.ParentEntryId = CE.CatalogEntryId
          LEFT OUTER JOIN [dbo].[CatalogEntry] CEChild ON ER.ParentEntryId = CEChild.CatalogEntryId
       LEFT OUTER JOIN [PriceDetail] PD ON PD.CatalogEntryCode = CE.Code
WHERE (PD.MarketId ='GB'OR PD.MarketId IS NULL)
GROUP BY  CE.CatalogEntryId, NR.CatalogNodeId, CN.[Name], CE.ClassTypeId, CE.Code, CEParent.Code, CE.MetaClassId, CEParent.MetaClassId, CE.Name, CEParent.Name, CEParent.CatalogEntryId, CEChild.Code, CEChild.MetaClassId
Order BY CE.CatalogEntryId

The output of the script can be copied to a CSV file for import to ODP.

Row 1 of the above export is the product with the variants linked by the parent_product_id

Price in this example is pound price for the GB market. You can update the Where clause to isolate the prices for your default market of choice. Support for multi-market & multi-currency pricing is on the roadmap for ODP so i look forward to hearing more on that in the coming months.

Importing to ODP

Copy the output of this script to Excel to convert to a CSV file before doing the following:

  • Do a Find/Replace to convert any “NULL” values in the price column to empty
  • Update price values to 2 decimal places

The name the file odp_products.csv and drop into the ODP Integration CSV Upload interface

Conclusion

While you’re waiting for the ODP Commerce Cloud connector to be released in Europe, this simple Export/Import process will help you migrate product data into ODP in a way that will be consistent with the connector schema.