Blogs

Asynchronous Apex Triggers and Change Data Capture

With any synchronous execution in salesforce we have multiple governor limits e.g. CPU time limit or Heap Size or SOQL limit or DML limit etc that our business logic need to respect. Sometimes the business process become such complex that we use Asynchronous transactions such as Future methods, Queuable Apex and Batch Apex etc to bypass the limits with synchronous processing.

In addition to above, Asynchronous transaction mechanism, Salesforce has introduced Asynchronous Apex Triggers in  Summer ’19.

Async Apex Trigger is nothing but the change event trigger which runs asynchronously after a Database transaction is done. Similar to regular apex trigger but it has only After insert event on change event object instead of SObject.  

For standard SObject, the change event object is SObject Type + ChangeEvent. For Example LeadChangeEvent.

For Custom SObject, it is Custom Object Name+ “__ChangeEvent”.

Async Apex trigger works along with Change Data Capture. In order to use async trigger, first change data capture to be enabled for that particular SObject.

Setup -> Integrations -> Change Data capture

Change Data Capture is one of the offerings from Salesforce Streaming API capabilities like Platform Events. Unlike Platform events, it has transactional boundaries.  CDC notification gets generated when CREATE, UPDATE, DELETE or UNDELETE operation performed on a SObject record.

Also we don’t have to create the event schema model like platform events, it comes out of the box.

Use Cases:

The resource intensive and non-transactional processes could be executed asynchronously in order to reduce transaction time and consumption of resources.

I have considered a simple use case here. When a Lead gets qualified, a task need to be generated and gets assigned to a Sales or Call Center agent to do follow-up on those leads for conversion.

Task creation is not necessarily to be synchronous, it could be after the transaction is completed.

Let’s get into the Code.

Here I have used the trigger framework by http://chrisaldridge.com/triggers/lightweight-apex-trigger-framework/

LeadChangeAsyncTrigger trigger on LeadChangeEvent :

trigger LeadChangeAsyncTrigger on LeadChangeEvent (after insert) {
  // Call the trigger dispatcher and pass it an instance of the LeadAsyncTriggerHandler and Trigger.opperationType
  TriggerDispatcher.Run(new LeadAsyncTriggerHandler(), Trigger.operationType);
}

Depending on the trigger operation type the dispatcher will pass trigger context variable to the trigger handler

TriggerDispatcher.cls :


public class TriggerDispatcher {
  /*
    Call this method from your trigger, passing in an instance of a trigger handler which implements ITriggerHandler.
    This method will fire the appropriate methods on the handler depending on the trigger context.
  */
  public static void Run(ITriggerHandler handler, System.TriggerOperation triggerEvent)
  {
    // Detect the current trigger context and fire the relevant methods on the trigger handler:
  
    switch on triggerEvent {
      when BEFORE_INSERT {
        handler.BeforeInsert(trigger.new);
      } when BEFORE_UPDATE {
        handler.BeforeUpdate(trigger.newMap, trigger.oldMap);
      } when BEFORE_DELETE {
        handler.BeforeDelete(trigger.oldMap);
      } when AFTER_INSERT {
        handler.AfterInsert(Trigger.new);
      } when AFTER_UPDATE {
        handler.AfterUpdate(trigger.newMap, trigger.oldMap);
      } when AFTER_DELETE {
        handler.AfterDelete(trigger.oldMap);
      } when AFTER_UNDELETE {
        handler.AfterUndelete(trigger.oldMap);
      }
    }  
  }
  public class TriggerException extends Exception {}
}

ITriggerHandler interface as part of the trigger framework :

public interface ITriggerHandler {
	
  void BeforeInsert(SObject[] newItems);

  void BeforeUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems);

  void BeforeDelete(Map<Id, SObject> oldItems);

  void AfterInsert(SObject[] newItems);

  void AfterUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems);

  void AfterDelete(Map<Id, SObject> oldItems);

  void AfterUndelete(Map<Id, SObject> oldItems);
    
}

LeadAsyncTriggerHandler .cls :

public class LeadAsyncTriggerHandler implements ITriggerHandler {
  public void BeforeInsert(List<SObject> newItems) {}
  public void BeforeUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {
  }
  public void BeforeDelete(Map<Id, SObject> oldItems) {}
  public void AfterInsert(List<SObject> newItems) {
    LeadAsyncService.createTasksForQualifiedLeads(newItems); 
  }
  public void AfterUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {}
  public void AfterDelete(Map<Id, SObject> oldItems) {}
  public void AfterUndelete(Map<Id, SObject> oldItems) {}  
}

LeadAsyncService .cls implements the asynchronous processing logic :


Public with sharing class LeadAsyncService {

  public static void createTasksForQualifiedLeads(List <LeadChangeEvent> leadChangeEvents) {
    List <Task> tasksTobeInserted = checkLeadStatus(leadChangeEvents);
    createTasks(tasksTobeInserted);
  }

  public static List<Task> checkLeadStatus(List <LeadChangeEvent> leadChangeEvents) {
    List<Task> tasks = new List<Task>();
    for(LeadChangeEvent leadEvent : leadChangeEvents) {  
      system.debug('leadEvent is:'+ leadEvent);     
      EventBus.ChangeEventHeader eventHeader = leadEvent.ChangeEventHeader;
      if(eventHeader.changetype == 'UPDATE' || eventHeader.changetype == 'CREATE') {
        if(leadEvent.status == 'Qualified') {
          Task task = new Task();
          task.ownerId = eventHeader.CommitUser;
          task.subject = 'Lead needs to be followed up';
          task.whoId = eventHeader.recordIds[0];
          task.ActivityDate = System.Today() + 3;
          tasks.add(task);
        }
      }          
    }
    return tasks;
  }
  
  public static void createTasks(List<Task> tasksTobeInserted) {
    system.debug('tasksTobeInserted is:'+ tasksTobeInserted);
    if(!tasksTobeInserted.isEmpty()) {
      insert tasksTobeInserted;
    }
  }
}

