feat(sms): add SMS delivery for recipients without email via direct REST API

- New DocusignSmsEnvelopeService: bypasses dfsle Toolkit to POST composite
  envelope JSON directly to Docusign REST API; sets additionalNotifications
  with secondaryDeliveryMethod=SMS and phoneNumber on the primary recipient
- DocusignEnvelopeRequest: new recipientSmsPhone InvocableVariable (E.164)
- DocusignCompositeEnvelopeBuilder: routes to DocusignSmsEnvelopeService when
  recipientSmsPhone is present; adds SMS_FALLBACK_EMAIL constant; buildRecipient
  substitutes placeholder email for no-email primary recipients in SMS path
- Flow (V3): Get_Records now fetches Docusign_Recipient_1__c; new
  Get_Recipient_Contact lookup checks Contact.Email; Is_Recipient_Email_Blank
  decision routes to SMS_Required_Screen when email is absent; phone number
  collected via required text input and passed as recipientSmsPhone to Apex
This commit is contained in:
Paul Huliganga 2026-03-12 23:48:17 -04:00
parent 86e7d2fb62
commit ac6de33317
5 changed files with 597 additions and 14 deletions

View File

@ -29,6 +29,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 and SMS delivery is requested.
// Docusign requires an email on every recipient even when delivery
// is via SMS; this constant satisfies that requirement without
// routing any actual email. Update this value if your org uses a
// different placeholder address.
// ============================================================
@TestVisible
private static final String SMS_FALLBACK_EMAIL = 'placeholder_email@docusign.com';
@InvocableMethod(
label='Send Composite Docusign Envelope'
description='Combines multiple Docusign templates into a single envelope using dfsle Apex Toolkit'
@ -188,7 +199,7 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
}
// Resolve recipients from Client_Case__c lookup fields
List<dfsle.Recipient> recipients = resolveRecipients(req.recordId);
List<dfsle.Recipient> recipients = resolveRecipients(req.recordId, req.recipientSmsPhone);
myEnvelope = myEnvelope.withRecipients(recipients);
// Set envelope subject to combined display names (deduplicated, with copy counts).
@ -222,11 +233,25 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
String envelopeBody = bodyParts.isEmpty() ? '' : String.join(bodyParts, '\n\n');
myEnvelope = myEnvelope.withEmail(envelopeSubject, envelopeBody);
// Send the envelope
// Send the envelope.
// When a recipient SMS phone is supplied we bypass the dfsle Toolkit entirely
// because it cannot set additionalNotifications for SMS delivery.
// DocusignSmsEnvelopeService posts directly to the Docusign REST API instead.
if (String.isNotBlank(req.recipientSmsPhone)) {
String envelopeId = DocusignSmsEnvelopeService.sendEnvelope(
req.recordId,
sortedIds,
docNames,
displayNames,
envelopeSubject,
envelopeBody,
req.recipientSmsPhone
);
result.envelopeId = envelopeId;
} else {
myEnvelope = dfsle.EnvelopeService.sendEnvelope(myEnvelope, true);
// Success
result.envelopeId = String.valueOf(myEnvelope.docuSignId);
}
result.success = true;
result.errorMessage = null;
@ -256,9 +281,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 they have no email.
* When non-blank, buildRecipient will substitute SMS_FALLBACK_EMAIL
* for the Docusign Recipient #1 role instead of throwing an error.
* (Only relevant for the dfsle path the SMS service resolves its own recipients.)
* @return List of dfsle.Recipient objects with role mappings
*/
private static List<dfsle.Recipient> resolveRecipients(String recordId) {
private static List<dfsle.Recipient> 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, '
@ -274,13 +303,13 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
// Recipient 1: Service Coordinator
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
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()) {
@ -297,9 +326,14 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
* @param roleName The Docusign template role name
* @param routingOrder Signing order
* @param sourceRecordId The source Client_Case__c record ID
* @param smsPhone Optional SMS phone number for the Docusign Recipient #1 role.
* When non-blank and the recipient has no email, SMS_FALLBACK_EMAIL
* is substituted so the dfsle Toolkit call can proceed.
* (The actual SMS delivery notification is handled separately by
* DocusignSmsEnvelopeService this path is a safety fallback only.)
* @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,9 +354,15 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
}
if (String.isBlank(recipientEmail)) {
if (roleName == ROLE_DOCUSIGN_RECIPIENT && String.isNotBlank(smsPhone)) {
// Recipient has no email but SMS delivery is requested — substitute
// the placeholder email so the dfsle Toolkit call does not throw.
recipientEmail = SMS_FALLBACK_EMAIL;
} else {
throw new IllegalArgumentException('No email found for ' + roleName + ' (' + recipientName + '). '
+ 'Please ensure the recipient has a valid email address.');
}
}
return dfsle.Recipient.fromSource(
recipientName,

View File

@ -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. E.164 format preferred (e.g. +15551234567). When provided, the envelope is sent via direct Docusign REST API instead of the dfsle Toolkit so that additionalNotifications/SMS delivery can be set.'
required=false
)
global String recipientSmsPhone;
}

