Publication List Banner
Marketing Cloud

Real-Time Multi-BU Publication List Management from Salesforce CRM to SFMC

Overview

Salesforce Marketing Cloud (SFMC) includes a List-Unsubscribe header with all commercial emails, enabling a seamless one-click unsubscribe experience for recipients. This header, authenticated via DKIM and interpreted by email clients, allows users to opt out without interacting with the email body. While convenient, this feature can lead to unintended global unsubscribes, especially when channel-specific Publication Lists are not used.

Email clients process unsubscribe requests in two ways:

  • Mail-To: Sends an email to SFMC, which processes it like a reply-based unsubscribe.
  • One-Click POST: Automatically opts the user out, bypassing SFMC’s confirmation pages and directly updating the subscriber’s status.

Due to RFC 8058 compliance, these headers cannot be disabled or redirected to custom landing pages—posing a challenge for marketers who want more control over subscriber preferences across multiple Business Units (BUs).

This blog presents a custom solution to overcome these limitations by automating the creation and management of Publication Lists using Salesforce CRM data, Apex Batch classes, and Cloud Pages. Our approach ensures subscribers are opted out of specific channels rather than globally, preserving engagement and compliance.

Problem Statement:

Managing customer communication preferences across multiple Business Units (BUs) in Salesforce Marketing Cloud (SFMC) can be challenging—especially when aiming to avoid global unsubscribes and maintain channel-specific control. To address this, we built a custom integration between Salesforce CRM and SFMC that automates Publication List creation and subscriber updates in real time.

Solution Approach

This solution leverages Salesforce Apex, SFMC Cloud Pages (SSJS), and SOAP API to automate Publication List management across multiple Business Units (BUs). Here’s how it works:

  1. Apex Batch Job in Salesforce CRM

    • Periodically scans CRM records for subscription channels, creates and updates.
    • Extracts relevant metadata and subscriber information.
    • Prepares a structured JSON payload for transmission.
  2. JSON Payload Sent to SFMC Cloud Page

    • The payload is securely sent to a Cloud Page endpoint hosted in SFMC.
    • This acts as the bridge between Salesforce and SFMC.
  3. Cloud Page Processes Data via SFMC SOAP API

    • The Cloud Page parses the incoming JSON.
    • Uses SFMC’s SOAP API to interact with Publication Lists and subscriber records.
  4. Publication List Creation/Update

    • If a Publication List doesn’t exist, it is automatically created in the respective business unit.
    • Existing lists are updated with new metadata or naming conventions as needed.
  5. Subscriber Record Update

    • Subscriber preferences are updated in the appropriate Publication Lists in real time.
    • Ensures channel-specific opt-in/opt-out control across multiple BUs.

Mogli

Steps to automate the creation of the Publication List

Step 1: Apex Batch Class in Salesforce CRM

The Apex Batch class retrieves CommSubscriptionChannelType records from Salesforce and constructs a JSON payload to initiate a callout to an SFMC Cloud Page. JSON payload containing:

  • List Name
  • MID (Business Unit ID)
  • List ID
  • RecordID
SAMPLE CODE
// Apex Batch Class to create a JSON payload 
public class CM_CommSubscriptionChannelTypeBatch implements Database.Batchable, Database.AllowsCallouts { 
    public void execute(Database.BatchableContext bc, List records) { 
        List<Map<String, Object>> payload = new List<Map<String, Object>>(); 
        for (CommSubscriptionChannelType csct : records) { 
            Map<String, Object> recordMap = new Map<String, Object>(); 
            recordMap.put('Name', csct.CM_Master_Segmentor_Name__c); 
            recordMap.put('MID', csct.CommunicationSubscription.CM_Business_Brand__r.OrgId); 
            recordMap.put('ID', csct.CM_Publication_List_Id__c); 
            recordMap.put('recordId', csct.Id); 
            payload.add(recordMap); 
        } 
        sendToMarketingCloud(JSON.serialize(payload)); 
    } 
}

Note: This code can be modify as per the requirement above code CM_Business_Brand__r is a custom lookup field with Business Brand Object where we store Business Unit (BU Id) and CM_Publication_List_Id__c is custom field where we are store Publication List ID.