Test class for asynchronous processing logic :

@isTest
Public class TestLeadChangeTrigger {
  @isTest
  static void testCreateLead() {
    //Enable all Change Data Capture entities for notifications only for Test. 
    Test.enableChangeDataCapture();
    Lead testLead = new Lead(LastName='Test Lead', Company='Test Comp', Status = 'Qualified');
    insert testLead;
    //To fire the trigger and deliver the test change event.
    Test.getEventBus().deliver();
    Task[] tasks = [Select id from Task];
    System.assertEquals(1, tasks.size(), 'The change event trigger did not create the expected task.') ;   
  }
  @isTest
  static void testCreateAndUpdateLead() {
    //Enable all Change Data Capture entities for notifications only for Test. 
    Test.enableChangeDataCapture();
    Lead testLead = new Lead(LastName='Test Lead', Company='Test Comp', Status = 'Working - Contacted');
    insert testLead;
    //To fire the trigger and deliver the test change event.
    Test.getEventBus().deliver();
    Lead[] testLeads = [Select Id, Status from Lead];
    testLeads[0].Status = 'Qualified';
    Update testLeads;
    Test.getEventBus().deliver();
    Task[] tasks = [Select id from Task];
    System.assertEquals(1, tasks.size(), 'The change event trigger did not create the expected task.');
  }


}

Implementing An End-to-End Scenario in SFMC Web Studio

I wanted to write a blog about Cloud Pages and basic AMPScripts covering an end to end Use Case, as I could not find something similar when I initially started working in SFMC.

Cloud Pages are HTML pages hosted in the SFMC server and support the following scripting languages:

  • Client Side – Javascript, JQuery, any framework built on Javascript
  • Server Side – AMPScript, Server Side JavaScript(SSJS)

I have taken a very basic and very frequent use case to create a landing page to be used as a basic subscription form for the customers. My example works for a setup with Marketing Cloud Connector, submitting the form will Create/Update data in Salesforce. The logic can be easily updated if the subscriber data has to reside in SFMC Data Extensions or Any other supported CRM or Database. Please note, AMPScript methods for Salesforce will only work for a SFMC instance connected to Salesforce.

I will breakdown the code to explain a bit, and provide a link to the file in GitHub.

AMPScript Logic:

  1. It basically captures the Below Input Values:
  • Email (Required, since you definitely need email to be able to send newsletter based on the subscription)
  • First Name
  • Last Name

2. Checks if a Person Account (Contact) already exists in Salesforce with the provided email. Based on the result, it either creates a new Person Account (Contact) record in Salesforce with the provided values or updates the Person Account (Contact) record in Salesforce. I have added additional comments to explain the important parts of the code.

    %%[
      //The logic will execute in page load as well as after the form submission, since it has no conditions
      //Variable Declaration
      var @firstName, @lastName, @email, @submitted, @subscriberRows, @subscriberRow, @UpdateAccount, @NewAccount, @RecordTypeId, @PersonAccountId
      //Variable Initialization, you can also initialize the variables in declaration
      set @firstName = ""
      set @lastName = ""
      set @email = ""
      set @submitted = "false"
      SET @RecordTypeId = "xxxxxxxxxxxxxxxxxx" //This variable will be used later on the creation of the Account record. Make sure you use RecordtypeID in your create calls when you have person account enabled
      //IF ELSE block in AMPScript
      //The below code block executes when we submit the form, as the value of submitted variable is set to true on the button click. 
      IF RequestParameter("submitted") == "true" THEN
        SET @email = QUERYPARAMETER('email')
        SET @firstName = QUERYPARAMETER('firstName')
        SET @lastName = QUERYPARAMETER('lastName')
        //RetrieveSalesforceObjects is used to query salesforce objects. The first parameter is the name of the object, the second parameter is the name of the fields to be returned, third parameter is the where condition. You can include more than one condition , but all of them will be evaluated with AND operator
        SET @subscriberRows = RetrieveSalesforceObjects(
          "Account",
          "Id,FirstName,LastName,Subscribed__pc",
          "PersonEmail", "=", @email )
        // Condition to check if the query has returned any rows
        IF RowCount(@subscriberRows) >= 1 THEN
          //getting the first record from the returned records
          SET @subscriberRow = Row(@subscriberRows, 1)
          // getting a specific field from the row
          SET @PersonAccountId = Field(@subscriberRow, "Id")
          //UpdateSingleSalesforceObject is used to update a single salesforce record. First parameter is the name of the object. Second parameter is the id of the record to be updated. After it the fields to be updated should be included in name value pairs.
          SET @UpdateAccount = UpdateSingleSalesforceObject('Account',@PersonAccountId,'Subscribed__pc',"true","FirstName", @firstname,"LastName", @lastname)
        ELSE
          //The else block is executed when no matching rows are found on Account object. It is going to create a new Account record with CreateSalesforceObject call. The first parameter is name of the object, second parameter is number of fields in your insert call. After that we have the fields in name value pair. if successful the call will return the id of the newly created record.
          SET @NewAccount = CreateSalesforceObject(
                  "Account",4,
                  "FirstName", @firstname,
                  "LastName", @lastname,
                  "PersonEmail", @Email,
                  "Subscribed__pc","true",
                  "RecordTypeId", @RecordTypeId)
        ENDIF
        SET @submitted = "true"
      ENDIF
    ]%%

SSJS : I have included the above AMPScript block inside a Server Side Javascript block to be able to capture any exceptions from the AMPScript Execution.

