Thursday, 6 May 2021

Integration of Salesforce Marketing Cloud with Open Source CRM System using Rest API

INTRODUCTION

While working on one of the requirements for a transportation sector project for a client based out of Melbourne, Australia there was a requirement to import existing records\leads into the Salesforce Marketing Cloud from third party open source CRM system.

We also needed to provide specifications to add lead into SFMC on the fly from an external CRM system by doing necessary configuration. Given specifications are to be used by other CRM team to insert record from CRM to SFMC on the fly. Below are the detailed steps we need to follow:

APPROACH

1) For importing existing data into Salesforce Marketing Cloud, we have created Data Extension and fields into SFMC and used CSV Import.

2) For integration with third party system, Marketing Cloud provides two APIs that uses OAuth 2 authentication mechanism:
  • The REST API gives you more access to Marketing Cloud features.
  • The SOAP API gives you complete access to most email features.

In this scenario, we have used REST API to integrate with external system. 

Marketing Cloud administrator user is required to perform the actions needed for configuration. Following steps are executed in SFMC.

  • Log in as Marketing Cloud Administrator user in SFMC.
  • Go to Setup -> Apps ->Installed Packages.
  • Click New to create a new package.
  • In the New Package Details window, enter the Name and Description of the package.
  • Click on the Save button.
  • Click on Add Component and will open one pop-up window for setup.
  • In pop-up, select API Integration and click Next. 

  • Select Server-to-Serveras Integration type then click Next.
  • Select required permission for API and click the Save button. Permission can be updated in future, if required.
  • Copy Client Id, Client secret, Authentication URL, and REST URL which will be used for API integration in future steps.

  • Getting Access Token
    • Example Request:
      • Authentication endpoint can be retrieved from the above step.
      • Endpointhttps://YOUR_SUBDOMAIN.auth.marketingcloudapis.com/v2/token
      • Method: POST
      • Content-Type: application/json
    • Request Body:
  • {
    "grant_type": "client_credentials",
    "client_id": "7a9j47upktedde30uedl822p",
    "client_secret": "195527892567524157"
    • Example Response : HTTP/1.1 200 OK
    • Response Body:
  • {
    "access_token":"eyJhbLciOiJIPzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIifQ.eyJhY2Nlc3NfdG9rZW4iOiJhYmJUQTlpSHZqRjkyd3Jkb0xWZEFCaloiLCJjbGllbnRfaWQiOiI3ZTRmYW1xaWUzcWtzdzlhNDRrcmxvZDgiLCJlaWQiOjEwNzU3Njc2LCJzdGFja19rZXkiOiJRQTFTMSJ9.wSFfEdeNrkoiU_tnmJ2ihm8iUqnJKlZoI3GlavTGBhs.hU4EsiC1e9txh_TCt90YlI2l7xZZ5E6_oa0xku3Jj9CCk1B72M4bhO3kUIyhwfVuB0MFbL0y9KD_RRFzg-nuqPgjPyONnby-iWopdZPBHd-3woupxCMST5-vfJO9qAED9qiUfYLS4WmHRuJTCX4NPScyu8BdROTVEe-D3iAoAeFoJX_rLZ9d5eEhIn1AvkYgoj9siuxAprHEvmySTgNIXkQA6uT_IQ-H1dbfOyJmlFKpYzvhvHb0KH7NJ24zy5bd2MQ5",
    "expires_in":1080,
    "token_type":"Bearer",
    "rest_instance_url":"mc563885gzs27c5t9-3k636tzgm.rest.marketingcloudapis.com",
    "soap_instance_url":"mc563885gzs27c5t9-63k636tzgm.soap.marketingcloudapis.com",
    "scope": "email_read email_write email_send"
    
    }

  • Upsert records in Data Extension: 
    • In a single synchronous call, the API upserts multiple data extension rows. The maximum size of a payload is 5 megabytes. We can post a maximum of 50 fields and 50 records at a time as a best practice.
    • Example Request: 
      • Host:https://YOUR_SUBDOMAIN.rest.marketingcloudapis.com/hub/v1/dataeventsasync/key:YOUR_KEY/rowset
      • Method: POST
      • Content-Type: application/json
      • Authorization: Bearer YOUR_ACCESS_TOKEN
    • Request Body 

  • [ { "keys":{ "Email": "someone@example.com"}, "values":{ "LastLogin": "2013-05-23T14:32:00Z", "IsActive": true, "FirstName": "John", "FollowerCount": 2, "LastName": "Smith" } }, { "keys": {"Email": "someone2@example.com"}, "values":{ "LastLogin": "2013-05-23T14:32:00Z", "IsActive": true, "FirstName": "Jane", "FollowerCount": 2, "LastName": "Smith" } } ]
    • Example Response : HTTP/1.1 200 OK
    • Response Body:
  • [
         {
             "keys": {
                     "Email": "someone@example.com"
                     },
             "values": {
                     "LastLogin": "2013-05-23T14:32:00Z",
                     "IsActive": true,
                     "FirstName": "John",
                     "FollowerCount": 2,
                     "LastName": "Smith"
                     }
         },
         {
             "keys": {
                     "Email": "someone2@example.com"
                     },
             "values": {
                     "LastLogin": "2013-05-23T14:32:00Z",
                     "IsActive": true,
                     "FirstName": "Jane",
                     "FollowerCount": 2,
                     "LastName": "Smith"
                     }
         }
     
  • API returns a 400 response for an an invalid request with details on the error.

SUMMARY
Using Salesforce Marketing cloud APIs, third party open source CRM can be integrated with SFMC. APIs and its sample responses are used to add records on the fly into SFMC. For further details you can refer to this Link!

If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 29 April 2021

[SFMC Email Campaign]: Emails Send to Data Extension Not Delivered to all Subscribers

INTRODUCTION

While working on one of the requirements for a transportation sector project for a client based out of Melbourne, Australia, there was a requirement to run an Email Campaign using an automation studio with data source as DataExtension in Salesforce Marketing Cloud.


We've used SQL Query activity in automation studio to get filtered leads from DataExtension based on filter criteria (e.g. Date Range, Status etc.) and add it to another filtered DataExtension. Then the filtered DataExtension is used as a data source in Email Activity to run campaign. 


Challenge

The campaign ran successfully at the specified time, but looking at the SFMC Email Tracking, we have found that none of the emails was sent out even though all the records were having proper emails. 


Digging out in detail, we have found that due to Email field of data extension has a text data type.

 


APPROACH\RESOLUTION

First, we have removed the email field having text data type from filtered data extension. Then we have added new email field with Email data type and ran the campaign. This time, we were able to see emails sent out in SFMC email tracking.


Marketing Cloud uses the Subscriber Relationship to see a subscriber exists in All Subscribers and uses the email address field from the All Subscribers List as email address where email to be sent out.


If a subscriber does not exist in the All Subscribers List, OR there are several fields with an EmailAddress data type in the DataExtension, OR there isn't any field with an EmailAddress data type in the Data Extension, then system does not know which email address to use and will not send to those subscribers.


To ensure proper email delivery, we have to modify the DataExtension so that there should be only one field with an EmailAddress data type, and it should have the desired email address to be sent. 

If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 22 April 2021

Introduction to Workflow Rule in Salesforce

INTRODUCTION

The workflow rule in Salesforce is one kind of process automation that basically contains the business logic that automates certain actions based on particular criteria.

What are the benefits of using Workflow Rules?

In a workflow rule, you get one set of criteria with multiple conditions and then a list of actions to run once the criteria is met. Being able to ‘chain’ multiple sets of criteria provides for if-then-else logic to easily build complex business processes. With traditional workflow rules, you would need several different rules to catch the different scenarios in your requirements. This can make complex business processes difficult to maintain.

There are endless ways you can use workflows, but of course, it is easier to illustrate what is a workflow in Salesforce with some examples. Mentioning the specific things that can be performed, you may automate the following 4 workflow actions in Salesforce:
  • creating tasks
  • Updating fields
  • Sending email alerts
  • Sending outbound messages

How to create workflow rules?
  • From Setup, enter the workflow rules in the Quick Find box, then select Workflow Rules.
  • Click New Rule.
  • Choose the object to which you want this workflow rule to apply.
  • Click Next.
  • Give the rule a name and description.
  • Set the evaluation criteria.(As per your requirement)
    • Created – Evaluate the rule criteria each time a record is created. If the rule criteria is met, run the rule. Ignore all updates to existing records.
    • Created, and every time it’s edited –Evaluate the rule criteria each time a record is created or updated. If the rule criteria is met, run the rule.
    • Created, and any time it’s edited to subsequently meet criteria - (Default) Evaluate the rule criteria each time a record is created or updated.


  • Enter your rule criteria.
  • Click Save & Next.

What are the Workflow Actions?

Email Alert: Email alerts are emails generated by an automated process and sent to designated recipients. These actions consist of the standard text and list of recipients for an email.

Field Update: A common use case of workflow rules is the function to overwrite field values with new data. This is, perhaps, the most frequently used action but please note that it won’t work in the cross-object case.

Outbound Message: An outbound message sends information to a designated endpoint, like an external service. You configure outbound messages from Setup. Provide the external endpoint and create a listener for the messages using the SOAP API. You can associate outbound messages with workflow rules, approval processes, or entitlement processes.

Task Creation: Task actions determine the details of an assignment given to a specified user by an automated process. You can associate task actions with workflow rules, approval processes, or entitlement processes.

How to Add Time-Dependent Action in Workflow Rule?
  • Open a workflow rule.
  • In the Time-Dependent Workflow Actions section, click Add Time Trigger.
  • Specify the number of days or hours before or after a date that’s relevant to the record, such as the date the record was created. Refer below image for better understanding.


  • Save your time trigger.
  • In the section for the time trigger you created, click Add Workflow Action.
  • Select one of the options to create an action or select an existing one.
  • Click Done.
If the workflow rule is still active and valid when this time occurs, the time trigger fires the workflow action.

Limitations of Workflow Rules:
  • You can’t add a time trigger if:
    • The evaluation criteria are set to evaluate the rule when a record is: Created, and any time it’s edited to subsequently, meet criteria.
    • The rule is activated.
    • The rule is deactivated but has pending actions in the workflow queue.
  • Please refer to the link for limitations.

SUMMARY

As illustrated, Salesforce Workflow is a much better and more efficient tool that let Salesforce admin creates business solutions with no code and with few clicks. So, we can easily perform some actions like email send, field update, etc.
        
    If you have any questions you can reach out to our Salesforce Consulting team here.

    Thursday, 15 April 2021

    Mass\Bulk Operations in Salesforce using Batch Apex

    INTRODUCTION/SCENARIO

    There was a requirement for Mass/bulk Update of  more than thousands of records on the particular object based on certain action on object for one of our clients - consulting firm based out of Atlanta, GA, USA.

    We, all are familiar with updating records using Apex class but updating more than a thousand records or fire DML on thousands of rows on particular objects is very complex in Salesforce and it does not allow you to operate on more than a certain number of records which satisfy the Governor limits.

    But for medium to large enterprises, it is essential to manage thousands of records every day. Adding/Updating/Deleting them when needed. Salesforce has come up with a powerful concept called Batch Apex. It allows you to handle a thousand number of records and manipulates them by using a specific syntax.

    APPROACH
    For updating the thousand of records we need to develop the Batch Apex class which can mass update the records on the particular object. As Batch Apex operates over small batches of records, covering your entire record set and breaking the processing down to manageable chunks of data.

    PROCESS
    We have developed a custom global apex class that extends Database.Batchable interface because Salesforce compiler will come to know, this class incorporates batch jobs. Below is a sample class that is designed to update all the records of Account object (Let's say your organization contains more than 50 thousand records and you want to mass update all of them). 

    batch class
    After using the code, we have to go to Developer Console and click on Debug and then Open Execute Anonymous Window & Enter the following code in the box and click on Execute to see the result.
    summary
    Using Batch Apex, we can perform Adding/Updating/Deleting of thousands number of records for a particular object. As Batch Apex is asynchronous execution of Apex code, specially designed for processing a large number of records and has greater flexibility in governor limits than the synchronous code.

    If you have any questions you can reach out our Salesforce Consulting team here.

    Thursday, 8 April 2021

    Salesforce Lightning TreeGrid with pagination

    INTRODUCTION/SCENARIO

    While working on one of the user stories of Pharma sector for a client based on Chicago, USA; there was a requirement to display Contact records under their parent account record. Also, pagination was needed with a picklist to choose number of records to be displayed on a page.

    CHALLENGE

    Lightning:treeGrid is useful component for displaying structured data such as hierarchy or forecasting data while dealing with the same object. But there is no functionality available to display records of 2 different objects (In our case, Account and Contact) in the same table while using Lightning:treeGrid.

    APPROACH / SOLUTION

    To overcome this limitation, I've implemented custom Javascript in the lightning component containing the mechanism to have customized list of Accounts and Contacts with pagination.

    Note: This component requires API version 42.0 and later.

    treeGridController is a controller class of the lightning components treeGrid utilized to fetch Account object data which has having child Contact records.

    treeGridController.apxc
    public class treeGridController {
        
        @AuraEnabled
        public static List <Account> getAccountList() {
            return [Select Id, Name,
                        (SELECT Name, Phone, Email FROM Contacts) 
                        From Account
                        Where Id IN (Select AccountId From Contact)
                        ORDER BY Name ASC];
        }
    }
    

    Below are Component & JavaScript files for the reference which i've used to meet the requirement..

    treeGrid.cmp
    <aura:component controller="treeGridController" implements="flexipage:availableForAllPageTypes" >
        <aura:attribute name="resultData" type="Object" access="private"/>
        <aura:attribute name="gridColumns" type="List" />
        <aura:attribute name="gridData" type="Object" />
        <aura:attribute name="gridExpandedRows" type="Object" />
        <aura:attribute name="PageNumber" type="Integer" />
        <aura:attribute name="TotalPages" type="Integer"/>
        <aura:attribute name="currentPage" type="Integer" default="0" />
        <aura:attribute name="limit" type="Integer" default="5" />
        <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
        <div class="slds-page-header" role="banner">
            <ui:inputSelect aura:id="pageSize" label="Display Records Per Page: " change="{!c.onSelectChange}">
                <ui:inputSelectOption label="5" text="5" value="true"/>
                <ui:inputSelectOption label="10" text="10"/>
                <ui:inputSelectOption label="50" text="50"/>
            </ui:inputSelect>
        </div>
        <lightning:treeGrid aura:id="accTree"
                            columns="{!v.gridColumns}"
                            data="{!v.gridData}"
                            expandedRows="{!v.gridExpandedRows}"
                            keyField="Id"
                            hideCheckboxColumn = "true"
                            />
        <div class="slds-clearfix">
            <div class="slds-page-header" role="banner">
                <div class="slds-float_right">            
                    <lightning:button disabled="{!v.PageNumber == 1}" variant="brand" aura:id="prevPage" label="Prev" onclick="{!c.handlePrev}" />            
                    <lightning:button disabled="{!v.PageNumber == v.TotalPages}" aura:id="nextPage" variant="brand" label="Next" onclick="{!c.handleNext}"/>
                </div>
                <p class="slds-page-header__title">Page {!v.PageNumber} of {!v.TotalPages}</p>
            </div>
        </div>
    </aura:component>
    

    treeGridController.js
    ({
        doInit : function(component, event, helper) {
            var columns = [
                {
                    type: 'url',
                    fieldName: 'AccountURL',
                    label: 'Account Name',
                    typeAttributes: {
                        label: { fieldName: 'accountName' }
                    }
                },
                {
                    type: 'text',
                    fieldName: 'Name',
                    label: 'Contact Name'
                },
                {
                    type: 'phone',
                    fieldName: 'Phone',
                    label: 'Phone Number'
                },
                {
                    type: 'email',
                    fieldName: 'Email',
                    label: 'Email'
                }
            ];
            component.set('v.gridColumns', columns);
            var action = component.get("c.getAccountList");
            action.setCallback(this, function(response){
                var state = response.getState();
                if (state === "SUCCESS" ) {
                    var resultData = response.getReturnValue();
                    component.set('v.resultData', resultData);
                    helper.bindTableData(component, event);
                }
            });
            $A.enqueueAction(action);
        },
        handleNext : function(component, event, helper){
            component.set('v.currentPage',component.get('v.currentPage')+1);
            helper.buildTable(component, event);
        },
        handlePrev : function(component, event, helper){
            component.set('v.currentPage',component.get('v.currentPage')-1);
            helper.buildTable(component, event);
        },
        onSelectChange : function(component, event, helper){
            component.set('v.currentPage',0);
            var pageSize = component.find('pageSize').get('v.value');
            component.set('v.limit',pageSize);
            helper.buildTable(component, event);
        }
    })
    

    treeGridHelper.js
    ({
        bindTableData : function(component, event) {
            var resultData = component.get('v.resultData');
            for (var i=0; i<resultData.length; i++ ) {
                resultData[i].accountName = resultData[i]['Name'];
                delete resultData[i]['Name'];
                resultData[i]._children = resultData[i]['Contacts'];
                delete resultData[i].Contacts;
                resultData[i].AccountURL = '/'+resultData[i].Id;                
            }
            component.set('v.resultData',resultData);
            this.buildTable(component, event);
        },
        buildTable : function(component, event){
            var resultData = component.get('v.resultData');
            var limit = component.get('v.limit');
            var currentPage = component.get('v.currentPage');
            var totalPage = Math.ceil(resultData.length / limit);
            var startIndex = currentPage * limit;
            var row = [];
            var expandedRows = [];        
            for (var i = startIndex; i < parseInt(startIndex)+parseInt(limit); i++) {
                if(resultData[i]){
                    expandedRows.push(resultData[i].Id);
                    row.push(resultData[i]);
                }
            }
            component.set('v.gridData', row);
            component.set('v.PageNumber',currentPage+1);
            component.set('v.TotalPages',totalPage);
            component.set('v.gridExpandedRows', expandedRows);
        }
    })
    

    OUTPUT: 


    If you have any questions you can reach out our Salesforce Consulting team here.