7

As Email Services can't be packaged in a Managed Package, I allow subscribers to create it after the installation. The code which works in my packaging org fails in productions orgs with:

An Apex Class must be specified when creating an Email Service. (FIELD_INTEGRITY_EXCEPTION).

1) The Email Handler class EmailHandler is inside of the managed package (with namespace MYNS) BUT declared global to be callable from the unpackage Email Service:

global with sharing class EmailHandler implements Messaging.InboundEmailHandler {

    global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {

2) This is how I create the using the MDAPI lib:

MetadataService.EmailServicesFunction result = new MetadataService.EmailServicesFunction();
result.fullName = 'AppErrorInbox';
result.apexClass = 'MYNS.EmailHandler';
result.functionName = 'App Error Inbox';
result.isActive = true;
result.attachmentOption = 'None';
result.authenticationFailureAction = 'Discard';
result.authorizationFailureAction = 'Discard';
result.functionInactiveAction = 'Discard';
result.isAuthenticationRequired = false;
result.isErrorRoutingEnabled = false;
result.isTextAttachmentsAsBinary = false;
result.isTlsRequired = false;
result.overLimitAction = 'Discard';
result.emailServicesAddresses = new List<MetadataService.EmailServicesAddress> { emailAddress() };

I also can reproduce this even without using a class but plain XML and the Workbench. I get the same error no matter if I use a dot or two underscores for the Apex Class name. If I leave it away I get a different message saying that a class with that name is not found.

4
  • Can you specify the apexClass using a dot instead of underscores to delimit the namespace prefix? – bigassforce May 26 '19 at 11:20
  • Tried that and I got the same error – Robert Sösemann May 26 '19 at 12:44
  • Long shot: deploy the emailServicesAddresses in a separate transaction? – bigassforce May 26 '19 at 13:47
  • @bigassforce Same error even if I leave the EmailAddresses out – Robert Sösemann May 27 '19 at 14:53
2
+50

It would appear that whatever mechanism is used behind EmailServicesFunction to verify the Apex class exists isn't taking into account Apex classes defined in an installed managed package.

As a possible work around try creating a wrapper Apex class in the subscriber org that extends your managed MYNS.EmailHandler class. Then configure EmailServicesFunction.apexClass to use this local namespace wrapper class. Yes, it is a bit of an ugly hack, but it might get things going while still allowing you to implement most of the handler code in the managed package.


While acknowledging that you want to automate all this from Apex. It might be worth trying the process manually with the UI via /email-admin/services/editEmailServicesFunction.apexp. I was able to select a managed Apex class in the UI. It didn't save because the the class didn't implement the Messaging.InboundEmailHandler interface, but it must have found the class to figure that out.


Finally, the SOAP version of EmailServicesFunction exposes ApexClassId. You could try the Partner SOAP API to create these record and use the more specific ID rather than the namespace and class name.

Expanding on this, I deployed the example class CreateTaskEmailExample that implements Messaging.InboundEmailHandler from Using the InboundEmail Object into my dev org. The resulting Apex class had the ID 01p0g000000KWccAAG.

Then with SOAP UI I POSTed the following to /services/Soap/u/36/00D700000000001:

Request

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com" xmlns:urn1="urn:sobject.partner.soap.sforce.com">
   <soapenv:Header>
      <urn:SessionHeader>
         <urn:sessionId>00D700000000001!AQoAQGfuT.NotMyRealSessionId.Re2s3N2Ihtqfr</urn:sessionId>
      </urn:SessionHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:create>
         <urn:sObjects>
            <urn1:type>EmailServicesFunction</urn1:type>
            <urn1:fieldsToNull></urn1:fieldsToNull>
            <urn1:ApexClassId>01p0g000000KWccAAG</urn1:ApexClassId>
            <urn1:FunctionName>CreateTaskEmailExample</urn1:FunctionName>
         </urn:sObjects>
      </urn:create>
   </soapenv:Body>
</soapenv:Envelope>

Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com">
   <soapenv:Header>
      <LimitInfoHeader>
         <limitInfo>
            <current>314</current>
            <limit>15000</limit>
            <type>API REQUESTS</type>
         </limitInfo>
      </LimitInfoHeader>
   </soapenv:Header>
   <soapenv:Body>
      <createResponse>
         <result>
            <id>0910g000000L0m9AAC</id>
            <success>true</success>
         </result>
      </createResponse>
   </soapenv:Body>
</soapenv:Envelope>

So that worked solely with the Apex class ID and a FunctionName.

I was able to make the same callout directly from Apex:

string sessionId = UserInfo.getSessionId();
HttpRequest req = new HttpRequest();
req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/Soap/u/36.0');
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml; charset=UTF-8');
req.setHeader('SOAPAction', 'Wololo');
req.setBody('<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:urn="urn:partner.soap.sforce.com" xmlns:urn1="urn:sobject.partner.soap.sforce.com"><env:Header><SessionHeader xmlns="urn:partner.soap.sforce.com"><sessionId>'+sessionId+'</sessionId></SessionHeader></env:Header><env:Body><urn:create><urn:sObjects><urn1:type>EmailServicesFunction</urn1:type><urn1:fieldsToNull></urn1:fieldsToNull><urn1:ApexClassId>01p0g000000KWccAAG</urn1:ApexClassId><urn1:FunctionName>CreateTaskEmailExample</urn1:FunctionName></urn:sObjects></urn:create></env:Body></env:Envelope>');
System.debug(req.getBody());
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());