<!-- Server Side Javascript, we have a try catch block to print the errors from the AMPScript Execution -->
      <script runat="server">
        //Loads the the Core server-side JavaScript library
        Platform.Load("core","1");
        try {
      </script>
      <!-- Insert AMPSCript Here -->
      <script runat="server">
        }
        //catch block will catch any error from the AMPScript execution
        catch (err) {
            Write("Error Message: " + Stringify(err.message) +        Stringify(err.description));
        }
    </script>

AMPScript can also be included within HTML. You can render specific part of the page using IF ELSE block. These are useful, to display confirmation message to the customers after they submit the form

%%[ IF @submitted == "true" THEN ]%%
        <div id="successDiv">
          <span>Thank you for submitting your details.</span>
        </div>
      %%[ ENDIF ]%%

Link to Github : Repo

Thank you for reading our blog. I will try to write next blog about incorporating the cloud pages into another website, and also passing parameters from the parent html. This is a frequent use case, when a company is launching a new campaign, and would like to capture the customer’s response to the campaign from their existing website.

Stay Safe.

Configure Marketing Cloud Connector

Marketing Cloud Connector is a tool from Salesforce to integrate Salesforce CRM and Marketing Cloud using a declarative approach. Salesforce has provided a very detailed documentation on how to configure Marketing Cloud Connector. We have summarized the steps to be followed in order to complete the setup.

Prerequisites:

  1. Four custom tabs should be available in your Salesforce instance
  2. One available user license each in Salesforce and Marketing Cloud
  3. Subscriber key must be enabled in your marketing cloud instance.
  4. Platform Events must be enabled in your Salesforce instance

Configuration Steps in Sequence:

  1. Install Connected App:https://sfdc.co/MCC
  2. Page Layout Updates: Update the following object layout, to include Marketing cloud components (Fields, related lists, visual force components).
  3. ObjectFieldsRelated ListsVisualforce Components
    User1. Marketing Cloud for Ap-pExchange Admin
    2. Marketing Cloud for Ap-pExchange User
    NANA
    ContactNA1. Email Sends
    2. Individual Email Results
    ContactActions
    LeadNA1. Email Sends
    2. Individual Email Results
    LeadActions
  4. Enable “Marketing Cloud for AppExchange Admin” and “Marketing Cloud for AppExchange User” for your user.
  5. Salesforce System User – Create a new user as follows (you can reuse an existing admin user based on your license availability.)
    1. User License: Salesforce
    2. Profile: System Administrator
    3. Select Marketing Cloud for AppExchange, Marketing Cloud for AppExchange
  6. Create a new permission set, which will later be used with the connected app to connect SFMC and SFDC. Let’s call this permission set “Marketing Cloud Connected App”. Assign this permission set to the system user you have created in the previous step.
  7. Make sure your marketing cloud connect user(s) have edit access to “Email Opt Out” field on contact and lead object. You can either enable it for the system admin profile or create a new permission set and assign this permission set to all marketing cloud connect users.
  8. Configuring the Connection in Marketing Cloud
    1. In SFMC setup page, click Apps -> Salesforce Integration
    2. Based on your required data access configuration either select or deselect Scope by User. More details about this option can be found here.
    3. After clicking Connect Account, and confirming the popup, you will be redirected to a new login window in salesforce. Login using the System user credentials you have set up earlier. Allow to grant access to Marketing cloud to connect to Salesforce CRM.
  9. Connected App Setup in Salesforce:
    1. Click “Edit Policies” on Salesforce Marketing Cloud connected app. Make the following changes and click save.
      1. Permitted Users – Admin approved users are pre-authorized
      2. IP Relaxation – Relax IP restrictions
      3. Refresh Token Policy – Immediately expire refresh token
    2. Assign the permission set (“Marketing Cloud Connected App”) you created earlier to the connected app.
  10. Check the default workflow user in Process Automation in Salesforce setup. If it is empty assign a user with System Administrator profile. You can use the System User you have created earlier.
  11. Open Session Settings in Salesforce setup and click on save without changing anything.
  12. Create Marketing Cloud API User: Create a new user in Marketing cloud with selecting API User to true and assign the following roles to it.
    1. Administrator
    2. Marketing Cloud Administrator.
      Logout from Marketing Cloud.
  13. In Salesforce, open the “Marketing Cloud” tab. It will ask for remote site verification, go ahead and complete it. Click on “Start Wizard” and acknowledge all 4 steps you have completed earlier. Select “I Agree” and click “Configure Marketing Cloud Connector”. Click Okay to finish the wizard on the configuration summary screen.
  14. Open the “Marketing Cloud” tab and click on “Connect to Marketing Cloud”. In the next screen login using credentials of Marketing Cloud API user you have created earlier. The next screen will let you configure Marketing Cloud Connect settings. Configure the settings based on your organization requirements. You can enable and configure Campaign Member tracking if it is required for your organization in the Marketing Cloud Connect settings page. Click “Save Settings”, on the next screen select the Business unit you want to configure with Marketing Cloud Connect and click Save.
  15. Login to Marketing Cloud. Open the user record of the API User. Click Integrate next to Salesforce.com Status field. Enter the username of the Salesforce System User and click on Save Settings. The status will change to Integrated.
  16. The Connector configuration is now completed. To test the connection, send a test email to a report of Lead or Contact record from Marketing Cloud.

Thank you for reading. Please drop your suggestion/feedback/questions on the comment box below.

Stay Safe.

Marketing Cloud Connector : Trailhead

Marketing Cloud Connector : Salesforce Help Document

Integrate External Services using Lightning Flow

As you know there are various ways of invoking the external APIs or Services. In Almost all integration techniques, we need a bit of code. In this post we are going to discuss how integration to external systems from Salesforce can be accomplished without writing code. This is a complete declarative approch of doing callouts from Salesforce.

