How to Open Email Composer Automatically using Aura Component

May 11, 2023


 

Insert Records using Lightning Web Components

 

Open Email Composer using Aura Component

 

In this salesforce blog, we will learn about How to create a screen to insert a custom object record using Lightning Web Component and How to open email action using Aura Component when we click on the button from the custom object record detail page. To achieve this requirement, let's create three objects named PlanOption and LineItem. Please have a look for these three object fields as below figure:

 

 

 

 

 

 

In this tutorial we will cover the two phases:

  1. Create Lighting Web Component Screen to insert LineItem object records.
  2. Open Email Composer using Aura Component.

Let gets dive into it and see these two phases step by step:

  1. Create Lighting Web Component Screen to insert Line Item object records:

     

    In the first step, create an apex class to invoke in the lightning web component:

    public with sharing class FuturePlanImplementationController {

        @AuraEnabled(Cacheable=false)

        public static List<LineItem__c> getAllLineItems(String planId, String optionName) {

            System.debug('optionName = '+optionName);

            List<LineItem__c> lines = new List<LineItem__c>();

            if(planId != null)

            {

                Plan__c plsnRec = [select Name from Plan__c where Id =:planId limit 1];

                List<Option__c> ops = [Select Id, Name, Plan__c from Option__c where Plan__C = :planId and Name = :optionName];

                if(ops.size() == 0)

                {

                    Option__c opt = new Option__c();

                    opt.Name = optionName;

                    opt.Plan__c = planId;

                    insert opt;

     

                    for(Integer i=1; i<=10; i++)

                    {

                        LineItem__c li = new LineItem__c();

                        li.Option__c = opt.Id;

                        lines.add(li);

                    }

                    insert lines;

                }

                else

                {

                    List<LineItem__c> lineItems = [Select Id, Name, Service_Name__c, Price__c from LineItem__c where Option__r.Plan__c = :planId and Option__c =:ops[0].Id order by Name];

                    if(lineItems.size() > 0)

                    {

                        System.debug('If statement.');

                        return lineItems;

                    }

                    else

                    {

                        for(Integer i=1; i<=10; i++)

                        {

                            LineItem__c item = new LineItem__c();

                            item.Option__c = ops[0].Id;

                            lines.add(item);

                        }

                        if(lines.size() > 0)

                            insert lines;

                        System.debug('Else statement.');

                    }

                }

            }

            return lines;

        }

     

        @AuraEnabled

        public static void doUpdate(String lineNameJSON, String linePriceJSON) {

            System.debug('lineNameJSON = '+lineNameJSON);

            System.debug('linePriceJSON = '+linePriceJSON);

           

            List<LineItem__c> toBeUpdateLineItems = new List<LineItem__c>();

            Map<String, String> nameMap = (Map<String, String>) JSON.deserialize(lineNameJSON, Map<String,String>.class);

            Map<String, String> priceMap = (Map<String, String>) JSON.deserialize(linePriceJSON, Map<String,String>.class);

            for(Id : nameMap.keySet())

            {

                toBeUpdateLineItems.add(new LineItem__c(Id = id, Service_Name__c = nameMap.get(id)));

            }

            if(!toBeUpdateLineItems.isEmpty())

            {

                update toBeUpdateLineItems;

            }

            toBeUpdateLineItems.clear();

            for(Id : priceMap.keySet())

            {

                toBeUpdateLineItems.add(new LineItem__c(Id = id, Price__c = Decimal.ValueOf(priceMap.get(id))));

            }

            if(!toBeUpdateLineItems.isEmpty())

            {

                update toBeUpdateLineItems;

            }

            System.debug('toBeUpdateLineItems = '+toBeUpdateLineItems);

        }

    }

     

    This is the code of lightning web component that will create a screen to insert LineItem object records:

    insertLineItem.html:

    <template>

    <div style="height: 640px;" align="center">

        <lightning-card>

            <div style="height: 20px; font-family: sans-serif; font-size: 20px; font-weight: bold; color: rgb(19, 149, 189);">LineItems</div>

            <div class="slds-m-around_large" style="background-color:#F2F3F4; width: 450px;" align="center">

                <div style="height: 10px; width: 400px;"></div>

                <div style="display: flex; height: 20px; width: 400px;" align="center">

                    <div style="margin-left:60px; font-size: 15px;">Name</div>

                    <div style="margin-left:160px; font-size: 15px;">Price</div>

                </div>

                    <template for:each={lineItemList} for:item="line">

                        <div key={line.Id} id={line.Id} style="display:flex; width: 400px;" align="center">

                            <lightning-input class="slds-m-right_large" data-field="Name" value={line.Service_Name__c} onchange={handleServiceNameChange} data-id={line.Id}></lightning-input>

                            <lightning-input class="slds-m-right_large" data-field="Price" value={line.Price__c} onchange={handlePriceChange} data-id={line.Id}></lightning-input>

                        </div>

                    </template>

                <br/>

                <div>

                    <button class="slds-button slds-button_brand slds-align_absolute-center" onclick={handleSave} title="Save">Save</button>

                </div>

                <div style="height: 10px; width: 400px;"></div>

            </div>

        </lightning-card>

    </div>

    </template>

     

    insertLineItem.js:

    import { LightningElement, api, track, wire } from 'lwc';

    import { ShowToastEvent } from 'lightning/platformShowToastEvent';

    import getAllLineItems from '@salesforce/apex/FuturePlanImplementationController.getAllLineItems';

    import doUpdate from '@salesforce/apex/FuturePlanImplementationController.doUpdate';

     

    export default class FuturePlanImplementation1 extends LightningElement {

        @api recordId;

        @api Type;

     

        @track error;

        @track lineItemList;

        @track inputValuesForName = new Map();

        @track inputValuesForPrice = new Map();

     

        connectedCallback() {

            console.log('>>>> Type >>>> '+this.Type);

            this.getLineItemListFromApex();

        }

     

        getLineItemListFromApex(){

            getAllLineItems({planId: this.recordId, optionName : this.Type})

                .then(result => {

                    if(result){

                        this.lineItemList = result;

                        console.log('>>>> Get LineItem result >>>> '+JSON.stringify(result));

                        this.error = undefined;

                    }

                })

                .catch(error => {

                    this.error = error;

                    this.lineItemList = undefined;

                    console.log('>>>> error >>>>'+error);

                })

        }

       

        handleServiceNameChange(event)

        {

            const key = event.currentTarget.dataset.id;

            const value = event.target.value;

            this.inputValuesForName.set(key, value);

        }

     

        handlePriceChange(event)

        {

            const key = event.currentTarget.dataset.id;

            const value = event.target.value;

            this.inputValuesForPrice.set(key, value);

        }

       

       

        handleSave()

        {

            var obj = Object.fromEntries(this.inputValuesForName);

            console.log("obj = ", JSON.stringify(obj));

     

            var obj1 = Object.fromEntries(this.inputValuesForPrice);

            console.log("obj1 = ", JSON.stringify(obj1));

     

            doUpdate({ lineNameJSON : JSON.stringify(obj), linePriceJSON : JSON.stringify(obj1)})

                .then(result => {

                    this.message = result;

                    this.error = undefined;

                    if(this.message !== undefined) {

                        this.dispatchEvent(

                            new ShowToastEvent({

                                title: 'Success',

                                message: 'Record Updated Successfully',

                                variant: 'success',

                            }),

                        );

                    }

                    console.log(JSON.stringify(result));

                })

                .catch(error => {

                    this.message = undefined;

                    this.error = error;

                    this.dispatchEvent(

                        new ShowToastEvent({

                            title: 'Error',

                            message: error.body.message,

                            variant: 'error',

                        }),

                    );

                    console.log("error >>>>>>>>>>>>>>>>", JSON.stringify(this.error.body.message));

                });

        }

    }

     

    insertLineItem.js-meta.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">

        <apiVersion>56.0</apiVersion>

        <isExposed>true</isExposed>

        <targets>

        <target>lightning__AppPage</target>

        <target>lightning__RecordPage</target>

        </targets>

        <targetConfigs>

        <targetConfig targets="lightning__RecordPage, lightning__AppPage">

            <property name="Type" type="String" datasource="Option 1, Option 2, Option 3, Option 4, Option 5" />

        </targetConfig>

    </targetConfigs>

    </LightningComponentBundle>

     

    Once you created apex class and lightning web component, let add this screen on the plan object so that we can access this lightning web component screen. I have created five tabs on the Plan object named Option 1, Option 2, Option 3, Option 4, Option 5 and add lightning web component to these five tabs So that when we click on these tabs, the Option object record and LineItem record is created automatically according to our apex class. For Example: if we click on the Option 1 tab, the Option object record named Option 1 and 10 of LineItem record is created and Option 1 record is added to these 10 Line Items records automatically.

     

    Note: Do not forgot to set the Type value as mentioned in the below figure (IN YELLOW BOX). You have to set Type value as followed:

    • For Option 1 Tab, Type value will be Option 1.
    • For Option 2 Tab, Type value will be Option 2.
    • For Option 3 Tab, Type value will be Option 3.
    • For Option 4 Tab, Type value will be Option 4.
    • For Option 5 Tab, Type value will be Option 5.

     

     

    After successfully adding these tabs and setting the Type value for every tab, you should be able to see the lighting web component as below figure, if not, please make sure that you have added the lightning web component to all the option�s tabs on the record page.

     

     

  2. Open Email Composer using Aura Component.

