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 createRoutingAddress(List 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 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{ buildResult(input, false) }; } } String routeXml = '' + 'EmailToCase' + '' + MetadataXml.escape(input.caseOrigin) + '' + '' + MetadataXml.escape(queues[0].DeveloperName) + '' + 'Queue' + 'Medium' + '' + MetadataXml.escape(input.emailAddress) + '' + '' + MetadataXml.escape(input.routingName) + '' + 'false' + ''; String emailToCaseXml = ''; 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 += ''; MetadataApiClient.updateMetadata( 'CaseSettings', MetadataXml.replaceOrInsertChild( caseSettings, 'emailToCase', emailToCaseXml ) ); return new List{ 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 fields = routingType.getDescribe() .fields.getMap(); String addressField = fields.containsKey('EmailAddress') ? 'EmailAddress' : (fields.containsKey('Address') ? 'Address' : null); if (addressField == null) { return result; } List selectFields = new List{ '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 matches = Database.queryWithBinds( query, new Map{ '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 fields = serviceType.getDescribe() .fields.getMap(); if ( !fields.containsKey('LocalPart') || !fields.containsKey('EmailDomainName') ) { return null; } List rows = Database.queryWithBinds( 'SELECT LocalPart, EmailDomainName FROM EmailServicesAddress WHERE Id = :serviceAddressId LIMIT 1', new Map{ 'serviceAddressId' => serviceAddressId }, AccessLevel.USER_MODE ); return rows.isEmpty() ? null : String.valueOf(rows[0].get('LocalPart')) + '@' + String.valueOf(rows[0].get('EmailDomainName')); } }