Use Case:

Let’s assume there is a external Order management application.

Scenario 1: When a customer contacts call center agent to inquire about his/her order, the agent will fetch the order status from this external service and inform the customer. Scenario 2: When agent/back office user updates the order status to “Ready to be Delivered” in SFDC, integration process will update the Order status in External application.

Steps to be followed to achieve this:

  1. Schema Definition of External Service
  2. Create Named Credential
  3. Register External Service
  4. Use External Service in Flow

Schema Definition for External Service

Schema the specification of the rest based API of the External Services Provider. It includes :

  1. Logical structure of endpoints
  2. Authentication Parameters
  3. HTTP method name such as GET, POST
  4. Request and Response body
  5. Error/Exception information.

 Interagent hyper-schema using JSON or Swagger Open API 2.0 can be used to create or update schema

Here, I have used https://app.mocklab.io/ as the external API server to mock the Schema and APIs. Both the Schema and APIs are hosted in this mock server. Below code block is the schema definition which is accessible via https://milan.mocklab.io/orders/schema. You can create your own account in mocklab and use it (14 days trial period). If you want to use some existing schema definition, you can use https://petstore.swagger.io/v2/swagger.json

{
  "swagger": "2.0",
  "host": "milan.mocklab.io",
  "basePath": "/",
  "info": {
    "version": "1.0",
    "title": "External Service for OrderManagement",
    "description": "### External Service for Order Management",
    "x-vcap-service-name": "OrderManagementRestServices"
  },
  "securityDefinitions": {
    "basicAuth": {
      "type": "basic"
    }
  },
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {
      "name": "OrderManagementRestServices"
    }
  ],
  "paths": {
    "/orders/{orderNumber}": {
      "get": {
        "operationId": "getOrder",
        "summary": "Retrieves an Order",
        "description": "Retrieves the Order with specific order number",
        "consumes": [
          "text/plain"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "orderNumber",
            "in": "path",
            "required": true,
            "type": "string",
            "description": "OrderNumber of the order"
          }
        ],
        "responses": {
          "200": {
            "description": "The response when system finds an Order with given orderNumber",
            "schema": {
              "$ref": "#/definitions/orderDetails"
            }
          },
          "400": {
            "description": "Error response if the order number parameter is less than minimum characters",
            "schema": {
              "$ref": "#/definitions/errorModel"
            }
          },
          "404": {
            "description": "Error response if the order is not supported by service or order is not found",
            "schema": {
              "$ref": "#/definitions/errorModel"
            }
          }
        }
      },
      "put": {
        "operationId": "updateOrder",
        "summary": "Updates an Order",
        "description": "Updates the Order with specified status",
        "consumes": [
          "text/plain"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "orderNumber",
            "in": "path",
            "required": true,
            "type": "string",
            "description": "OrderNumber of the Order"
          },
          {
            "name": "orderStatus",
            "in": "query",
            "required": true,
            "type": "string",
            "description": "The status of order"
          }
        ],
        "responses": {
          "200": {
            "description": "The response when system finds an Order with given orderNumber",
            "schema": {
              "$ref": "#/definitions/orderDetails"
            }
          },
          "400": {
            "description": "Error response if the order number parameter is less than minimum characters",
            "schema": {
              "$ref": "#/definitions/errorModel"
            }
          },
          "404": {
            "description": "Error response if the order is not supported by service or order is not found",
            "schema": {
              "$ref": "#/definitions/errorModel"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "orderDetails": {
      "required": [
        "orderNumber",
        "status",
        "shippingMethod",
        "expectedDeliveryDate"
      ],
      "properties": {
        "orderNumber": {
          "type": "string",
          "description": "Order Number"
        },
        "status": {
          "type": "string",
          "description": "Order Status"
        },
        "shippingMethod": {
          "type": "string",
          "description": "Shipping Method"
        },
        "expectedDeliveryDate": {
          "type": "string",
          "description": "Expected Delivery Date"
        }
      }
    },
    "errorModel": {
      "required": [
        "errorCode",
        "errorMessage"
      ],
      "properties": {
        "errorCode": {
          "type": "string",
          "description": "A service-specific error code."
        },
        "errorMessage": {
          "type": "string",
          "description": "A service-specific error code."
        }
      }
    }
  }
}

Create Named Credential

The Url of the Named credential is the host which is mentioned in the schema. No Authentication is selected in this example, but in real implentations the authentication protocol needs to be selected based on what the External Service Provider supports.

Register External Service.

While registering External service, two important information to be filled in.
a) Named Credential. The one created in the above step.
b) Either Service schema relative url or Complete JSON need to be given in order to create APEX actions. Here in my example, I have given relative URL which is “/orders/schema”.
Upon Save, Apex actions will be generated. Screenshot is given below.

Invocation of External Service from Flow.

Two flows are created for the two tasks listed below.
  1. Fetch Order Status.
  2. Update Order Status
While creating flow most important element to consider is Action element. While creating new Action either we can filter by Category or Type.
If Category is selected, it will show the external service name Ex. OrderManagementService, then the next step to select the apex actions. Ex. getOrder or updateOrder.
If we filter by Type then navigation will be like this. Type -> External Service -> Select the Apex Action.
Please see the video recording of these flows how it works.I have used Screen flow(User Initiated) but Auto lunched flow can also be used depending on the use case.
Flow Recording

Thank you for visitng our blog, pleave leave your feedback/questions in the comment section.

Setting Up Sender Authentication Package(SAP) for Marketing Cloud Account

