1

I am wondering how to implement my mass emails so they are scheduled so a batch of 100 is sent out every 30 minutes until all emails are sent out, as it stands I have added in ability to batch send all your emails but due to the limitations of 5,000 emails per day I am looking to develop a work around.

I have managed to add the logic for the process in my SchSentReport class but am struggling to get them working together so that the batches of 100 are processed separately. Thanks

SchSentReport

global class SchSentReport implements Schedulable{
    integer count = 1;
    public SchSentReport(){

    }

global void execute(SchedulableContext sc) {
    //check apex job available in system before run batch
    if(BatchSendReports.isAvailableApexJob()){
        BatchSendReports batch = new BatchSendReports();
        Database.executeBatch(batch, 100);//100 because single email allow send 100 records per mail
        setScheduleNext30mn();
        System.debug('Batch No. ' + count + ' Added');
        count = count + 1;
    }else{
        System.abortJob(SC.getTriggerId());

    }
}

/**
* The shedule will start next 30mn 
*/
public void setScheduleNext30mn(){
    SchSentReport sch = new SchSentReport();

    String myTime = '0 $1 h do m ? y';
    Datetime dt = System.now();
    dt = dt.addMinutes(30 * count); //schedule at next 30 minutes
    System.debug('Batch Time set for :' + dt);
    myTime=myTime.replace('h', dt.hour()+'');
    myTime=myTime.replace('do', dt.day()+'');
    myTime=myTime.replace('m', dt.month()+'');
    myTime=myTime.replace('y', dt.year()+'');
    myTime=myTime.replace('$1', dt.minute()+'');
    System.debug('My Time :' + myTime);
    System.schedule('SchSentReport', myTime, sch);
    }

}

SendReportPageController