View File

@ -0,0 +1,414 @@
/**
* @description Sends a Docusign composite envelope via direct REST API call when the
* primary recipient (Docusign Recipient #1) does not have an email address
* and requires SMS delivery instead.
*
* The dfsle Apex Toolkit does not support the Docusign
* additionalNotifications/secondaryDeliveryMethod property needed for
* SMS-only recipients, so this service bypasses the toolkit and calls
* the Docusign REST API directly.
*
* Prerequisites:
* - Named Credential "DocusignAPI" must be configured in the org.
* - Docusign account must have SMS delivery enabled.
* - Docusign_Configuration__c custom setting must have Account_Id__c populated.
*
* @author Paul Huliganga
* @date 2026-02-25
*/
global with sharing class DocusignSmsEnvelopeService {
// Named Credential for Docusign REST API callouts
@TestVisible
private static final String NAMED_CREDENTIAL = 'callout:DocusignAPI';
// Endpoint path template — {0} is replaced with the accountId
@TestVisible
private static final String ENVELOPES_PATH = '/accounts/{0}/envelopes';
// Dummy email used for SMS-only recipients. Docusign requires an email address
// on every recipient even when delivery is via SMS. This placeholder satisfies
// that requirement without routing any actual email.
@TestVisible
private static final String SMS_PLACEHOLDER_EMAIL = 'placeholder_email@docusign.com';
// Country code assumed when the caller supplies a bare 10-digit US number.
// The flow enforces E.164 format (e.g. +15551234567) so this is a safety fallback.
private static final String DEFAULT_COUNTRY_CODE = '1';
/**
* @description Builds and sends a composite Docusign envelope with SMS delivery for
* the primary recipient. Mirrors the logic in DocusignCompositeEnvelopeBuilder
* but constructs the JSON payload manually so that additionalNotifications
* can be included on the recipient object.
*
* @param recordId The Client_Case__c record ID (used to look up recipients)
* @param sortedIds Template IDs in sorted order (may contain duplicates for multi-copy)
* @param docNames Document labels in same order as sortedIds
* @param displayNames Deduplicated display labels for email subject / body
* @param envelopeSubject Email subject line (already formatted and truncated)
* @param envelopeBody Email body text (already formatted and deduplicated)
* @param smsPhone Mobile phone number for SMS delivery (E.164 preferred, e.g. +15551234567)
* @return Docusign envelope ID string on success
* @throws IllegalArgumentException when recipients or configuration data are missing
* @throws CalloutException on HTTP errors
*/
global static String sendEnvelope(
String recordId,
List<String> sortedIds,
List<String> docNames,
List<String> displayNames,
String envelopeSubject,
String envelopeBody,
String smsPhone
) {
// ─── Fetch Docusign account ID from custom setting ───────────────────────────
Docusign_Configuration__c config = Docusign_Configuration__c.getInstance();
if (config == null || String.isBlank(config.Account_Id__c)) {
throw new IllegalArgumentException(
'Docusign_Configuration__c is not configured. '
+ 'Please set Account_Id__c in the Docusign Configuration custom setting.'
);
}
String accountId = config.Account_Id__c;
// ─── Resolve recipients from Client_Case__c ───────────────────────────────────
RecipientInfo serviceCoordinator = null;
RecipientInfo docusignRecipient = null;
String query = 'SELECT Id, Service_Coordinator__c, Docusign_Recipient_1__c '
+ 'FROM Client_Case__c WHERE Id = :recordId LIMIT 1';
Client_Case__c caseRecord = Database.query(query);
Id scId = (Id) caseRecord.get('Service_Coordinator__c');
Id drId = (Id) caseRecord.get('Docusign_Recipient_1__c');
if (scId != null) {
serviceCoordinator = resolveRecipient(scId, 'Service Coordinator', 1, null, false);
}
if (drId != null) {
// SMS phone provided — substitute placeholder email for the Docusign Recipient
docusignRecipient = resolveRecipient(drId, 'Docusign Recipient #1', 2, smsPhone, true);
}
if (serviceCoordinator == null && docusignRecipient == null) {
throw new IllegalArgumentException(
'No recipients found on the Client Case record. '
+ 'Please ensure Service Coordinator and Docusign Recipient #1 are populated.'
);
}
// ─── Build JSON body ─────────────────────────────────────────────────────────
String jsonBody = buildEnvelopeJson(
sortedIds,
docNames,
envelopeSubject,
envelopeBody,
serviceCoordinator,
docusignRecipient,
recordId
);
// ─── Call the Docusign REST API ───────────────────────────────────────────────
String endpoint = NAMED_CREDENTIAL
+ String.format(ENVELOPES_PATH, new List<Object>{ accountId });
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint(endpoint);
httpReq.setMethod('POST');
httpReq.setHeader('Content-Type', 'application/json');
httpReq.setHeader('Accept', 'application/json');
httpReq.setBody(jsonBody);
Http http = new Http();
HttpResponse httpResp = http.send(httpReq);
Integer statusCode = httpResp.getStatusCode();
String responseBody = httpResp.getBody();
if (statusCode == 200 || statusCode == 201) {
// Parse envelopeId from response
Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(responseBody);
Object envelopeIdObj = responseMap.get('envelopeId');
if (envelopeIdObj == null) {
throw new CalloutException(
'Docusign API returned success but no envelopeId in response: ' + responseBody
);
}
return String.valueOf(envelopeIdObj);
} else {
// Try to extract a meaningful error message from the response JSON
String errorDetail = parseDocusignError(responseBody);
throw new CalloutException(
'Docusign API returned HTTP ' + statusCode + ': ' + errorDetail
);
}
}
// ─────────────────────────────────────────────────────────────────────────────────
// PRIVATE HELPERS
// ─────────────────────────────────────────────────────────────────────────────────
/**
* @description Resolves a recipient's name and email from a Contact or User record.
* When applySmsPlaceholder is true and the record has no email,
* the SMS_PLACEHOLDER_EMAIL constant is used instead.
*/
private static RecipientInfo resolveRecipient(
Id recipientId,
String roleName,
Integer routingOrder,
String smsPhone,
Boolean applySmsPlaceholder
) {
String objectType = recipientId.getSObjectType().getDescribe().getName();
String name;
String email;
if (objectType == 'Contact') {
Contact c = [SELECT Id, Name, Email FROM Contact WHERE Id = :recipientId LIMIT 1];
name = c.Name;
email = c.Email;
} else if (objectType == 'User') {
User u = [SELECT Id, Name, Email FROM User WHERE Id = :recipientId LIMIT 1];
name = u.Name;
email = u.Email;
} else {
throw new IllegalArgumentException(
'Unsupported recipient type: ' + objectType + '. Expected Contact or User.'
);
}
if (String.isBlank(email)) {
if (applySmsPlaceholder) {
email = SMS_PLACEHOLDER_EMAIL;
} else {
throw new IllegalArgumentException(
'No email found for ' + roleName + ' (' + name + '). '
+ 'Please ensure the recipient has a valid email address.'
);
}
}
RecipientInfo info = new RecipientInfo();
info.name = name;
info.email = email;
info.roleName = roleName;
info.routingOrder = routingOrder;
info.smsPhone = applySmsPlaceholder ? smsPhone : null;
return info;
}
/**
* @description Constructs the full envelope JSON payload for the Docusign REST API.
* Uses compositeTemplates so that all selected templates are merged into
* a single envelope, matching what the dfsle Toolkit does internally.
*
* Docusign compositeTemplates reference:
* https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopes/create/
*/
@TestVisible
private static String buildEnvelopeJson(
List<String> sortedIds,
List<String> docNames,
String envelopeSubject,
String envelopeBody,
RecipientInfo serviceCoordinator,
RecipientInfo docusignRecipient,
String sourceRecordId
) {
// Build the recipients object (shared across all compositeTemplates)
// Each recipient gets a unique recipientId (sequential string integer).
List<Object> signers = new List<Object>();
Integer recipientIdCounter = 1;
if (serviceCoordinator != null) {
signers.add(buildSignerMap(serviceCoordinator, String.valueOf(recipientIdCounter++)));
}
if (docusignRecipient != null) {
signers.add(buildSignerMap(docusignRecipient, String.valueOf(recipientIdCounter++)));
}
Map<String, Object> recipientsMap = new Map<String, Object>{
'signers' => signers
};
// Build compositeTemplates array — one entry per document (template ID + label)
List<Object> compositeTemplates = new List<Object>();
for (Integer i = 0; i < sortedIds.size(); i++) {
String templateId = sortedIds[i];
String docLabel = docNames[i];
Map<String, Object> serverTemplate = new Map<String, Object>{
'sequence' => '1',
'templateId' => templateId
};
// Inline template overrides — set the document label and wire up role assignments
List<Object> inlineRecipientSigners = new List<Object>();
Integer irCounter = 1;
if (serviceCoordinator != null) {
inlineRecipientSigners.add(new Map<String, Object>{
'recipientId' => String.valueOf(irCounter++),
'roleName' => serviceCoordinator.roleName,
'name' => serviceCoordinator.name,
'email' => serviceCoordinator.email,
'routingOrder' => String.valueOf(serviceCoordinator.routingOrder)
});
}
if (docusignRecipient != null) {
Map<String, Object> drSigner = new Map<String, Object>{
'recipientId' => String.valueOf(irCounter++),
'roleName' => docusignRecipient.roleName,
'name' => docusignRecipient.name,
'email' => docusignRecipient.email,
'routingOrder' => String.valueOf(docusignRecipient.routingOrder)
};
// Add SMS additionalNotifications for this recipient
if (String.isNotBlank(docusignRecipient.smsPhone)) {
String[] phoneParts = parsePhone(docusignRecipient.smsPhone);
drSigner.put('additionalNotifications', new List<Object>{
new Map<String, Object>{
'secondaryDeliveryMethod' => 'SMS',
'phoneNumber' => new Map<String, Object>{
'countryCode' => phoneParts[0],
'number' => phoneParts[1]
}
}
});
}
inlineRecipientSigners.add(drSigner);
}
Map<String, Object> inlineTemplate = new Map<String, Object>{
'sequence' => '2',
'recipients' => new Map<String, Object>{
'signers' => inlineRecipientSigners
},
'document' => new Map<String, Object>{
'name' => docLabel,
'documentId' => String.valueOf(i + 1),
'transformPdfFields' => 'true'
}
};
compositeTemplates.add(new Map<String, Object>{
'serverTemplates' => new List<Object>{ serverTemplate },
'inlineTemplates' => new List<Object>{ inlineTemplate },
'recipients' => recipientsMap
});
}
Map<String, Object> envelopeDefinition = new Map<String, Object>{
'status' => 'sent',
'emailSubject' => envelopeSubject,
'emailBlurb' => envelopeBody,
'compositeTemplates' => compositeTemplates
};
return JSON.serialize(envelopeDefinition);
}
/**
* @description Builds the signer map for a recipient in the envelope-level recipients object.
* Adds additionalNotifications for SMS recipients.
*/
private static Map<String, Object> buildSignerMap(RecipientInfo info, String recipientId) {
Map<String, Object> signer = new Map<String, Object>{
'recipientId' => recipientId,
'name' => info.name,
'email' => info.email,
'roleName' => info.roleName,
'routingOrder' => String.valueOf(info.routingOrder)
};
if (String.isNotBlank(info.smsPhone)) {
String[] phoneParts = parsePhone(info.smsPhone);
signer.put('additionalNotifications', new List<Object>{
new Map<String, Object>{
'secondaryDeliveryMethod' => 'SMS',
'phoneNumber' => new Map<String, Object>{
'countryCode' => phoneParts[0],
'number' => phoneParts[1]
}
}
});
}
return signer;
}
/**
* @description Parses a phone number into [countryCode, nationalNumber].
* Accepts E.164 format (+15551234567) or a bare 10-digit number.
* Returns [DEFAULT_COUNTRY_CODE, strippedNumber] for bare numbers.
*
* @param phone Raw phone string from the flow input
* @return String array: index 0 = country code, index 1 = national number (digits only)
*/
@TestVisible
private static String[] parsePhone(String phone) {
if (String.isBlank(phone)) {
return new String[]{ DEFAULT_COUNTRY_CODE, '' };
}
String stripped = phone.replaceAll('[^\\d+]', ''); // keep digits and leading +
if (stripped.startsWith('+')) {
// E.164: determine country code length heuristically
// We only support 1-digit (+1) and 2-digit (+XX) country codes for now
String digits = stripped.substring(1); // remove leading +
if (digits.length() == 11 && digits.startsWith('1')) {
// +1XXXXXXXXXX (North American Numbering Plan)
return new String[]{ '1', digits.substring(1) };
} else if (digits.length() == 12) {
// +XXXXXXXXXXXX (2-digit country code + 10 digit number)
return new String[]{ digits.left(2), digits.substring(2) };
} else {
// Fall back: treat first digit(s) as country code not supported; use full digits
return new String[]{ DEFAULT_COUNTRY_CODE, digits };
}
}
// Bare digits — assume DEFAULT_COUNTRY_CODE
return new String[]{ DEFAULT_COUNTRY_CODE, stripped };
}
/**
* @description Attempts to extract a human-readable error message from a Docusign
* REST API error response JSON.
* @param responseBody Raw HTTP response body
* @return Error string with errorCode and message if available, otherwise raw body
*/
private static String parseDocusignError(String responseBody) {
if (String.isBlank(responseBody)) {
return '(no response body)';
}
try {
Map<String, Object> errorMap = (Map<String, Object>) JSON.deserializeUntyped(responseBody);
String errorCode = errorMap.containsKey('errorCode') ? String.valueOf(errorMap.get('errorCode')) : null;
String message = errorMap.containsKey('message') ? String.valueOf(errorMap.get('message')) : null;
if (String.isNotBlank(errorCode) || String.isNotBlank(message)) {
return (errorCode != null ? '[' + errorCode + '] ' : '') + (message != null ? message : '');
}
} catch (Exception ex) {
// Non-JSON response — return raw body below
}
return responseBody.left(500); // cap at 500 chars to avoid log spam
}
// ─────────────────────────────────────────────────────────────────────────────────
// INNER CLASSES
// ─────────────────────────────────────────────────────────────────────────────────
/**
* @description Lightweight holder for resolved recipient data.
*/
private class RecipientInfo {
String name;
String email;
String roleName;
Integer routingOrder;
String smsPhone; // null for email recipients; phone string for SMS recipients
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -35,6 +35,12 @@
<elementReference>authReleaseFormCopies</elementReference>
</value>
</inputParameters>
<inputParameters>
<name>recipientSmsPhone</name>
<value>
<elementReference>recipientSmsPhone</elementReference>
</value>
</inputParameters>
<nameSegment>DocusignCompositeEnvelopeBuilder</nameSegment>
<offset>0</offset>
<outputParameters>
@ -179,7 +185,7 @@
<name>Is_Language_Selected</name>
<label>Is Language Selected?</label>
<locationX>611</locationX>
<locationY>242</locationY>
<locationY>458</locationY>
<defaultConnector>
<targetReference>Language_Not_Added_Screen</targetReference>
</defaultConnector>
@ -225,6 +231,109 @@
<label>Yes</label>
</rules>
</decisions>
<recordLookups>
<name>Get_Recipient_Contact</name>
<label>Get Recipient Contact</label>
<locationX>611</locationX>
<locationY>242</locationY>
<assignNullValuesIfNoRecordsFound>true</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Is_Recipient_Email_Blank</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>Id</field>
<operator>EqualTo</operator>
<value>
<elementReference>Get_Records.Docusign_Recipient_1__c</elementReference>
</value>
</filters>
<getFirstRecordOnly>true</getFirstRecordOnly>
<object>Contact</object>
<queriedFields>Id</queriedFields>
<queriedFields>Email</queriedFields>
<storeOutputAutomatically>true</storeOutputAutomatically>
</recordLookups>
<decisions>
<name>Is_Recipient_Email_Blank</name>
<label>Is Recipient Email Blank?</label>
<locationX>611</locationX>
<locationY>350</locationY>
<defaultConnector>
<targetReference>Is_Language_Selected</targetReference>
</defaultConnector>
<defaultConnectorLabel>Has Email - Continue</defaultConnectorLabel>
<rules>
<name>Recipient_Has_No_Email</name>
<conditionLogic>or</conditionLogic>
<conditions>
<leftValueReference>Get_Recipient_Contact.Email</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<conditions>
<leftValueReference>Get_Recipient_Contact.Email</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<stringValue></stringValue>
</rightValue>
</conditions>
<connector>
<targetReference>SMS_Required_Screen</targetReference>
</connector>
<label>No Email - SMS Required</label>
</rules>
</decisions>
<screens>
<name>SMS_Required_Screen</name>
<label>SMS Delivery Required</label>
<locationX>842</locationX>
<locationY>458</locationY>
<allowBack>false</allowBack>
<allowFinish>true</allowFinish>
<allowPause>false</allowPause>
<connector>
<targetReference>Is_Language_Selected</targetReference>
</connector>
<fields>
<name>SmsRequiredNotice</name>
<fieldText>&lt;p&gt;⚠️ The primary recipient &lt;strong&gt;({!Get_Records.Docusign_Recipient_1__c})&lt;/strong&gt; does not have an email address on file.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;The DocuSign envelope will be delivered via &lt;strong&gt;SMS text message&lt;/strong&gt; instead. Please enter the recipient&apos;s mobile phone number below.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Include the country code in E.164 format, e.g. &lt;strong&gt;+15551234567&lt;/strong&gt; for a US number.&lt;/p&gt;</fieldText>
<fieldType>DisplayText</fieldType>
<styleProperties>
<verticalAlignment>
<stringValue>top</stringValue>
</verticalAlignment>
<width>
<stringValue>12</stringValue>
</width>
</styleProperties>
</fields>
<fields>
<name>recipientSmsPhone_Input</name>
<dataType>String</dataType>
<fieldText>Mobile Phone Number</fieldText>
<fieldType>InputField</fieldType>
<helpText>Enter the recipient&apos;s mobile phone number in E.164 format (e.g. +15551234567). The country code and + prefix are required for international numbers.</helpText>
<isRequired>true</isRequired>
<outputParameters>
<assignToReference>recipientSmsPhone</assignToReference>
<name>value</name>
</outputParameters>
<styleProperties>
<verticalAlignment>
<stringValue>top</stringValue>
</verticalAlignment>
<width>
<stringValue>6</stringValue>
</width>
</styleProperties>
</fields>
<nextOrFinishButtonLabel>Next</nextOrFinishButtonLabel>
<showFooter>true</showFooter>
<showHeader>true</showHeader>
</screens>
<environments>Default</environments>
<interviewLabel>Docusign Envelope Templates V3 {!$Flow.CurrentDateTime}</interviewLabel>
<label>Docusign Envelope Templates V3</label>
@ -315,7 +424,7 @@
<locationY>134</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Is_Language_Selected</targetReference>
<targetReference>Get_Recipient_Contact</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
@ -329,6 +438,7 @@
<object>Client_Case__c</object>
<queriedFields>Id</queriedFields>
<queriedFields>Docusign_Envelope_Language__c</queriedFields>
<queriedFields>Docusign_Recipient_1__c</queriedFields>
<storeOutputAutomatically>true</storeOutputAutomatically>
</recordLookups>
<screens>
@ -636,6 +746,13 @@
<booleanValue>false</booleanValue>
</value>
</variables>
<variables>
<name>recipientSmsPhone</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<choices>
<name>AuthCopies_1</name>
<choiceText>1 copy</choiceText>