Sender Authentication Package(SAP) is additional product of salesforce on top of the Marketing cloud license, which helps increasing the brand value of your brand using different product such as :

  1. Private Domain
  2. Account Branding
  3. Dedicated IP
  4. Reply Mail Management

Sender Authentication Package (SAP) helps your subscribers identify your brand and increases awareness of your brand, which in- turn increases your brand(domain) reputation and thus increasing your email deliverability. In this post we will discuss the step by step process to configure SAP with private domain for your Marketing Cloud Account.

Salesforce reccomends using a dedicated domain or sub-domain to send emails from marketind cloud. This domain usually works as the from address for the email sends from the marketing cloud account. When a SAP package is purchased, Salesforce authenticates email sends using the Sender Policy Framework (SPF), Sender ID, and DomainKeys/DKIM authentication. Salesforce provides mutiple options with SAP configuration such as :

  1. Salesforce buys the domain for you and configures SAP on it, this domain is used dedicatedly for Marketing cloud
  2. Delegate an existing domain or a sub-domain that you own. Salesforce will authenticate this domain/sub-domain for you.
  3. Self host DNS using a Domain/sub-domain you own.

The 2nd scenario is the most common scenario and set-up that we see, as customers are most likely to use a part of their existing domain for Email Marketing. This step requires delegating the sub-domain to marketing cloud in your DNS entries: Lets jump to the steps now.

Step 1:
Decide on the domain, or sub domain name. It is important to understand that once your domain is setup, any changes to it will be of additional cost to you from Salesforce.

Step 2:
Once you have you domain/subdomain ready, DNS entries need to be made to delegate it to Marketing cloud. The detailed step can be found in this help document

Step 3:
After you complete your DNS entries, submit the SAP form with the link received from Salesforce. Salesforce usally takes 2-3 working days to complete the configuration

Step 4:
After you receive the confirmation from Salesforce on the SAP configuration, create necessary configuration in your marketing cloud account for sending emails, which includes:
1. From Address Creation with the verified SAP domain: Since the domain is already verified, any from address you create with it will be automatically verified.
2. Creation of Sender Profiles : Create Sender profiles as per your customer requirement using the From address from the authenticated domain.
3. Creation of Send Classification : Create Send Classification using the Sender profiles you created earlier. Usually you will need different send classification for your newsletter, commercial and transactional emails.

Step 5:
With the confimation email of your SAP configuration, your should ideally receive instructions to audit your sender score. To do it you will need to send an email to “cssreputation@etreputation.com” from the newly created send classficication and raise a Salesoforce case to Salesforce with the following information:
1. Subject line
2. From Name
3. Approfximate Email Send time
Salesforce will provide you the sender score for your email domain. A score of 90 or higher is considered good. Details can be found here

Your Domain Authentication is complete with the above step, and you would be ready to send emails from your new domain. The Reply Mail Management, and Dedicated IP configuration are different topics and we will discuss then in another post. Feel free to leave your questions/feedback.

Thank You and Stay Safe.

More about LWC, LDS, UI API and Decorators.

In the last post, we discussed about LWC, @track decorator and Apex Imperative Method technique. In this post, we will go little deeper to know about other Decorators such as @api and @wire.

Aura components and Lightning web components share the same underlying services Lightning Data Service, User Interface API etc. We will see how LDS(Lightning Data Service) and UI API work with LWC.
We will be creating 2 LWCs (lwcwithlds, createContact) to cover these concepts. Let’s get started.

lwcwithlds” LWC has two sections. The first section of displays Account data using LDS and also has inline edit within it. The recordId and objectApiName are declared as @api decorators.

Generally the api decorator is used for Parent-to-child inter-component communication. In this case, the Flexi Page(Account Record page) provides the recordId and objectApiName to this web component. Also one more thing to mention here is the fields are defined using dynamic schema approach. The syntax for the same is below:

fields = ['Name', 'Industry', 'AccountNumber'];

Another approach could be using Static Schema also can used as below:

import NAME_FIELD from '@salesforce/schema/Account.Name';  
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';   import ACCOUNTNUMBER_FIELD from '@salesforce/schema/Account.AccountNumber';       

The second section of the component contains another child LWC “createContact”. This is to create Contact for the Account using LDS. Good thing here is; Record creation can be achieved without writing Server side code which is a well known forte of Lightning Design system. Here we are using @api decorator for “accountid”. The value of accountid passes from parent component “lwswithlds”. In order to create record, UI API can be used as below.

import { createRecord } from ‘lightning/uiRecordApi’;

To know more about User Interface API, refer the link given below. https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_records_create.htm

Success/Error message is being shown using Lightning Toast.  

import { ShowToastEvent } from ‘lightning/platformShowToastEvent’;

Fields and Object name are referenced using Static Schema as explained above. Advantage of static schema approach is it respects referential integrity that means the fields and Object name is validated during Compilation time.  

The third section of the Component, renders all the related contacts for that specific account. Contacts are fetched from the server using Apex Wire Function with parameters. Yes you guess it right, we are using @wire decorator here.

import getContactList from '@salesforce/apex/ContactSearchController.getContactList'; 
           
//Call Apex method using Wire Decorator with a Parameter
@wire(getContactList, { accId: '$recordId' }) contacts;     

Code Snippet for lwswithlds.html