That gave a successful response that showed the EmailServicesFunction record was created.

You could either directly parse the XML response in Apex, or use a tool to generated the Apex proxy classes for WebServiceCallout.invoke to do the SOAP calls for you. The FuseIT SFDC Explorer version of WSDL2Apex can do that for you if required, including just generating the required classes for that one method. (Disclaimer, this is a free tool from my current employer.)

5
  • Thanks Daniel for your time. Even if the wrapper thing works it won’t help me as I am just doing this to automate thing after the install. The ApexClassId hint sounds interesting but the FF mdapi wrapper doesn’t provide such a field. Any idea how to get that in? – Robert Sösemann May 28 '19 at 6:45
  • Oh now I get it. The SOAP API not the metadata API. I have no idea if and how to call that from an Apex class. Can you provide an example please? Or is there any neat wrapper API? – Robert Sösemann May 28 '19 at 7:22
  • 1
    It's not too bad to call directly from Apex with an HTTP callout. Or you could use the FuseIT version of WSDL2Apex to create wrapper classes that would be more like the Metadata API library if you wanted. – Daniel Ballinger May 28 '19 at 9:44
  • Super cool. Will try it out later. – Robert Sösemann May 28 '19 at 10:50
  • 1
    You saved my weekend. You rock! – Robert Sösemann May 28 '19 at 18:35
0

I attach an example that Works for me with MetadataService (without Managed Package):

   //init service
   MetadataService.MetadataPort service = new MetadataService.MetadataPort();
   service.SessionHeader = new MetadataService.SessionHeader_element();
   service.SessionHeader.sessionId = UserInfo.getSessionId();

    MetadataService.EmailServicesFunction fieldMdApi = new MetadataService.EmailServicesFunction();
    fieldMdApi.fullName = 'Email Example Test';
    fieldMdApi.apexClass = 'TestEmail'; //Implements Messaging.InboundEmailHandler {
    fieldMdApi.functionName = 'Email Example';
    fieldMdApi.attachmentOption = 'All';
    fieldMdApi.authenticationFailureAction = 'Discard';
    fieldMdApi.functionInactiveAction = 'Discard';
    fieldMdApi.overLimitAction = 'Discard';
    fieldMdApi.authorizationFailureAction = 'Discard';

    //call service
    List<MetadataService.SaveResult> lstResults = mdapi.createMetadata(new List<MetadataService.Metadata>{ fieldMdApi });
    system.debug(lstResults);

Is there a way today to attach the class ID using the metadata service?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.