In this phase we will create an action button using aura so when clicks on the button it will open a PDF which shows all Line Items in and an aura button to open the email composer automatically.

To show PDF and open email composer automatically, first we need to create the below snippet code:

  1. Create Apex Class.
  2. Create Visualforce Page.
  3. Create Aura Component.

 

Let us understand one by one:

  1. Create Apex Class:

public class PlanFutureImplementationPDFController {

    public Map<Option__c, List<LineItem__c>> mapOptionLineItems {get;set;}

    public List<Option__c> optionList {get;set;}

    public Plan__c planObj {get;set;}

    public String addressWithStreetVal {get;set;}

    public String addressVal {get;set;}

    public String currentDate {get;set;}

   

    public PlanFutureImplementationPDFController(ApexPages.StandardController controller)

    {

        Id planId = controller.getId();

        System.debug('planId = ' + planId);

       

        planObj = [Select Id, Name, First_Name__c, Last_Name__c, Email__c, Phone__c, Address__Street__s,

                   Address__City__s, Address__StateCode__s, Address__CountryCode__s, Address__PostalCode__s from Plan__c where Id=:planId];

       

        addressWithStreetVal = '';

        addressVal = '';

       

        if(planObj.Address__Street__s != null){

            addressWithStreetVal = planObj.Address__Street__s;

        }

        if(planObj.Address__City__s != null){

            addressVal = planObj.Address__City__s + ', ';

        }

        if(planObj.Address__StateCode__s != null){

            addressVal += planObj.Address__StateCode__s + ', ';

        }

        if(planObj.Address__CountryCode__s != null){

            addressVal += planObj.Address__CountryCode__s + ', ';

        }

        if(planObj.Address__PostalCode__s != null){

            addressVal += planObj.Address__PostalCode__s + ', ';

        }

        if(addressVal != ''){

            addressVal = addressVal.removeEnd(', ');

        }

       

        optionList = [Select Id, Name, (Select Service_Name__c, Price__c From LineItems__r order by Name) From Option__c where Plan__c =:planId order by Name];

        mapOptionLineItems = new Map<Option__c, List<LineItem__c>>();

        for(Option__c opt : optionList)

        {

            List<LineItem__c> lineItems = new List<LineItem__c>();

            for(LineItem__c item : opt.LineItems__r)

            {

                if(item.Service_Name__c != null && item.Price__c != null)

                    lineItems.add(item);

            }

            if(!lineItems.isEmpty())

                mapOptionLineItems.put(opt, lineItems);

        }

        DateTime dT = DateTime.now();

        currentDate = dT.month()+'/'+dT.day()+'/'+dT.year();

        String optionId = '';

    }

}

 

Create Visualforce Page:

<apex:page standardcontroller="Plan__c" extensions="PlanFutureImplementationPDFController" renderAs="pdf">

    <div class="slds-card" style="border: 1px solid black;">

        <div  class="slds-card" style="padding-top:23px; padding-left:15px; padding-right:30px; padding-bottom:10px;">

            <div>

                <table style="border-collapse: collapse;border: 1px solid #5e73a2; font-family:sans-serif; width:100%;color: #13203a; font-size: 13px;">

                    <tr>

                        <td style="vertical-align:top;">

                            <img src="{!$Resource.PDF_Image}" style="height:231px; width:200px"/>                        

                        </td>

                        <td align="right" style="vertical-align:top;"><span style="font-weight:bold; color:#374aac;padding:10px;">Plan</span><br/><span style="padding:10px;">{!planObj.Name}</span> <br/><br/><span style="font-weight:bold; color:#374aac; padding:10px;">Service Date:</span><br/><span style="padding:10px;">{!currentDate}</span></td>

                    </tr>

                </table>

            </div>

            <hr class="solid" style="border-top: 1px solid #bbb;"/>

            <div>

                <table style="border-collapse: collapse;border: 1px solid #5e73a2; font-family:sans-serif; width:100%;color: #13203a; font-size: 13px;">

                    <tr>

                        <th style="font-weight:bold; color:#374aac; padding:10px;width:40%; padding-left:25px;">Plan For</th>

                        <th align="right" style="vertical-align:top;"><span style="font-weight:bold; color:#374aac;padding:10px; ">Payable to</span><br/><span style="font-family:Arial, sans-serif; color:gray; font-size:13px;padding:10px;">Company Name Here.</span></th>

                    </tr>

                    <tr>

                        <td style="font-family:Arial, sans-serif; color:gray; font-size:13px; padding-left:25px;">{!planObj.First_Name__c} {!planObj.Last_Name__c}<br/>{!addressWithStreetVal}<br/>{!addressVal}<br/>{!planObj.Email__c}<br/>{!planObj.Phone__c}</td>

                        <th align="right" style="vertical-align:bottom;"><span style="font-weight:bold; color:#374aac;padding:10px;">Plan</span><br/><span style="font-family:Arial, sans-serif; color:gray; font-size:13px;padding:10px;">some text here.</span></th>

                    </tr>

                    <tr>

                        <td></td>

                        <td align="right" style="font-family:Arial, sans-serif; color:gray; font-size:13px; padding-left:20px; padding:10px"></td>

                    </tr>

                </table>

            </div>

            <hr class="solid" style="border-top: 1px solid #bbb;"/>

            <div>

                <table style="border-collapse: collapse; border: 1px solid #5e73a2; font-family:sans-serif; width:100%;color: #13203a; font-size: 13px;">

                    <thead>

                        <tr>

                            <th style="font-weight:bold; color:#374aac; padding:10px;padding-left:25px;width:80%">Job Description</th>

                            <th align="right" style="padding:10px;color:#374aac; width:30%">Total price</th>

                        </tr>

                    </thead>

                    <tbody>

                        <apex:repeat value="{!mapOptionLineItems}" var="optionKey">

                            <apex:variable var="count" value="{!0}"/>

                            <apex:variable var="sumTotal" value="{!0}"/>

                            <tr style="background-color: #E8F1F9;">

                                <td role="gridcell" style="width:100%; height:5px; font-family:Arial, sans-serif; color:Black; font-size:16px; text-align:center; padding: 10px; ">

                                    <apex:outputText >{!optionKey.Name}</apex:outputText>

                                </td>

                                <td align="right" role="gridcell" style="width:100%; height:5px; font-family:Arial, sans-serif; color:Black; font-size:13px; padding-left: 0px;">

                                </td>

                            </tr>

                            <apex:repeat value="{!mapOptionLineItems[optionKey]}" var="obj">

                                <tr style="{!IF(MOD(count,2)==0, 'background-color: #FFFFFF;','background-color: #E5E7E9;')}">

                                    <td role="gridcell" style="height:5px; font-family:Arial, sans-serif; color:gray; font-size:13px; padding-left:25px;">

                                        <apex:outputText >

                                            {!obj.Service_Name__c}

                                        </apex:outputText>

                                    </td>

                                    <td align="right" role="gridcell" style="height:5px; font-family:Arial, sans-serif; color:gray; font-size:13px; padding-left:20px; padding:10px;">

                                        <span>${!obj.Price__c}</span>

                                    </td>

                                </tr>

                                <apex:variable var="count" value="{!count+1}"/>

                                <apex:variable var="sumTotal" value="{!sumTotal+ obj.Price__c}"/>

                            </apex:repeat>

                            <tr style="height:60px">

                                <td role="gridcell" style="height:5px; font-family:Arial, sans-serif; color:gray; font-size:13px; padding-left:25px;">

                                    <span>GRAND TOTAL</span>                                

                                </td>

                                <td align="right" role="gridcell" style="vertical-align:bottom; height:5px; font-family:Arial, sans-serif; font-size:13px; padding-left:20px; padding:10px">

                                    <span>${!sumTotal}</span>

                                </td>

                            </tr>

                        </apex:repeat>

                    </tbody>

                </table>

            </div>

        </div>

    </div>