<template>
    <lightning-card title="Account Record Form using LDS (Dynamic)" icon-name="standard:Account">

        <div class="slds-m-around_medium">
            <lightning-record-form object-api-name={objectApiName} record-id={recordId} fields={fields}></lightning-record-form>
        </div>
    </lightning-card>
    <lightning-card>    
        <template if:true={recordId}>
            <c-create-contact accountid={recordId}> </c-create-contact>
        </template>
    </lightning-card>
    
    <template if:true={contacts.data}>
        <lightning-card title="Related Contacts using Wire decorator" icon-name="standard:Contact">
            <table class="slds-table slds-table_bordered slds-table_striped">
                <thead>
                    <tr>
                        <th scope="col"><span class="slds-truncate">Name</span></th>
                        <th scope="col"><span class="slds-truncate">Contact Title</span></th>
                        <th scope="col"><span class="slds-truncate">Email</span></th>
                        <th scope="col"><span class="slds-truncate">Phone</span></th>
                    </tr>
                </thead>
                <tbody>
                    <template for:each={contacts.data} for:item="contact">
                        <tr key={contact.Id}>
                            <td>{contact.Name}</td>
                            <td>{contact.Title}</td>
                            <td>{contact.Email}</td>
                            <td>{contact.Phone}</td>
                        </tr>
                    </template>
                </tbody>
            </table>
        </lightning-card>
    </template>
    
</template>

Code Snippet for createContact.html

<template>
    <lightning-card title="Create Contact using LDS" icon-name="standard:record">
        <div class="slds-m-around_medium">
            <lightning-input label="Id" disabled value={contactId}></lightning-input>
            <lightning-input label="Last Name" onchange={handleNameChange} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-input label="Account" value={accountid} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-input label="Email" onchange={handleEmailChange} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-input label="Phone" onchange={handlePhoneChange} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-button label="Create Contact" variant="brand" onclick={createContact}></lightning-button>
        </div>
    </lightning-card>    
</template> 


Code Snippet for lwswithlds.js

import { LightningElement, api, wire } from 'lwc';
import getContactList from '@salesforce/apex/ContactSearchController.getContactList';

export default class Lwcwithlds extends LightningElement {
     // Flexipage provides recordId and objectApiName
     @api recordId;
     @api objectApiName;
     fields = ['Name', 'Industry', 'AccountNumber'];
     //Call Apex method using Wire Decorator with a Parameter
     @wire(getContactList, { accId: '$recordId' })
     contacts;
} 

Code Snippet for createContact.js

import { LightningElement, track, api } from 'lwc';
//To show succes/error message using Lightning Toast
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
//Create Record using User Interface API
import { createRecord } from 'lightning/uiRecordApi';
import CONTACT_OBJECT from '@salesforce/schema/Contact';
import NAME_FIELD from '@salesforce/schema/Contact.LastName';
import EMAIL_FIELD from '@salesforce/schema/Contact.Email';
import PHONE_FIELD from '@salesforce/schema/Contact.Phone';
import ACCOUNTID_FIELD from '@salesforce/schema/Contact.AccountId';

export default class CreateContact extends LightningElement {
    @track contactId;
    //Account Id passed from Parent to Child Component using API decorator.
    @api accountid;

    name = '';
    email = '';
    phone = '';    
    

    handleNameChange(event) {
        this.contactId = undefined;
        this.name = event.target.value;
    }

    handleEmailChange(event) {
        this.email = event.target.value;
    }

    handlePhoneChange(event) {
        this.phone = event.target.value;
    }

    createContact() {
        const fields = {};
        fields[NAME_FIELD.fieldApiName] = this.name;
        fields[EMAIL_FIELD.fieldApiName] = this.email;
        fields[PHONE_FIELD.fieldApiName] = this.phone;
        fields[ACCOUNTID_FIELD.fieldApiName] = this.accountid;
        const recordInput = { apiName: CONTACT_OBJECT.objectApiName, fields };
        createRecord(recordInput)
            .then(contact => {
                this.contactId = contact.id;
                this.dispatchEvent(
                    new ShowToastEvent({
                        title: 'Success',
                        message: 'Contact created',
                        variant: 'success',
                    }),
                );
            })
            .catch(error => {
                this.dispatchEvent(
                    new ShowToastEvent({                        
                        title: 'Error creating record',
                        message: error.message,
                        variant: 'error',
                    }),
                );
            });
    }
}

Code Snippet for ContactSearchController.cls

public with sharing class ContactSearchController {   @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList(Id accId) {
        system.debug('accId is:'+ accId);
        return [SELECT Id, Name, Title, Phone, Email FROM Contact where AccountId = :accId];
    }

}

The code is available on the following github link. The components also need to be pushed to Salesforce before it can be used. Steps to push the components are already explained in the previous post. Once we have the component avaible in Salesforce, we have created an Account Record to page to verify and see how it looks.

New Component
Account Record Page
Contact info for Contact Creation
Contact Created successfully

Lightning Web Components (lwc)

Lightning Web Components is one of the Salesforce’s great findings of today’s evolving web standards.  It is a lightweight framework(thin layer of specialized services) built on top of the web standards. Because of its very nature of coexistence and interoperability, it can be used along with aura components, which means Aura components  can also include lightning web components.

As of today, LWC is a pre-release feature of Salesforce. In order to use it we need to use a pre-release org. This can be implemented in both Scratch and non-scratch orgs.  For explaining the concept here we will be creating a component named as “searchContacts” in a scratch org. This component will search the contacts based on the user input and render the results on the component.

Pre-requisites:

Before we start into the details of LWC and implement the concepts in a real working example, we need to have out environment and set up ready. As mentioned earlier, since LWC is a pre-release feature we would need to setup Salesforce DX in a pre-release environment and visual studio. Please follow the steps in the following trailhead module for the setup.

Set Up Your Salesforce DX Environment

Set Up Visual Studio Code

Once the setup is done you are ready to develop the brand new “Lightning Web Components”. Before we start with the implementation, we would like to discuss about the Decorators a little bit, because you would need to use them while you develop your own components.

Decorators:

LWC supports the following decorators:

Api :  To expose a public property, decorate it with @api. Public properties define the API for a component. Public properties are reactive. If the value of a reactive property changes, the component’s template rerenders any content that references the property.

