1

I need to send an email to Contact(s) listed in my Opportunity Contact Roles, 24 hours after an Opportunity has been Closed/Lost, if the Opportunity hasn't been reopened in the meantime. So essentially I need to replicate a Time Dependent Workflow Action or Process Builder's Scheduled Action.

I'm guessing that I can use a Queueable class for this but I can't find any documentation explaining how to schedule the job so that it's run after a certain time period?

public class WE_WinLossSurvey {

    Map<Id,Opportunity> oldOpps;
    Map<Id,Opportunity> newOpps;
    Set<Id> oldOppsIds;
    Set<Id> newOppsIds;
    Opportunity oldOpp;
    Set<Id> lostOppIds = new Set<Id>();
    List<Opportunity> oppsWithNoDmContact = new List<Opportunity>();
    Set<Id> oppIdsToProcess = new Set<Id>();

    public WE_WinLossSurvey(
    Map<Id, Opportunity> oldTriggerOpps,
    Map<Id, Opportunity> newTriggerOpps) {

        oldOpps = oldTriggerOpps;
        newOpps = newTriggerOpps;

        WE_ApexUtility apxUtil = new WE_ApexUtility();
        apxUtil.checkApexControllerStatus();
        if (!WE_ApexUtility.setting.contains(true)){

            apxUtil.retrieveValidRecordTypes(
            'EU Opps');

            for(Opportunity o : newOpps.values()) {
                oldOpp = oldOpps.get(o.Id);
                if (WE_ApexUtility.validEuRecordTypeIds.contains(o.RecordTypeId) &&
                oldOpp.Probability != o.Probability &&
                o.Probability == 0)
                {
                    lostOppIds.add(o.Id);
                }
            }
            if (lostOppIds.size() > 0) {
                for(Opportunity o : [SELECT Id, Owner.Email, Owner.SenderName,
                                    (SELECT Contact.Email
                                       FROM OpportunityContactRoles
                                      WHERE Role = 'Decision Maker')
                                       FROM Opportunity
                                      WHERE Id IN :lostOppIds])
                {
                    if (o.OpportunityContactRoles.size() == 0) {
                        oppsWithNoDmContact.add(newOpps.get(o.Id));
                    } else {
                        oppIdsToProcess.add(o.Id);
                    }
                }
                if (oppsWithNoDmContact.size() > 0) {
                    for(Opportunity o : oppsWithNoDmContact) {
                        o.addError(' Please select a Contact with the Role \'Decision Maker\'. This Contact will receive the Lost Deal Survey');
                    }
                }
                if (oppIdsToProcess.size() > 0) {
                    SendWinLossSurveyEmails surveySender = new SendWinLossSurveyEmails(lostOppIds);
                    Id jobId = System.enqueueJob(surveySender);
                }
            }
        }
    }

    public class SendWinLossSurveyEmails implements Queueable {

        Set<Id> lostOppIds = new Set<Id>();

        public SendWinLossSurveyEmails(
            Set<Id> classIds) {
                lostOppIds = classIds;
            }

        public void execute(QueueableContext context) {

            List<Messaging.SingleEmailMessage> emailsToSend = new List<Messaging.SingleEmailMessage>();
            List<String> sendTos = new List<String>();
            List<String> ccTos = new List<String>();
            List<String> bccTos = new List<String>();

           if (lostOppIds.size() > 0) {
               for(Opportunity o : [SELECT Id, Owner.Email, Owner.SenderName, Probability,
                                   (SELECT Contact.Email
                                      FROM OpportunityContactRoles
                                     WHERE Role = 'Decision Maker')
                                      FROM Opportunity
                                     WHERE Id IN :lostOppIds])
                 {
                     if (o.Probability == 0) {
                         WE_ApexUtility.PrepareEmails pe = new WE_ApexUtility.PrepareEmails();
                         Messaging.reserveSingleEmailCapacity(o.OpportunityContactRoles.size());
                         for(OpportunityContactRole ocr : o.OpportunityContactRoles) {
                             sendTos.add(ocr.Contact.Email);
                             // ccTo & bccTo deliberately left empty
                             emailsToSend.add(pe.createEmail(
                             sendTos,           // sendTo
                             o.Owner.Email,     // replyTo
                             o.Owner.SenderName,// displayName
                             ccTos,             // ccTo
                             bccTos,            // bccTo
                             'Survey',          // subject
                             'Testing'          // body
                             ));
                             sendTos.Clear();
                             ccTos.Clear();
                             bccTos.Clear();
                         }
                     }
                     if (emailsToSend.size() > 0) {
                         for(Messaging.SingleEmailMessage e : emailsToSend) {
                             Messaging.SendEmail(new Messaging.SingleEmailMessage[] { e });
                         }
                     }
                 }
             }
         }
     }
 }
4
  • Why can't you use process builder/flows? – sfdcfox Apr 8 '16 at 18:17
  • @sfdcfox I can't use the Process Builder because I need to send the email notification to Contacts listed in the Opportunity Contact Roles. I could use a flow but I'd prefer to use code to manage this process. I could also trigger the code by checking a checkbox from a time scheduled workflow action but I just assumed that scheduling the execution for a later date was possible. – Alex S Apr 8 '16 at 18:20
  • 1
    something worth looking at if you want to build out a generalized apex async framework is the Dan Appleman Advanced Apex 3rd edition chapter 7 where you use a combination of an asyncRequest__c object with a preferred starttime and queueables that continuously chain looking for the next thing to do – cropredy Apr 8 '16 at 20:27
  • @cropredy thanks that sounds good, I'll check it out. – Alex S Apr 8 '16 at 20:28
2

You can't schedule Queueable/Future/Batchable classes directly (except that we do have Database.scheduleBatch, which lets us specify a batch X minutes in the future), we're limited to the number of active schedules we have.

Personally, I'd go with a Timed Workflow that runs 24 hours after the probability hits 0, with a field update to the opportunity (doesn't really matter what, so long as it does), and then send your notifications in a trigger.

Alternatively, you could take the slightly longer route and build your own queue object, and push values into it when the probability changes to zero, deletes values from it when the probability is not zero, and is monitored hourly by a scheduled class that sends notifications (as your original logic) and clears the queue.

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.