</apex:page>

 

 

  1. Create Aura Component:

First, we need to create an apex controller to set in an aura component. So, let get started to create apex controller class:

EmailController.cls:

public with sharing class EmailController {

    @AuraEnabled

    public static List<string> getContentDocumentIds(String recordId) {

        List<string> sendContentDocumentId = new List<string>();

       

        List<ContentDocumentLink> contentDocumentLinks = [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :recordId];

        List<string> contentDocumentIds = new List<string>();

        for (ContentDocumentLink : contentDocumentLinks) {

            contentDocumentIds.add(contentDocumentLink.ContentDocumentId);

        }

       

        List<contentDocument> contentDocumentList = [SELECT Id, CreatedDate FROM contentDocument WHERE Id IN :contentDocumentIds order by CreatedDate desc];

        sendContentDocumentId.add(contentDocumentList[0].Id);

        System.debug('sendContentDocumentId = '+sendContentDocumentId);

       

        return sendContentDocumentId;

    }

   

    @AuraEnabled

    public static String setToAddressOnEmailComposer(String planId)

    {

        String toAddress = '';

        Plan__c planRecord = [Select Id, name, Email__c, Email_To__c from Plan__c where Id = :planId];

        System.debug('planRecord = '+planRecord);

     

if(planRecord.Email_To__c == 'Customer')

        {

            toAddress = planRecord.Email__c;      

        }

       

        return toAddress;

    }

   

    @AuraEnabled

    public static void insertContentDoucment(String planRecordId)

    {

        List<string> contentDocumentIds = new List<string>();

        List<string> sendContentDocumentId = new List<string>();

       

        String requestURL = '/apex/ShowPDF?Id='+planRecordId;

        Blob strCSV;

        if(Test.isRunningTest())

            strCSV = Blob.ValueOf('test');

        else

            strCSV = new PageReference(requestURL).getContent();

        Blob pdfData = strCSV;

       

        ContentVersion cv = new ContentVersion();

        cv.Title = 'Plan '+ Date.today().format().replace('/', '-');

        cv.PathOnClient = cv.Title + '.pdf';

        cv.VersionData = pdfData;

        cv.origin = 'H';

        insert cv;

       

        ContentDocumentLink cdl = new ContentDocumentLink();

        cdl.LinkedEntityId = planRecordId;

        cdl.contentdocumentid = [select contentDocumentId from contentversion where id =: cv.id].contentdocumentid;

        cdl.ShareType = 'I';

        cdl.Visibility = 'AllUsers';

        insert cdl;

    }

}

 