Track : To track a private property’s value and rerender a component when it changes, decorate the property with @track. Tracked properties are also called private reactive properties.

Wire: To read Salesforce data, Lightning web components use a reactive wire service. When the wire service provisions data, the component rerenders. Components use @wire in their JavaScript class to specify a wire adapter or an Apex method.

Prior to their use, all the above decorators must be imported from lwc in javascript as below.

import { LightningElement,<Decorator Name> } from ‘lwc’;

Implementation:

Okay, so we now have our environment ready and visual studio configures. We also know which decorators are available to us and where to use those. Let’s get back to work guys 😉

Creating Project in VS

  1. Open Visual Studio, press Command + Shift + P on a Mac or Ctrl + Shift + P on Windows.
  2. Type SFDX and Select SFDX: Create Project.
  3. Enter searchContactsLWC as the project name, and press Enter.
  4. Select a folder
  5. Click Create Project. Visual Studio will create the base project setup for you and you will see it as below

Authorize a Dev Hub:

When you are setting up DX for the first time, you would need to authorize your dev hub with the visual studio, you can skip this step if you have already authorized your pre-release environment.

  1. Open Visual Studio, press Command + Shift + P on a Mac or Ctrl + Shift + P on Windows.
  2. Type SFDX and Select SFDX: Authorize a Dev Hub.
  3. Log in using your pre-release Dev Hub org credentials.Click Allow

Creating the Lightning Web Component:

In the project tree, Go to “lwc” folder , right click and select “sfdx: Create Lightning Web Component”. Enter the folder/Component name as you wish, here it is searchContacts. It creates 3 files inside the lightning web component such as .html, .js and .xml files.

Enter the below code snippet in searchContacts.html

<template>

        <lightning-card title="ContactSearch" icon-name="custom:custom57">
    
            <div class="slds-m-around_medium">
                <lightning-input type="search" class="slds-m-bottom_small" label="Contact Search" value="{searchKey}" onchange="{changeHandler}"></lightning-input>
                <p class="slds-m-bottom_small">
                        <lightning-button label="Load Contacts" onclick="{handleLoad}"></lightning-button>
                </p>
                <template if:true="{contacts}">
                    <template for:each="{contacts}" for:item="contact">
                    <lightning-layout vertical-align="center" key="{contact.Id}">
                        <lightning-layout-item padding="around-small">
                            <p>{contact.Name}</p>
                            <p>{contact.Title}</p>
                            <p><lightning-formatted-phone value="{contact.Phone}"></lightning-formatted-phone></p>
                        </lightning-layout-item>
                    </lightning-layout>
                    </template>
                </template>
            </div>
            <template if:true="{errors}">
                <div class="slds-p-around_medium">
                <lightning-icon icon-name="utility:error"></lightning-icon>
                <div class="slds-p-around_small"></div>
                    <template for:each="{errors}" for:item="error">
                        <p key="{error.message}">{error.message}</p>
                    </template>
                </div>
            
            </template>    
        </lightning-card>    
    </template>

Code Snippet for searchContacts.js

import { LightningElement, track } from 'lwc';
import findContacts from '@salesforce/apex/ContactSearchController.findContacts';

export default class SearchContacts extends LightningElement {
    @track contacts;
    @track errors;
    @track searchKey = '';

    changeHandler(event) {
        this.searchKey = event.target.value;
    }

    handleLoad() {      
        const searchKey = this.searchKey;
        //Invoke using ApexImperativeMethod approach
        findContacts({ searchKey })
            .then(result => {
                this.contacts = result;
                this.error = undefined;
            })
            .catch(error => {
                this.errors = error;
                this.contacts = undefined;
            });

    }
}

Code Snippet for searchContacts.css

lightning-input {
    position: relative;
    border: solid 1px #ecebea;
    border-radius: 4px;
    display: block;
    padding: 2px;
}

lightning-input:before {
    color: #dddbda;
    position: absolute;
    top: -9px;
    left: 4px;
    background-color: #ffffff;
    padding: 0 4px;
}

In order to fetch records from the Contact Object, we are using Apex method. Apex method can be called from Lightning Web Components in two ways. Using @Wire Decorator, or by creating Apex Imperative method. In this LWC, we are going to use Apex Imperative Method. An  Apex Imperative method must be a static method annotated with @AuraEnabled.

