From e41e43cabd5e4a7925c15f639e43cc47ed8ff1aa Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Fri, 13 Mar 2026 09:57:28 -0400 Subject: [PATCH] feat(sms): SMS delivery via dfsle withSmsDelivery() for recipients without email - Add recipientSmsPhone InvocableVariable to DocusignEnvelopeRequest - Add SMS_PLACEHOLDER_EMAIL constant to DocusignCompositeEnvelopeBuilder - Update resolveRecipients() and buildRecipient() to accept smsPhone param - Chain .withSmsDelivery(smsPhone) on recipient when smsPhone is provided - Substitute placeholder email when recipient has no email and SMS phone given - Add Flow V4 with Get_Recipient_Contact lookup, Is_Recipient_Email_Blank decision, and SMS_Phone_Screen to collect phone for no-email recipients - V3 left untouched for existing Salesforce deployments --- .../DocusignCompositeEnvelopeBuilder.cls | 64 +- .../classes/DocusignEnvelopeRequest.cls | 7 + ...cusign_Envelope_Templates_V4.flow-meta.xml | 765 ++++++++++++++++++ 3 files changed, 821 insertions(+), 15 deletions(-) create mode 100644 composite-envelope-builder/force-app/main/default/flows/Docusign_Envelope_Templates_V4.flow-meta.xml diff --git a/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls b/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls index 95d00d5..80558ec 100644 --- a/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls +++ b/composite-envelope-builder/force-app/main/default/classes/DocusignCompositeEnvelopeBuilder.cls @@ -28,6 +28,17 @@ global with sharing class DocusignCompositeEnvelopeBuilder { // ============================================================ @TestVisible private static final String MULTI_COPY_TEMPLATE_NAME = 'Authorization to Release Information'; + + // ============================================================ + // SMS DELIVERY: Placeholder email used when the primary recipient + // (Docusign Recipient #1) has no email address and SMS delivery is + // requested via recipientSmsPhone. Docusign requires an email on + // every recipient even when dfsle.Recipient.withSmsDelivery() is used; + // this placeholder satisfies that requirement without routing any + // actual email — delivery occurs entirely via SMS. + // ============================================================ + @TestVisible + private static final String SMS_PLACEHOLDER_EMAIL = 'placeholder_email@docusign.com'; @InvocableMethod( label='Send Composite Docusign Envelope' @@ -188,7 +199,7 @@ global with sharing class DocusignCompositeEnvelopeBuilder { } // Resolve recipients from Client_Case__c lookup fields - List recipients = resolveRecipients(req.recordId); + List recipients = resolveRecipients(req.recordId, req.recipientSmsPhone); myEnvelope = myEnvelope.withRecipients(recipients); // Set envelope subject to combined display names (deduplicated, with copy counts). @@ -256,9 +267,13 @@ global with sharing class DocusignCompositeEnvelopeBuilder { * @description Resolves recipients from Client_Case__c lookup fields. * Queries the case record and related contacts to get name/email. * @param recordId The Client_Case__c record ID + * @param smsPhone Optional SMS phone for the primary recipient. When provided, + * the Docusign Recipient #1 is configured for SMS delivery via + * dfsle.Recipient.withSmsDelivery() and a placeholder email is + * substituted if the recipient has no email address. * @return List of dfsle.Recipient objects with role mappings */ - private static List resolveRecipients(String recordId) { + private static List resolveRecipients(String recordId, String smsPhone) { // Query the Client_Case__c record with recipient lookup fields // NOTE: Adjust field API names if they differ in your org String query = 'SELECT Id, ' @@ -271,16 +286,16 @@ global with sharing class DocusignCompositeEnvelopeBuilder { List recipients = new List(); Integer routingOrder = 1; - // Recipient 1: Service Coordinator + // Recipient 1: Service Coordinator (always email delivery) Id serviceCoordinatorId = (Id) caseRecord.get(FIELD_SERVICE_COORDINATOR); if (serviceCoordinatorId != null) { - recipients.add(buildRecipient(serviceCoordinatorId, ROLE_SERVICE_COORDINATOR, routingOrder++, recordId)); + recipients.add(buildRecipient(serviceCoordinatorId, ROLE_SERVICE_COORDINATOR, routingOrder++, recordId, null)); } - // Recipient 2: Docusign Recipient #1 + // Recipient 2: Docusign Recipient #1 (SMS delivery when smsPhone is provided) Id docusignRecipientId = (Id) caseRecord.get(FIELD_DOCUSIGN_RECIPIENT); if (docusignRecipientId != null) { - recipients.add(buildRecipient(docusignRecipientId, ROLE_DOCUSIGN_RECIPIENT, routingOrder++, recordId)); + recipients.add(buildRecipient(docusignRecipientId, ROLE_DOCUSIGN_RECIPIENT, routingOrder++, recordId, smsPhone)); } if (recipients.isEmpty()) { @@ -292,14 +307,20 @@ global with sharing class DocusignCompositeEnvelopeBuilder { } /** - * @description Builds a dfsle.Recipient from a Contact/User lookup ID - * @param recipientId The Contact or User record ID - * @param roleName The Docusign template role name - * @param routingOrder Signing order + * @description Builds a dfsle.Recipient from a Contact/User lookup ID. + * When smsPhone is provided the recipient is configured for SMS + * delivery via dfsle.Recipient.withSmsDelivery(). If the recipient + * has no email address and SMS delivery is requested, SMS_PLACEHOLDER_EMAIL + * is substituted — Docusign requires an email field on every recipient + * even when delivery is via SMS. + * @param recipientId The Contact or User record ID + * @param roleName The Docusign template role name (must match exactly) + * @param routingOrder Signing order * @param sourceRecordId The source Client_Case__c record ID + * @param smsPhone Optional phone number for SMS delivery. Null for email delivery. * @return dfsle.Recipient configured for the role */ - private static dfsle.Recipient buildRecipient(Id recipientId, String roleName, Integer routingOrder, String sourceRecordId) { + private static dfsle.Recipient buildRecipient(Id recipientId, String roleName, Integer routingOrder, String sourceRecordId, String smsPhone) { // Determine if this is a Contact or User String objectType = recipientId.getSObjectType().getDescribe().getName(); @@ -320,17 +341,30 @@ global with sharing class DocusignCompositeEnvelopeBuilder { } if (String.isBlank(recipientEmail)) { - throw new IllegalArgumentException('No email found for ' + roleName + ' (' + recipientName + '). ' - + 'Please ensure the recipient has a valid email address.'); + if (String.isNotBlank(smsPhone)) { + // SMS delivery requested — substitute placeholder email so the dfsle + // Toolkit can create the recipient. Actual delivery is via SMS only. + recipientEmail = SMS_PLACEHOLDER_EMAIL; + } else { + throw new IllegalArgumentException('No email found for ' + roleName + ' (' + recipientName + '). ' + + 'Please ensure the recipient has a valid email address.'); + } } - return dfsle.Recipient.fromSource( + dfsle.Recipient recipient = dfsle.Recipient.fromSource( recipientName, recipientEmail, - null, // phone (optional) + null, // phone (not used — SMS delivery set below) roleName, // must match template role exactly new dfsle.Entity(sourceRecordId) // source record for merge fields ); + + // Enable SMS delivery when a phone number is provided + if (String.isNotBlank(smsPhone)) { + recipient = recipient.withSmsDelivery(smsPhone); + } + + return recipient; } private static void logResult(Integer templateCount, String envelopeId, String status, String errorMessage) { diff --git a/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls b/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls index 84c074d..d1fa32a 100644 --- a/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls +++ b/composite-envelope-builder/force-app/main/default/classes/DocusignEnvelopeRequest.cls @@ -39,4 +39,11 @@ global class DocusignEnvelopeRequest { required=false ) global Integer authReleaseFormCopies; + + @InvocableVariable( + label='Recipient SMS Phone' + description='Mobile phone number for SMS delivery when the primary recipient (Docusign Recipient #1) has no email address. Uses dfsle Recipient.withSmsDelivery(). A placeholder email is substituted automatically so the envelope can be created. Format: +15551234567 (E.164 preferred).' + required=false + ) + global String recipientSmsPhone; } diff --git a/composite-envelope-builder/force-app/main/default/flows/Docusign_Envelope_Templates_V4.flow-meta.xml b/composite-envelope-builder/force-app/main/default/flows/Docusign_Envelope_Templates_V4.flow-meta.xml new file mode 100644 index 0000000..356db02 --- /dev/null +++ b/composite-envelope-builder/force-app/main/default/flows/Docusign_Envelope_Templates_V4.flow-meta.xml @@ -0,0 +1,765 @@ + + + + Send_Composite_Envelope + + 182 + 1082 + DocusignCompositeEnvelopeBuilder + apex + + Check_Envelope_Result + + Automatic + + templateIds + + compositeTemplateIds + + + + recordId + + recordId + + + + language + + Get_Records.Docusign_Envelope_Language__c + + + + authReleaseFormCopies + + authReleaseFormCopies + + + + recipientSmsPhone + + recipientSmsPhone + + + DocusignCompositeEnvelopeBuilder + 0 + + envelopeId + envelopeId + + + envelopeSuccess + success + + + envelopeErrorMessage + errorMessage + + + 60.0 + false + + Add_Template_ID + + 270 + 1106 + + compositeTemplateIds + Add + + Build_Template_ID_Collection.dfsle__DocuSignId__c + + + + Build_Template_ID_Collection + + + + Flag_Auth_Release_Selected + + 270 + 1000 + + authReleaseTemplateSelected + Assign + + true + + + + Scan_For_Auth_Release_Template + + + + Store_Auth_Release_Copies + + 182 + 1160 + + authReleaseFormCopies + Assign + + authReleaseFormCopies_Radio + + + + Build_Template_ID_Collection + + + + Check_Envelope_Result + + 182 + 1190 + + Error_Screen + + Default Outcome + + Envelope_Sent_Successfully + and + + envelopeSuccess + EqualTo + + true + + + + Success_Screen + + + + + + Check_Row_Selection + + 380 + 674 + + Row_not_selected + + Default Outcome + + Is_Row_Selected + and + + data.firstSelectedRow.Id + IsNull + + false + + + + Scan_For_Auth_Release_Template + + + + + + Is_Auth_Release_Selected + + 380 + 890 + + Build_Template_ID_Collection + + No - Proceed + + Auth_Release_Template_Found + and + + authReleaseTemplateSelected + EqualTo + + true + + + + Authorization_Copies_Screen + + + + + + Is_Language_Selected + + 611 + 458 + + Language_Not_Added_Screen + + Default Outcome + + Language_Selected + and + + Get_Records.Docusign_Envelope_Language__c + IsNull + + false + + + + Language_Warning_Screen + + + + + + Does_Row_Contain_Auth_Release + + 270 + 890 + + Scan_For_Auth_Release_Template + + No + + Row_Is_Auth_Release_Template + and + + Scan_For_Auth_Release_Template.Name + Contains + + Authorization to Release Information + + + + Flag_Auth_Release_Selected + + + + + + Get_Recipient_Contact + + 611 + 242 + true + + Is_Recipient_Email_Blank + + and + + Id + EqualTo + + Get_Records.Docusign_Recipient_1__c + + + true + Contact + Id + Email + Name + true + + + Is_Recipient_Email_Blank + + 611 + 350 + + Is_Language_Selected + + Has Email - Continue + + Recipient_Has_No_Email + or + + Get_Recipient_Contact.Email + IsNull + + true + + + + Get_Recipient_Contact.Email + EqualTo + + + + + + SMS_Phone_Screen + + + + + + SMS_Phone_Screen + + 842 + 458 + false + true + false + + Is_Language_Selected + + + SMS_Instructions_Text + <p>The selected recipient ({!Get_Recipient_Contact.Name}) does not have an email address on file. The envelope will be delivered via <strong>SMS text message</strong> instead.</p><p>Please enter the recipient&apos;s mobile phone number in E.164 format (e.g. <strong>+15551234567</strong>).</p> + DisplayText + + + recipientSmsPhone_Input + String + Recipient Mobile Phone Number + InputField + true + + recipientSmsPhone + value + + 0 + + Next + true + true + + Default + Docusign Envelope Templates V4 {!$Flow.CurrentDateTime} + + + Build_Template_ID_Collection + + 182 + 1214 + data.selectedRows + Asc + + Add_Template_ID + + + Send_Composite_Envelope + + + + Scan_For_Auth_Release_Template + + 380 + 782 + data.selectedRows + Asc + + Does_Row_Contain_Auth_Release + + + Is_Auth_Release_Selected + + + + BuilderType + + LightningFlowBuilder + + + + CanvasMode + + AUTO_LAYOUT_CANVAS + + + + OriginBuilderType + + LightningFlowBuilder + + + Flow + + DocuSign_Envelope_Templates + + 380 + 458 + false + + Envelope_template_records + + and + + Envelope_Template_Language__c + EqualTo + + Get_Records.Docusign_Envelope_Language__c + + + + Short_Name__c + IsNull + + false + + + false + dfsle__EnvelopeConfiguration__c + Id + Name + dfsle__DocuSignId__c + Name + Asc + true + + + Get_Records + + 611 + 134 + false + + Get_Recipient_Contact + + and + + Id + EqualTo + + recordId + + + true + Client_Case__c + Id + Docusign_Envelope_Language__c + Docusign_Recipient_1__c + true + + + Envelope_template_records + + 380 + 566 + true + true + false + Back + + Check_Row_Selection + + + data + + T + dfsle__EnvelopeConfiguration__c + + flowruntime:datatable + ComponentInstance + + label + + Select Templates for Composite Envelope + + + + selectionMode + + MULTI_SELECT + + + + minRowSelection + + 0.0 + + + + tableData + + DocuSign_Envelope_Templates + + + + columns + + [{"apiName":"Name","guid":"column-6d57","editable":false,"hasCustomHeaderLabel":true,"customHeaderLabel":"Envelope Template Name","wrapText":true,"order":0,"label":"Name","type":"text"}] + + + UseStoredValues + true + true + + + top + + + 12 + + + + Send + true + true + + + Authorization_Copies_Screen + + 182 + 1106 + true + true + false + Back + + Store_Auth_Release_Copies + + + AuthCopiesHeader + <p>The <strong>Authorization to Release Information</strong> form was selected.</p><p>How many copies of this form should be included in the envelope?</p> + DisplayText + + + top + + + 12 + + + + + authReleaseFormCopies_Radio + AuthCopies_1 + AuthCopies_2 + AuthCopies_3 + Number + AuthCopies_1 + Number of Copies + RadioButtons + true + 0 + + + top + + + 12 + + + + Next + true + true + + + Error_Screen + + 314 + 1298 + true + true + false + Back + + ErrorDisplayMessage + <p><span style="font-size: 16px; color: rgb(255, 0, 0);">❌ Failed to send composite envelope.</span></p><p><br></p><p><strong>Error:</strong> {!envelopeErrorMessage}</p><p><br></p><p>Please try again or contact your administrator.</p> + DisplayText + + + top + + + 12 + + + + true + false + + + Language_Not_Added_Screen + + 842 + 350 + false + true + false + + LanguageNotSelected + <p>The <strong>DocuSign Envelope Language</strong> is not populated on the record. Please add the language first and then proceed.</p> + DisplayText + + + top + + + 12 + + + + true + true + + + Language_Warning_Screen + + 380 + 350 + false + true + false + + DocuSign_Envelope_Templates + + + LangWarningText + <p>The current selected language is <strong>{!Get_Records.Docusign_Envelope_Language__c}. </strong>On the next screen you will be able to see form names of {!Get_Records.Docusign_Envelope_Language__c} language only. If you want to switch the language, please go back to record and select another language form <strong>DocuSign Envelope Language</strong>.</p> + DisplayText + + + top + + + 12 + + + + Next + true + true + + + Row_not_selected + + 578 + 782 + true + true + false + Back + + ErrorMessage + <p><strong style="background-color: rgb(255, 255, 255); color: rgb(68, 68, 68);"><em>You have not selected any of the forms. Please go back and select the form first and then proceed.</em></strong></p> + DisplayText + + + top + + + 12 + + + + true + false + + + Success_Screen + + 50 + 1298 + false + true + false + + SuccessMessage + <p><span style="font-size: 16px; color: rgb(0, 128, 0);">✅ Composite envelope sent successfully!</span></p><p><br></p><p><strong>Envelope ID:</strong> {!envelopeId}</p><p><strong>Templates combined:</strong> All selected templates were merged into a single envelope.</p> + DisplayText + + + top + + + 12 + + + + true + false + + + 485 + 0 + + Get_Records + + + Draft + + compositeTemplateIds + String + true + false + false + + + envelopeErrorMessage + String + false + false + false + + + envelopeId + String + false + false + false + + + envelopeSuccess + Boolean + false + false + false + + + recordId + String + false + true + false + + + authReleaseFormCopies + Number + false + false + false + 0 + + 1.0 + + + + authReleaseTemplateSelected + Boolean + false + false + false + + false + + + + AuthCopies_1 + 1 copy + Number + + 1.0 + + + + AuthCopies_2 + 2 copies + Number + + 2.0 + + + + AuthCopies_3 + 3 copies + Number + + 3.0 + + + + recipientSmsPhone + String + false + false + false + +