As it stands I am looking to schedule a batch so that each batch will send 100 emails, wait 30 minutes, then sends the next 100 emails. This will happen until all the the associated apexjobs to my class are completed. As it stands I have it set up but all it seems to do is Schedule the whole batch (i.e. all emails) for in 30 minutes from initialisation. I want to get it so that it staggers the sending so each schedulebatch is for 100 emails and with 30minutes added on from the previous batch.


Run Send All -> Schedule Apex job created (current time + 30mins) with all emails -> sends all emails


Run Send All -> Schedule Apex jobs with 100 emails adding 30minutes to each job -> until no jobs are left

As it stands I have:


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;}
Integer minutesFromNow = 30;
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!');
        return null;

    String msgVal = '';
    Boolean isSent = false;

        BatchSendReports batch      = new BatchSendReports();

        batch.sendDashboard         = sendDashboard;
        batch.sendLocationCO2Report = sendLocationCO2Report;
        batch.sendGroupCO2Report    = sendGroupCO2Report;
        batch.sendPartnerReport     = sendPartnerReport;
        batch.sendLinkedReport      = sendLinkedReport;
        //100 because single email allow send 100 records per mail
        //Database.executeBatch(batch, 100);
        System.scheduleBatch(batch, 'EmailBatch in ' + minutesFromNow + ' mins', minutesFromNow, 100);
        minutesFromNow = minutesFromNow + 30;
        msgVal = 'Sending report is in progress.';
        isSent = true;
        msgVal = 'Apex job isnot available. Please try again later.';

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

I believe the logic has to be implemented in my controller but this is my first time working with this so would love some input. Also I have added in my batchsendreport class


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])
            mapAccConGlobalReport.put(con.AccountId, new List<String>());

    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){
            System.debug('Email : ' + 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)){

        //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));

        try {

                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.setBccAddresses(new List<String>{/*'[email protected]'*/'[email protected]'});
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

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

  • 1
    What is your question? I see a lot of code but it is not clear what you need. Commented Jan 23, 2017 at 17:33
  • @SebastianKessel I will update further
    – Nebbyyy
    Commented Jan 23, 2017 at 17:34
  • You want to either implement Database.Schedulable or use Queable classes that are chained where the the finish method schedules another Queable to run again in 30 minutes. You don't really need a batch for what you're doing.
    – crmprogdev
    Commented Jan 23, 2017 at 17:54
  • @crmprogdev so you'd recommend changing my second class to a database.scheduable? I'll read up on that on my way home
    – Nebbyyy
    Commented Jan 23, 2017 at 17:56
  • You have a couple of issues in that you're trying to split up batches and limit the Global scope to 100 emails. To do that properly, you want to use either a schedulable that doesn't use batches or else use queuables (preferable IMO) that can then schedule the next one to run if the job isn't complete.
    – crmprogdev
    Commented Jan 23, 2017 at 18:09

1 Answer 1


You process looks complex with lot of unnecessary things. You need to schedule the chain of batch. This is the way you can do it.

First thing you should change, instead of running the batch report run it on Contact/Account object to which you want to send email. Set the batch size to 100 (governor limit).
This way you don't need to manage that 100 email thing. Rest of thje information like what is processed, which dashboard to send, what criteria? etc follow the below approach:-

  1. Have an org-wide custom setting which would store information like is Batch Scheduled, No. of email processed, Total emails count, where to start with next email set, no of records processed.

  2. When you are scheduling the batch you must check with custom setting that is Batch Scheduled, Are all records processed, is there other dashboard to sent etc. if everything looks fine then schedule the batch else show the message.

  3. Update custom setting. Schedule the batch to run again in next 30 minute if there is need. Otherwise, stop the process and send out post batch run things like sending finish email etc.

Handling batch schedule limit approach looks fine.

  • Thanks a lot for taking the time to post this. I'll begin to work on this tomorrow and update how I get on.
    – Nebbyyy
    Commented Jan 23, 2017 at 18:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .