+ Start a Discussion
Jeff GilmoreJeff Gilmore 

Send email from future method with callouts?

I have a triiger that, upon update or insert makes a number of callouts to an external API.  

So the trigger calls a method from a helper class that is defined as @future (callout=true) .

That all works great until I try to send an email as part of this sequence.  Then any callouts that occur after the email is generated give the error "System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out".

I first tried sending this email through another method marked as @future, but was told that future methods can't call future methods.

So I changed it to a queable:
 
public class TribeSendEmail implements Queueable {
    
    String HTMLBody = '';  
    String recipientEmail = '';
    
    public TribeSendEmail(String HTMLBody, String recipientEmail) {  
  
        this.HTMLBody = HTMLBody;  
        this.recipientEmail = recipientEmail;  
    }  
  
    public void execute(QueueableContext qc) {  
        Messaging.reserveSingleEmailCapacity(1);
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        
        String[] toAddresses = new String[] {recipientEmail}; 
        String[] ccAddresses = new String[] {foo@bar.org'};
        
        mail.setToAddresses(toAddresses);
        mail.setCcAddresses(ccAddresses);
        mail.setSubject('This is the subject');
        mail.setBccSender(false);        
        mail.setHtmlBody(HTMLBody);        
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });    
    } 
}
Which I call as follows from the @future method:
System.enqueueJob( new TribeSendEmail( emailBody, recipient ) );
but it still gives me the errors on any subsequent callouts.

Note that there are no DML actions in any of these classes aside from the sending of the email, and if I comment out the "enqueJob" statement, it all works fine.

Can anyone suggest a way to send an email from within this context or advise if I am doing something wrong?

Thanks!



 
Best Answer chosen by Jeff Gilmore
Ankit SehgalAnkit Sehgal
Have you tried making the callout first and then sending email?

All Answers

Ankit SehgalAnkit Sehgal
Have you tried making the callout first and then sending email?
This was selected as the best answer
Elisa FieldsElisa Fields

Hello Jeff Gilmore,
Thanks for asking this.
 I faced similar kind of issue last time, I am still searching for some proper solution Same issue still no fix to this.myPennMedicine (https://www.mypennmedicine.ltd/)
Please help any one .
thnak you.
Jeff GilmoreJeff Gilmore
I followed Ankit's helpful response and figured out a way to restructure my code to send the email after all callouts.  I was able to call my mail sending class as a simple public class--no future or queueable required.

Callouts are quite a rat's nest of overlapping restrictions!

Here's a list some I encountered in case that is helpful to anyone else fairly new to this stuff:
  • Triggers can only call methods with callouts if they are marked as @future (callout=true).  Thus you need to add a helper class to support the trigger and hold such a method.  Having this helper class does assist with writing tests, however.
  • You can't pass complex objects such as sObjects to future methods.  I got around that by serializing the Trigger.new and Trigger.old sObjects as JSON, passing that, and deserializing them in the future method.
  • Future methods cannot return a value--they must be voids.  This made it hard to write meaningful tests of end-to-end functionality because there was nothing significant to Assert.  Writing tests of internediate methods was OK, though.
  • Writing tests for classes with callouts is quite challenging.  For classes that make only one callout, it's not too hard to create a HttpCalloutMock and a static resource containg a sample of the info returned from the callout (in my case blocks of JSON).  But if the code calls numerous callouts with logic between them, that requires a "MultiMock", i.e a set of URLS and static resources.  This get's tricky because these URLs need to be 100% exact or else when you run the class you get an immediate System.NullPointerException: Static Resource not found: <null> error, and it is tricky to find the culprit.  And the creation of the associated static resources gets pretty complex for multi-callout scenarios as well.  But it does work.
  • Using Postman.co was immeasuably valuable for this process of defining accurate callouts and capturing detailed responses for use in static resources for HttpCalloutMocks!
  • If you have to intermingle callouts with DML updates, just walk away... ;- )
  • In my case, I had a lot of different fields that needed to be passed to and from the various methods in this project.  It was tremendously helpful to create a custom class with @invocableVariables defined (https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableVariable.htm).  This allowed me to pass this object freely between classes and, since it is pass by reference rather than by value, each method could add info to it as needed and that info would be available in subsequent methods that consume the object.  Probably obvious to most of you, but new to me.  This looked like:
public class UserInfo {
        @InvocableVariable(required=true)
        public string username;
        @InvocableVariable(required=true)
        public String name;
        @InvocableVariable(required=true)
        public String email;       
        public String sessionToken;
        @InvocableVariable(required=false)
        public String source;
        @InvocableVariable(required=false)
        public String externalid;
        @InvocableVariable(required=false)
        public String userID;
        @InvocableVariable(required=false)
        public String emailBody;
    }
P.S. I used that last variable emailBody to solve the problem described in this question.  Various methods could add text to the email body along the way and I could wait to send it until the end.