function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Ben Allington 7Ben Allington 7 

Using Batch Scheduling for mass emails apex

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')];
}



}


 
NagendraNagendra (Salesforce Developers) 
Hi Ben,

Please check with below post from stack exchange community with similar discussion and possible solution. May I request you to please close this thread by marking it as solved so that others can get benefited.

Regards,
Nagendra.