Step 2: Send JSON Payload to SFMC Cloud Page to create/update Publication List
    1. Login to SFMC and go to the Web Studio -> CloudPages

      CloudPage

    2. Create a CloudPage in the parent BU will routes the payload data based on MID to their respective BU endpoint URL.
      SAMPLE CODE
      // Cloud Page Route the JSON payload based on MID
                  var rawPayload = Platform.Request.GetPostData(); 
                  var records = Platform.Function.ParseJSON(rawPayload); 
                  var urlMap = { 
                      "": "https://mc-bu1.sfmc-content.com/endpoint1", 
                      "": "https://mc-bu2.sfmc-content.com/endpoint2" 
                  }; 
                  for (var midKey in records) { 
                      var payload = Platform.Function.Stringify(records[midKey]); 
                      var targetUrl = urlMap[midKey] || defaultUrl; 
                      HTTP.Post(targetUrl, "application/json", payload); 
                  }        

      Note: This code can be modified as per the requirement.

    3. Create another CloudPage in the respective BU that handles and processes the data using WX Proxy to create/update the publication list in the Business Unit:
      • Checks if Publication List exists, will not create a duplicate.
      • Create a new Publication List if it doesn’t exist.
      • Update the Publication List if the List ID is not empty.
      SAMPLE CODE: JSON Payload received from CRM handler
      [
        { 
          "recordId": "0eBNS0000GGSR772", 
          "ID": "1255424", 
          "MID": "11000373", 
          "Name": "Newsletter" 
        } 
      ]
      SAMPLE CODE:
      // Code to parse JSON data and constructs publication list 
      var fetchData = Platform.Request.GetPostData(); 
      var jsonData = Platform.Function.ParseJSON(fetchData); 
      var api = new Script.Util.WSProxy();  
      for (var i = 0; i < jsonData.length; i++) { 
           var listName = jsonData[i].ListName; 
           var subscriberKey = jsonData[i].ContactId; 
           var status = jsonData[i].Status; 
           var email = jsonData[i].EmailAddress; 
       
          var listRetrieved = api.retrieve("List", ["ListName","ID"], {Property:"ListName",SimpleOperator:"equals",Value:listName}); 
           if (listRetrieved) { 
               var listId = listRetrieved.Results[0].ID; 
               var config = { 
                   SubscriberKey: subscriberKey, 
                   EmailAddress: email, 
                   Lists: [{ ID: listId, Status: status }] 
               }; 
               api.updateItem("Subscriber", config, {SaveOptions:[{PropertyName:"*",SaveAction:"UpdateAdd"}]}); 
           } 
      } 

      Note: If ID is null, then a new publication list will be created, and if ID is not null, then the existing list will be updated using the Publication list ID and MID (Business ID).

Steps to modify the records in the channel-specific Publication List

Step 1: Apex Batch class for updating records in the publication lists:

Create a batch class that will retrieve subscriber consent data and construct a payload to update records in their respective Publication Lists.

JSON payload containing:

      • List Name
      • MID (Business Unit ID)
      • Contact ID
      • Email Address
      • Status (‘Active’ or ‘Unsubscribed’)
Step 2: CloudPage to modify the consents in the Channel-specific Publication List
      1. Create a Cloud Page that receives JSON data and uses WSProxy to modify records in their respective publication list.
      2. It will search the Publication List by name.
      3. If matched, then update the subscriber status in that list.
      4. If the record does not exist, it will add the data to the list.

SAMPLE CODE: Upsert Record in the channel-specific Publication Lists

Platform.Load("Core", "1"); 

try { 

    var fetchData = Platform.Request.GetPostData();  // Receiving POST JSON from CRM 

    var jsonData = Platform.Function.ParseJSON(fetchData); 

    var api = new Script.Util.WSProxy(); 

    for (var i = 0; i < jsonData.length; i++) { var listName = jsonData[i].ListName; var subscriberKey = jsonData[i].ContactId; var status = jsonData[i].Status; var email = jsonData[i].EmailAddress; // Get List ID var listResult = api.retrieve("List", ["ListName", "ID"], { Property: "ListName", SimpleOperator: "equals", Value: listName }); if (listResult && listResult.Results.length > 0) { 
            var listID = listResult.Results[0].ID; 

            var subscriberObj = { 
                SubscriberKey: subscriberKey, 
                EmailAddress: email, 
                Lists: [{ ID: listID, Status: status }] 
            }; 

            var options = { 
                SaveOptions: [{ PropertyName: "*", SaveAction: "UpdateAdd" }] 
            }; 

            var result = api.updateItem("Subscriber", subscriberObj, options); 
            Write("
Success: " + Stringify(result)); 
        } else { 
            Write("
Error: List not found for " + listName); 
        } 
    } 

} catch (e) { 
    Write("Exception: " + e.message); 
} 

SAMPLE CODE: JSON Payload data:

[
  {
    "EmailAddress": "john.doe@example.com",
    "ContactId": "003B0000009KjF5",
    "Status": "Active",
    "ListName": "cm_preference_newsletter",
    "MID": "11000373"
  },
  {
    "EmailAddress": "jane.doe@example.com",
    "ContactId": "003B0000009KjF6",
    "Status": "Unsubscribed",
    "ListName": "cm_preference_updates",
    "MID": "11000374"
  }
]

Key Features of the Solution

      • MID-Based Routing: Ensures operations are performed in the correct BU.
      • Real-Time Sync: Immediate reflection of CRM preference changes in SFMC.
      • Automation: Eliminates manual Publication List management.
      • Compliance: Supports consent and preference management at scale.

Conclusion

This custom automation simplifies subscriber preference management by removing the need for manual Publication List handling in Marketing Cloud. With Salesforce CRM driving real-time creation and updates, and channel-specific lists supporting List-Unsubscribe headers, teams can ensure accurate, BU-specific communication preferences—enhancing compliance, scalability, and operational efficiency

If you like the content, please share and drop comments.

Leave a Reply