1

I am using Amazon SES to send mails to the all opportunities using batch apex with the body of email template. I'm using the solution proposed in an answer to Using APEX to assemble HTML Letterhead Emails. Basically i'm fencing the Messaging.sendEmail() between a savepoint and rollback in order to get the template applied.

Here is the code

Global class Class1 implements
    Database.Batchable<sObject>,Database.AllowsCallouts{

    Global Database.QueryLocator start(Database.BatchableContext BC) {
        String query = 'SELECT Id,name,accountid,contactid__c,email__c from opportunity';
        return Database.getQueryLocator(query);
    }

    Global void execute(Database.BatchableContext BC, List<Opportunity> Scope) {
        for(Opportunity oop:scope){
            list<string> toAddresses = new list<string>();
            toAddresses.add('sample@gmail.com');

Here i am using email technique with merge field values, there i am receiving complete body

    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();     
    mail.setWhatId(oop.Id);     mail.settargetObjectId(opp.contactid__c);
    mail.setTemplateId(tempbody); 
    mail.setToAddresses( toAddresses);

    Savepoint sp = Database.setSavepoint(); 
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); 
    Database.rollback(sp);

    tempbody = mail.getPlainTextBody();
    httpReq.setEndpoint('https://email.us-east-1.amazonaws.com');
    [..]        

Here i am calling http callout to send mail by using the Amazon SES

            httpReq.setBody(email);
            System.debug(httpReq.getBody());
            Http http = new Http();
            HttpResponse response = http.send(httpReq);        
        } 
    }   
}

Here i am getting error like , You have uncommitted work pending. Please commit or rollback before calling out

I feel that calling http callout and commit and rollback in same causing the issue. Please suggest me where i am doing wrong, Thanks in advance.

  • Whats the purpose of your rollback? Also not sure why you're sending the email before building a body for it. Roll back is a DML operation and you can't do DML and http calllouts in the same thread if there is anything uncommitted. – thegogz Oct 14 '14 at 9:00
  • If we dont use rollback, mail will send using salesforce standard way that does not allow to send more mails per day as we had limitations, i am sending the mails by thirdpart, I.e.., Amazon SES – Sunny Oct 14 '14 at 9:04
  • Why are you calling Messaging.sendEmail() at all, when you do not intend to send the e-mail using the salesforce infrastructure? – Alexander Johannes Oct 14 '14 at 12:46
  • @AlexanderJohannes, After placing the "Messaging.sendEmail()" this only i can able to get template body, else it is coming null – Sunny Oct 14 '14 at 13:01
  • @Sunny Have you tried querying the template and manually parsing it instead then? – dphil Oct 14 '14 at 14:07
1

Although the error says to commit or rollback before making the callout, and you are doing the rollback, SFDC seems to ignore this when the callout is executed - this could be because of batch context but I didn't have time to explore that.

OK, here are some ways to work around the error

Option 1 You will need two batchable classes and one custom object Deferred_Callouts__c.

  • Batchable class 1 - use the Database.savepoint - rollback sequence to get the rendered template. Then save the email recipient id and body in the custom object Deferred_Callouts__c. insert these all in the finish() method - which then invokes Batchable class 2
  • Batchable class 2 - Read all the Deferred_Callouts__c and construct the callouts

Considerations:

  • You are limited in the # of callouts so you'll need to be careful of limits in the batch size passed as scope.

Option 2 You will need one batchable class, one custom object Deferred_Callouts__c, and a workflow

  • Batchable class 1 - use the Database.savepoint - rollback sequence to get the rendered template. Then save the email recipient id and body in the custom object Deferred_Callouts__c. insert these all in the finish() method

  • Use a workflow that executes upon Create of Deferred_Callouts__c that uses an outbound message action to send via SOAP to the end point (assumes endpoint supports SOAP)

Option 3

You will need to use the Dan Appleman Async Framework as presented at Dreamforce that allows for unlimited async operations. This is well worth understanding.

This is my personal favorite as the solution is extensible to all your async needs over time

Option 4 Do what Alexander suggested and use the simulated merge field approach here . If you have formulas in the template, this will be difficult. This avoids altogether the DML operations.

| improve this answer | |
  • Hi @corp1645, I selected option 3, but i can not able to understand how to do that. Any suggestions to startup – Sunny Nov 12 '14 at 9:33
  • Sunny -- I would suggest a) Purchasing Appleman's book - Advanced Apex Programming, second edition which will explain in detail how to use the framework and includes code samples for download. The book was written after the Dreamforce links above and has some framework enhancements. Then, b) develop iteratively and make sure each bit is working before trying to debug it all at once – cropredy Nov 12 '14 at 17:30
  • You can try by using @future Global void execute(Database.BatchableContext BC, List<Opportunity> Scope){ } – kumar gaurav Aug 15 '15 at 16:28
1

Use Messaging.renderStoredEmailTemplate to generate the rendered Messaging.SingleEmailMessage. Then use getPlainTextBody(), getHtmlBody(), etc. on the rendered message.

| improve this answer | |
0

There is one solution in which you can make use of Database.Stateful and send emails even avoiding uncommited work pending error. Here is the code:

Global class Class1 implements
    Database.Batchable<sObject>,Database.AllowsCallouts,Database.Stateful{ //Marking this batch as stateful in order to maintain tempates

    List<EmailMessageWrapper> emails = null;

    Global Database.QueryLocator start(Database.BatchableContext BC) {
        String query = 'SELECT Id,name,accountid,contactid__c,email__c from opportunity';
        return Database.getQueryLocator(query);
    }

    Global void execute(Database.BatchableContext BC, List<Opportunity> Scope) {
        //CALLOUT SAFE ZONE: since no DML PERFORMED
        if(emails!=null){
            //do your callouts and send mails
        }

        //CALLOUT UNSAFE ZONE: since DML would be performed
        //Prepare your emails ready when the execute would be executing
        emails = new List<EmailMessageWrapper>();
        /****
         * YOUR WORKAROUND CODE TO GET TEMPLATE VARIABLES POPULATED 
         * THIS WILL INCLUDE SAVEPOINT , sendEMAIL  & ROLLBACK
         * /
        /*****
         * NOTE : Now whatever you populate in emails will not be sent in the same context but 
         * these emails will be sent when next time execute would run. So you are preparing emails to be sent for next executes execution
         * where you would not face uncommited work pending since you wont be making any dmls before making callout.
         * when last opportunity (opportunities) would be considered then emails would be populated which would need to be sent from finish method.
         */

    }

    Global void finish(Database.BatchableContext BC){
        //do your callouts to send last batch of emails
    }

    //Wrapper class to hold email information
    class EmailMessageWrapper{
        String subject;
        String body;
        String toAddresses;
        String ccAddresses;
    }

}
| improve this answer | |

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.