futurePlanImplementationPDF_Aura.cmp

<aura:component controller="EmailController" implements="force:hasRecordId,force:lightningQuickActionWithoutHeader" access="global">

    <lightning:quickActionAPI aura:id="quickActionAPI" />

    <aura:handler name="init" action="{!c.doInit}" value="{!this}" />

    <aura:html tag="style">

        .cuf-content {

        padding: 0 0rem !important;

        }

        .slds-p-around--medium {

        padding: 0rem !important;

        }      

        .slds-modal__content{

        overflow-y:hidden !important;

        height:unset !important;

        max-height:unset !important;

        }

    </aura:html>    

    <div class="modal-header slds-modal__header slds-size_1-of-1">

        <h4 class="title slds-text-heading�medium">Plan PDF</h4>

    </div>  

   

    <div class="slds-modal__content slds-p-around�x-small slds-align_absolute-center slds-size_1-of-1 slds-is-relative">

        <iframe src="{!'/apex/ShowPDF?Id=' + v.recordId}" height="400px" width="100%"></iframe>

    </div>

   

    <div class="modal-footer slds-modal__footer slds-size_1-of-1">

        <lightning:button variant="Brand" class="slds-button" label="Send Email" onclick="{!c.sendEmail}"/>

        <lightning:button variant="Neutral" class="slds-button" label="Cancel" onclick="{!c.handleExit}"/>

    </div>    

