This commit is contained in:
2026-08-21 16:38:06 +00:00
commit 43d764f9bc
31 changed files with 1911 additions and 0 deletions
@@ -0,0 +1,243 @@
public inherited sharing class CreateEmailToCase {
public class Request {
@InvocableVariable(required=true label='Routing Name')
public String routingName;
@InvocableVariable(required=true label='Email Address')
public String emailAddress;
@InvocableVariable(required=true label='Case Owner Queue Id')
public Id caseOwnerId;
@InvocableVariable(required=true label='Case Origin')
public String caseOrigin;
}
public class Result {
@InvocableVariable(label='Metadata Full Name')
public String metadataFullName;
@InvocableVariable(label='Routing Name')
public String routingName;
@InvocableVariable(label='Email Address')
public String emailAddress;
@InvocableVariable(label='Email-to-Case Address')
public String emailToCaseAddress;
@InvocableVariable(label='Email Routing Address Id')
public Id emailRoutingAddressId;
@InvocableVariable(label='Created')
public Boolean created;
}
@InvocableMethod(
label='Create Email-To-Case'
description='Adds an on-demand Email-to-Case routing address to Case Settings.'
category='Queue Configuration'
callout=true
)
public static List<Result> createRoutingAddress(List<Request> requests) {
MetadataXml.require(
requests != null && requests.size() == 1,
'Create Email-To-Case accepts exactly one request per invocation.'
);
Request input = requests[0];
input.routingName = input.routingName == null
? null
: input.routingName.trim();
input.emailAddress = input.emailAddress == null
? null
: input.emailAddress.trim();
input.caseOrigin = input.caseOrigin == null
? null
: input.caseOrigin.trim();
MetadataXml.require(
!String.isBlank(input.routingName),
'Routing Name is required.'
);
MetadataXml.require(
!String.isBlank(input.emailAddress),
'Email Address is required.'
);
MetadataXml.require(
input.caseOwnerId != null,
'Case Owner Queue Id is required.'
);
MetadataXml.require(
!String.isBlank(input.caseOrigin),
'Case Origin is required.'
);
List<Group> queues = [
SELECT Id, DeveloperName
FROM Group
WHERE Id = :input.caseOwnerId AND Type = 'Queue'
WITH USER_MODE
LIMIT 1
];
MetadataXml.require(
!queues.isEmpty(),
'Case Owner Id must identify a queue.'
);
Dom.XmlNode caseSettings = MetadataApiClient.readMetadata(
'CaseSettings',
'Case'
);
MetadataXml.require(
caseSettings != null,
'Case Settings metadata was not found.'
);
Dom.XmlNode emailToCase = MetadataXml.child(caseSettings, 'emailToCase');
MetadataXml.require(
emailToCase != null,
'Email-to-Case is not configured. Enable Email-to-Case in Setup before creating a routing address.'
);
for (
Dom.XmlNode route : MetadataXml.children(emailToCase, 'routingAddresses')
) {
String existingName = MetadataXml.text(route, 'routingName');
String existingAddress = MetadataXml.text(route, 'emailAddress');
Boolean sameName = input.routingName.equalsIgnoreCase(existingName);
Boolean sameAddress = input.emailAddress.equalsIgnoreCase(
existingAddress
);
if (sameName || sameAddress) {
MetadataXml.require(
sameName && sameAddress,
'An Email-to-Case routing address already uses this routing name or email address with different values.'
);
return new List<Result>{ buildResult(input, false) };
}
}
String routeXml =
'<routingAddresses>' +
'<addressType>EmailToCase</addressType>' +
'<caseOrigin>' +
MetadataXml.escape(input.caseOrigin) +
'</caseOrigin>' +
'<caseOwner>' +
MetadataXml.escape(queues[0].DeveloperName) +
'</caseOwner>' +
'<caseOwnerType>Queue</caseOwnerType>' +
'<casePriority>Medium</casePriority>' +
'<emailAddress>' +
MetadataXml.escape(input.emailAddress) +
'</emailAddress>' +
'<routingName>' +
MetadataXml.escape(input.routingName) +
'</routingName>' +
'<saveEmailHeaders>false</saveEmailHeaders>' +
'</routingAddresses>';
String emailToCaseXml = '<emailToCase>';
Boolean inserted = false;
for (Dom.XmlNode child : emailToCase.getChildElements()) {
if (!inserted && child.getName().compareTo('routingAddresses') > 0) {
emailToCaseXml += routeXml;
inserted = true;
}
emailToCaseXml += MetadataXml.serialize(child);
}
if (!inserted) {
emailToCaseXml += routeXml;
}
emailToCaseXml += '</emailToCase>';
MetadataApiClient.updateMetadata(
'CaseSettings',
MetadataXml.replaceOrInsertChild(
caseSettings,
'emailToCase',
emailToCaseXml
)
);
return new List<Result>{ buildResult(input, true) };
}
private static Result buildResult(Request input, Boolean created) {
Result result = new Result();
result.metadataFullName = 'Case';
result.routingName = input.routingName;
result.emailAddress = input.emailAddress;
result.created = created;
Schema.SObjectType routingType = Schema.getGlobalDescribe()
.get('EmailRoutingAddress');
if (routingType == null) {
return result;
}
Map<String, Schema.SObjectField> fields = routingType.getDescribe()
.fields.getMap();
String addressField = fields.containsKey('EmailAddress')
? 'EmailAddress'
: (fields.containsKey('Address') ? 'Address' : null);
if (addressField == null) {
return result;
}
List<String> selectFields = new List<String>{ 'Id', addressField };
if (fields.containsKey('RoutingAddress')) {
selectFields.add('RoutingAddress');
}
if (fields.containsKey('EmailServicesAddressId')) {
selectFields.add('EmailServicesAddressId');
}
String query =
'SELECT ' +
String.join(selectFields, ',') +
' FROM EmailRoutingAddress WHERE ' +
addressField +
' = :emailAddress LIMIT 1';
List<SObject> matches = Database.queryWithBinds(
query,
new Map<String, Object>{ 'emailAddress' => input.emailAddress },
AccessLevel.USER_MODE
);
if (matches.isEmpty()) {
return result;
}
result.emailRoutingAddressId = (Id) matches[0].get('Id');
if (fields.containsKey('RoutingAddress')) {
result.emailToCaseAddress = (String) matches[0].get('RoutingAddress');
} else if (fields.containsKey('EmailServicesAddressId')) {
result.emailToCaseAddress = emailServicesAddress(
(Id) matches[0].get('EmailServicesAddressId')
);
}
return result;
}
private static String emailServicesAddress(Id serviceAddressId) {
if (serviceAddressId == null) {
return null;
}
Schema.SObjectType serviceType = Schema.getGlobalDescribe()
.get('EmailServicesAddress');
if (serviceType == null) {
return null;
}
Map<String, Schema.SObjectField> fields = serviceType.getDescribe()
.fields.getMap();
if (
!fields.containsKey('LocalPart') || !fields.containsKey('EmailDomainName')
) {
return null;
}
List<SObject> rows = Database.queryWithBinds(
'SELECT LocalPart, EmailDomainName FROM EmailServicesAddress WHERE Id = :serviceAddressId LIMIT 1',
new Map<String, Object>{ 'serviceAddressId' => serviceAddressId },
AccessLevel.USER_MODE
);
return rows.isEmpty()
? null
: String.valueOf(rows[0].get('LocalPart')) +
'@' +
String.valueOf(rows[0].get('EmailDomainName'));
}
}