public with sharing class ContactSearchController {

    @AuraEnabled(cacheable=true)
    public static List<Contact> findContacts(String searchKey) {
        system.debug('searchKey is:'+ searchKey);
        String key = '%' + searchKey + '%';
        List<Contact> lstContacts = new List<Contact>();
        lstContacts = [SELECT Id, Name, Title, Phone, Email FROM Contact WHERE Name LIKE :key LIMIT 10];
        system.debug('lstContacts are:'+ lstContacts);
        return lstContacts;
    }

Now we are done with development of the Lightning Web Component. But it is now available only in our local copy, i.e. the project folder we have selected while creating the Project. To be able to use it, the component must be available in our Salesforce environment, and for that we would need to push all our new components i.e. the LWC and the apex class to Salesforce. Lets now see how it is done:

Push to a Scratch Org

  1. Open Visual Studio, press Command + Shift + P on a Mac or Ctrl + Shift + P on Windows.
  2. Type SFDX and Select SFDX: Push Source to Default Scratch Org.

Verifying and Using the searchContacts LWC

Go back to the prep-release Salesforce org. Go to set up, enter “Lightning Components” in the Quick find box and select. The Lightning Web Components are listed in “Lightning Components” and the type is “LWC”. Verify your lightning component in the list of components.

Now that we have verified our lightening component, let see how does it look like. To use this we will be creating a lightning page. Steps below:

  • Switch the view to Lightning Experience
  • Go to Setup and enter “Lightning App Builder” in Search.
  • Select New, and select “App Page”, give the page name “searchContacts”
  • Select the desired layout. The “contactSearch” component will be available in the custom components section.
  • Drag the “contactSearch” component to a block and activate the page and save it.
  • Open apps menu, and select “searchContacts” and Voila you have your app right there build on your new LWC
  • Enter the search keyword, the contact name and click on “Load Contacts” to get the results on your app

The project is available in github. Follow the link https://github.com/Milanjenasfdc/LightningWebComponents

There we are!!!! We have just created and used our first LWC. We hope this blog helped you understanding the concepts of scratch org, VS set up and LWC. Post your questions feedback on the comments section.

Next blog will be on the Winter 19 Release notes highlights. Stay Tuned 🙂

Why Salesforce?

The greatest thing about Salesforce – it has something to offer for everyone. Be it a startup, be it a mid-size Business, or be it a huge global Business with millions of Customers. Salesforce CRM can be implemented for all. Whether you are doing a new CRM implementation for your business or want to migrate from your legacy CRM application to cloud Salesforce has got tools to make everything easy.

From the cost point of view as well, you pay only for the features you want to use. The standard CRM applications in the platform are designed modularly, hence the Customers are not forced to use or pay for feature they do not require.

Let’s say you have a very robust Sales application and your Sales Representatives are quite happy with it. But your Service application is not doing so well, and your call center agents are continuously complaining about it. To fix it using Salesforce CRM, you only need to spend for the license cost of “Salesforce Service Cloud” based on the number of users you would onboard on the CRM.No doubt they claim they are the world’s number one CRM.

As we told you it’s has something for everyone, Salesforce is not only for a business; you can build your own career in Salesforce. And let us make this clear for you that building a career in Salesforce not necessarily means that you have to get a job at “The Salesforce Organization”. Salesforce in the market is not just a cloud CRM, but it is a highly demanded Skill. With the increasing popularity day by day and with the opportunities of Customizing the pre-built CRM, Creating custom aplications on the Force.com platform, integrating Salesforce CRM to many other External application etc, it is now very safe to call it as a niche skill.

Icing on the cake??

To start your career in Salesforce, you do not necessarily need to know “CODING”. Yes, you read it right. You can become a Salesforce CRM Consultant or an awesome Admin without having to write any logic. You can build an awesome application on the platform with only using out of box features and Salesforce point and click tools. How cool is that?

And if you already familiar with codes, or with integration concepts, or you have background in scripting then you can do wonders with Salesforce.

Well it’s a lot of theory now, we will stop here. The next blog would be about creating your very own Salesforce blog to explore the world of CRM and force.com features.

Stay tuned!!

What is Salesforce?

Spoiler Alert: This post is aimed to explain Salesforce in very basic terms.

If you have landed on this page then it means you already know there is a term “Salesforce”, and trust us you are getting inside a world of pure “Magic”. We are amazed by the numerous number of features this platform (will come to the terminology later) has to offer.

“Salesforce is a CRM built and hosted on force.com platform.

This statement is only one of those computer Jargons if you do not understand what is CRM or Platform or Cloud. So, let’s dig into what these terms actually mean.

CRM – The name suggests Customer Relationship Management. CRM is basically the combination of the many ways an organization interacts with it’s current and prospected Customers aiming at raising the revenue. CRM touches all the process that is part of an end to end business model. E.g. Marketing the product to prospected customers, regular product servicing at the customer site etc.. everything is part of the CRM system.

Cloud- Cloud Computing is basically a web-based computing where multiple services including storage, application, server etc are delivered to a customer on their devices using the Internet. Cloud computing relies on on-demand sharing of resources to achieve coherence, cost-effectiveness, and “Optimal and Fair” sharing of the resources to its tenants. To make it little simpler, it creates a virtual pool of resources, and the needed resources are used by the tenant as and when they need it. In technical terms it is the “Multi-Tentant Server Architecture“.

Force.com Platform- Force.com is a Platform As A Service (PaaS). Force.com is a platform built with “Multitenant Server” architecture hosted the cloud. 
In a more simpler term, it is basically a web space where the application(s) can be developed and hosted. Basically, you have your data model, data, application, server all at one place. Everything is on the platform and you don’t have to worry about setting up anything to build or host an application. To enable the fair sharing model on Force.com Salesforce has set up some rules and limits on the usage of resources termed as “Governor Limits” in Salesforce.

Since now you have a bit more understanding of the above terms let’s say it again: “Salesforce is a CRM application built and hosted on force.com platform.” Makes a lot more sense now right?

Salesforce” is a pre-built hosted CRM Environment, which you can start using as it is. But of course, you are not, and will never be limited only to the existing features(they call it Out-Of-The-Box feature, and is not it the exact opposite of how you used the phrase “out of the box” till today as in “Think Out Of The Box”;) . Salesforce has already done that for us 🙂 ). You can configure (using point and clicks tools) and customize(using a little bit of the coding knowledge based on the OOPS concepts) the inbuilt Salesforce CRM Applications to make your CRM application for your Own customers.

We hope the explanation above helped you know little about Salesforce. Please

provide your feedback in the comments section.

The upcoming blogs will be about the Salesforce CRM Applications. Stay tuned 🙂

Cheers!!

The Journey Begins

Thanks for joining us! We welcome you to our world of Salesforce. What is Salesforce for us??

Well, it is our “Bread & Butter”

We hope our effort to share our knowledge about Salesforce with you will help you some way in your “Journey of Salesforce”. It is definitely going to help us.

We would appreciate if you can share your feedback about the blog and its contents in the comments section.

Thank You!!

Good company in a journey makes the way seem shorter. — Izaak Walton

post