public with sharing class SendReportPageController {

public Boolean sendDashboard {get;set;}
public Boolean sendLocationCO2Report {get;set;}
public Boolean sendGroupCO2Report {get;set;}
public Boolean sendPartnerReport {get;set;}
public Boolean sendLinkedReport {get;set;}

public SendReportPageController(){
    sendDashboard           = false; 
    sendLocationCO2Report   = false;
    sendGroupCO2Report      = false;
    sendPartnerReport       = false;
    sendLinkedReport        = false;
}

public PageReference doSend(){
    if(!sendDashboard && !sendLocationCO2Report && !sendGroupCO2Report && !sendPartnerReport && !sendLinkedReport){
        Apexpages.Message msg = new Apexpages.Message(ApexPages.Severity.ERROR, 'You have to select at least a report type!');
        Apexpages.addMessage(msg);
        return null;
    }

    String msgVal = '';
    Boolean isSent = false;

    if(BatchSendReports.isAvailableApexJob()){
        BatchSendReports batch      = new BatchSendReports();

        batch.sendDashboard         = sendDashboard;
        batch.sendLocationCO2Report = sendLocationCO2Report;
        batch.sendGroupCO2Report    = sendGroupCO2Report;
        batch.sendPartnerReport     = sendPartnerReport;
        batch.sendLinkedReport      = sendLinkedReport;

        Database.executeBatch(batch, 100);//100 because single email allow send 100 records per mail
        msgVal = 'Sending report is in progress.';
        isSent = true;
    }else{
        msgVal = 'Apex job isnot available. Please try again later.';
    }

    Apexpages.Message msg = new Apexpages.Message(isSent ? ApexPages.Severity.CONFIRM : ApexPages.Severity.Warning, msgVal);
    Apexpages.addMessage(msg);
    return null;
}

BatchSendReports

I dont think this class has any relevance on the functionality but added it just incase anyone felt it was relevant.

public class BatchSendReports implements Database.Batchable<sObject>{
private ID accid;
private static final String DASHBOARD_TEMP  = 'Monthly_Location_Dashboard';
private static final String MONTHLY_TEMP    = 'Monthly_Recycling_Report';
private static final String GROUP_TEMP      = 'Group_Recycling_Report';
private static final String PARTNER_TEMP    = 'Partner_Recycling_Report';
private static final String LINKED_TEMP     = 'Linked_Recycling_Report';

private static final String DASHBOARD_REPORT= 'dashboard';
private static final String MONTHLY_REPORT  = 'co2 report';
private static final String GROUP_REPORT    = 'enqix spreadsheet';
private static final String PARTNER_REPORT  = 'partnership co2 report';
private static final String LINKED_REPORT   = 'linked account co2 report';

private Map<String, String> mapReportTypeEmailTemplate = new Map<String, String>{   DASHBOARD_REPORT=> DASHBOARD_TEMP,
                                                                                    MONTHLY_REPORT  => MONTHLY_TEMP,
                                                                                    GROUP_REPORT    => GROUP_TEMP,
                                                                                    PARTNER_REPORT  => PARTNER_TEMP,
                                                                                    LINKED_REPORT   => LINKED_TEMP};
public Boolean sendDashboard            {get;set;}
public Boolean sendLocationCO2Report    {get;set;}
public Boolean sendGroupCO2Report       {get;set;}
public Boolean sendPartnerReport        {get;set;}
public Boolean sendLinkedReport         {get;set;}

//for testing
public BatchSendReports(ID accid){
    this.accid = accid;
    ////If run from test, default value will true
    this.sendDashboard          = Test.isRunningTest(); 
    this.sendLocationCO2Report  = Test.isRunningTest();
    this.sendGroupCO2Report     = Test.isRunningTest();
    this.sendPartnerReport      = Test.isRunningTest();
    this.sendLinkedReport       = Test.isRunningTest();
}

public BatchSendReports(){
    //If run from test, default value will true
    this.sendDashboard          = Test.isRunningTest();
    this.sendLocationCO2Report  = Test.isRunningTest();
    this.sendGroupCO2Report     = Test.isRunningTest();
    this.sendPartnerReport      = Test.isRunningTest();
    this.sendLinkedReport       = Test.isRunningTest();
}

public Database.QueryLocator start(Database.BatchableContext BC){

    Set<String> reportTypes = new Set<String>();

    if(sendDashboard)           reportTypes.add(DASHBOARD_REPORT);
    if(sendLocationCO2Report)   reportTypes.add(MONTHLY_REPORT);
    if(sendGroupCO2Report)      reportTypes.add(GROUP_REPORT);
    if(sendPartnerReport)       reportTypes.add(PARTNER_REPORT);
    if(sendLinkedReport)        reportTypes.add(LINKED_REPORT);

    String query = 'Select Name, Main_Contact_Email__c, Report_Type__c From Account ';
    String condition = ' Where Report_Type__c != null AND Report_Type__c in:reportTypes ' + (accid != null ? ' AND id=:accid ' : '') ;

    return Database.getQueryLocator(query + condition);
}

public void execute(Database.BatchableContext BC, List<sObject> scope){

    //get all related email templates
    Map<String, String> mapEmailTemplates = new Map<String, String>();
    for (EmailTemplate et : [SELECT Id, DeveloperName FROM EmailTemplate WHERE DeveloperName in:mapReportTypeEmailTemplate.values()]) {
        mapEmailTemplates.put(et.DeveloperName, et.Id);
    }

    //get all Contacts related Account that its Receive_Global_Report__c = true
    Map<String, List<String>> mapAccConGlobalReport = new Map<String, List<String>>();
    for(Contact con: [Select Email, AccountId 
                        From Contact 
                        Where Receive_Global_Report__c = true 
                            AND Email != null 
                            AND AccountID in: (List<Account>) scope])
    {
        if(!mapAccConGlobalReport.containsKey(con.AccountId)){
            mapAccConGlobalReport.put(con.AccountId, new List<String>());
        }
        mapAccConGlobalReport.get(con.AccountId).add(con.Email);
    }

    List<OrgWideEmailAddress> orgWilds = [Select Id From OrgWideEmailAddress Where DisplayName ='First Mile' AND IsAllowAllProfiles = true limit 1];
    List<Messaging.SingleEmailMessage> lstMails = new List<Messaging.SingleEmailMessage>();
    List<ResultReportSent__c> lstRecordResults = new List<ResultReportSent__c>();

    for(Account acc: (List<Account>) scope){
        String reportType = acc.Report_Type__c.toLowerCase();
        Boolean isGeneratePDF   = mapReportTypeEmailTemplate.containsKey(reportType) && !mapReportTypeEmailTemplate.get(reportType).equalsIgnoreCase(DASHBOARD_TEMP);
        Boolean isGroup         = reportType.equalsIgnoreCase(GROUP_REPORT);
        Boolean isPartner       = reportType.equalsIgnoreCase(PARTNER_REPORT);
        Boolean isLinked        = reportType.equalsIgnoreCase(LINKED_REPORT);

        //get email template by Account report type
        String emailTemp = mapReportTypeEmailTemplate.containsKey(reportType) ? mapReportTypeEmailTemplate.get(reportType) : '';
        if(emailTemp == '') continue;

        String emailTemplateId = mapEmailTemplates.containsKey(emailTemp) ? mapEmailTemplates.get(emailTemp) : '';
        if(emailTemplateId == '') continue;

        List<String> lstReciptients = new List<String>();

        //if Account report type is Dashboard or Monthly report, it required acc.Main_Contact_Email__c
        if((reportType.equalsIgnoreCase(DASHBOARD_REPORT) || reportType.equalsIgnoreCase(MONTHLY_REPORT)) && acc.Main_Contact_Email__c == null) continue;

        if(acc.Main_Contact_Email__c != null){
            lstReciptients.add(acc.Main_Contact_Email__c);
        }

        //when the "Group Recycling Report" or "Partner Recycling Report" OR "Linked Recycled Report" is pressed,
        //any contacts for that account that "Receive Global Report" checked will get the group report.
        if((isGroup || isPartner || isLinked) && mapAccConGlobalReport.containsKey(acc.Id)){
            lstReciptients.addall(mapAccConGlobalReport.get(acc.Id));
        }

        //record the results by report type of the number sent for each batch
        lstRecordResults.add(new ResultReportSent__c(   Account__c = acc.Id,
                                                        Status__c = 'Sent',
                                                        Date_Sent__c = System.today(),
                                                        Name = System.now().format('MMMM yyyy') + ' Report'));
        //prepare email
        lstMails.add(SendingEmailUtils.generateEmail(emailTemplateId, acc.Id, lstReciptients, orgWilds, isGeneratePDF, isGroup, isPartner, isLinked));
    }

    if(!lstMails.isEmpty()){
        try {
            Messaging.sendEmail(lstMails);

            if(!lstRecordResults.isEmpty()){
                insert lstRecordResults;
            }
        }catch (Exception ex) {
            System.debug('****** Error: ' + ex.getMessage());
        }
    }

} 

public void finish(Database.BatchableContext BC){
        AsyncApexJob asyn = [Select Status, CreatedBy.Email, TotalJobItems, NumberOfErrors From AsyncApexJob Where id=:BC.getJobId() limit 1];
        String[] toAddresses = new String[]{asyn.CreatedBy.Email};

        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setSubject('Batch Sending Report ' + asyn.Status);
        mail.setPlainTextBody('The batch Apex job processed ' + asyn.TotalJobItems + ' batches with ' + asyn.NumberOfErrors + ' failures.');
        mail.setToAddresses(toAddresses);
        mail.setBccAddresses(new List<String>{/*'customers@thefirstmile.co.uk'*/});
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

public static Boolean isAvailableApexJob(){
    return 5 > [Select count() From AsyncApexJob where Status in ('Processing', 'Preparing')];
}



}
1
  • 1
    I would say you need a batch to gather the Ids of the records that need to be sent and put them in a Batch Queue object. Then in the batch that sends the emails point it to that queue with a limit of 100 in the start method query. Once the email is sent delete the batch queue item
    – Eric
    Dec 21 '16 at 14:19
1

There are actually several things you want to do here. First is you want to create an object that you can send the Ids of the records for which you want to schedule related emails to be sent, and from looking at this part of your code, you'll want to include both the Ids and type of email (along with any related Ids that are needed) you want to send:

    batch.sendDashboard         = sendDashboard;
    batch.sendLocationCO2Report = sendLocationCO2Report;
    batch.sendGroupCO2Report    = sendGroupCO2Report;
    batch.sendPartnerReport     = sendPartnerReport;
    batch.sendLinkedReport      = sendLinkedReport;

Once you do that, each time your schedulable class runs, since you still have a limit of 5000 emails/day you'll want to use the Limits Class to getEmailInvocations() and getLimitEmailInvocations() before you process the records. If you don't have the available limits, you'll want to abort the schedulable class.

Upon successful completion of sending the emails, you'll then want to delete those emails from your temp object that holds those record Ids that you're querying each time. If you have an error processing one of them, you'll need to either 'mark' that record as having failed to send, retry it in the next batch, and if it fails again, then report it using an error handler of some kind.

You can do that on the original record itself or by sending an email to either the admin or record owner along with the error message you obtained from the Messaging.SendEmailResult[] method. See the Messaging class for more on it's use.

You can do the error handling using methods from a separate class that you call before you call your method to schedule the next run of your schedulable class. You may want to make the time of the next run dependent on whether the limits call showed there was an adequate number of emails availabe. If there aren't, then you'll want to increase your settings to extend the call further out until more limits are available for your use since it's a running 24 hour limit.

0

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.