</aura:component>

 

futurePlanImplementationPDF_AuraController.js

({

    doInit : function(component, event, helper){

        var currentRecordId = component.get("v.recordId");

        var action = component.get("c.insertContentDoucment");

        action.setParams({ "planRecordId" : currentRecordId });

        action.setCallback(this, function(response) {

            if (response.getState() === "SUCCESS") {

                console.log('>>>>> Successfully invoked insertContentDoucment() >>>>>');

                $A.get('e.force:refreshView').fire();

            }

        });

        $A.enqueueAction(action);

    },

   

    sendEmail : function(component, event, helper) {

        var actionAPI = component.find("quickActionAPI");

        var currentRecordId = component.get("v.recordId");

       

        var args = {

            actionName: "Plan__c.Send_Email",

            entityName: "Plan__c",

            targetFields: {

                Subject: { value: "Plan Details" },

                HtmlBody: { value: "Test Body"},

                ToAddress: { value: "" },

                BccAddress: {value: ""},

                ContentDocumentIds: { value: [''] },

            }

        }

       

        // Call Apex controller method to retrieve ContentDocumentIds

        console.log('currentRecordId = ', currentRecordId);

        var action = component.get("c.getContentDocumentIds");

        action.setParams({ "recordId" : currentRecordId });

        action.setCallback(this, function(response) {

            if (response.getState() === "SUCCESS") {

                var contentDocumentIds = response.getReturnValue();

                args.targetFields.ContentDocumentIds.value = contentDocumentIds;

                console.log('Response from getContentDocumentIds() >>>>> ', JSON.stringify(response));

                actionAPI.setActionFieldValues(args).then(function(response){

                    actionAPI.invokeAction(args);

                }).catch(function(e){

                    console.log('>>>>> Error getContentDocumentIds() >>>>>', JSON.stringify(e));

                });

            }

        });

       

        // Call Apex controller method to set ToAddress

        var action1 = component.get("c.setToAddressOnEmailComposer");

        action1.setParams({ "planId": currentRecordId });

        action1.setCallback(this, function(response) {

            if (response.getState() === "SUCCESS") {

                var toAddress = response.getReturnValue();

                console.log('toAddress = ', toAddress);

                args.targetFields.ToAddress.value = toAddress;

                console.log('Response form setToAddressOnEmailComposer() >>>>> ', JSON.stringify(response));

                actionAPI.setActionFieldValues(args).then(function(response){

                    actionAPI.invokeAction(args);

                }).catch(function(e){

                    console.log('>>>>> Error setToAddressOnEmailComposer() >>>>>', JSON.stringify(e));

                });

            }

        });

       

        $A.enqueueAction(action);

        $A.enqueueAction(action1);

        window.setTimeout(

            $A.getCallback(function() {

                $A.get("e.force:closeQuickAction").fire()

            }), 500

        );

    },

   

    handleExit : function(component, event, helper) {

        $A.get("e.force:closeQuickAction").fire();

    }

})

 

Now we need to create a new action button using aura. Please take a look for How we created aura button:

Note:

  1. Make sure you have created Send Email action on the Plan object, because we used the api name of Send Email action in this java script file.
  2. The Send Email action must be available on the current record page otherwise you have to face the java script error like we can't execute the api because the parent record is not selected.

The Send Email action on the Plan object:

The Send Email action must be available on the current record page:

Thus, we can open the email composer automatically and also load the attachments in the predefined values of the email composer.

I hope this blog helped you!