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
Gleb VrevskyGleb Vrevsky 

Best way to avoid SendEmail limits

We have a custom object - Employees. I have an Apex class to send an email to all employees. It is scheduled to be sent once a month, using "Schedule Apex". But it fails with an error: "Scheduler: failed to execute scheduled job: jobId: 707G000003Axx2S, class: common.apex.async.AsyncApexJobObject, reason: Too many Email Invocations: 11"

Obviously, it fails due to governor limits (more than 10 sendEmails). Please, find my Apex class below. What is the best way to make it work for more than 100 employees? Should I rework it as a Batch Apex or...?

Any example would be much appreciated.
global class EmployeeMonthlyEmail implements Schedulable {
    
    global void execute(SchedulableContext ctx) {
        List<Employee__c> empList = 
            [SELECT Id, Name, FirstName__c, LastName__c, Email__c, 
                    Birthdate__c, MobilePhone__c, PersonalEmail__c, Skype__c
             FROM Employee__c
             WHERE Status__c != 'Disabled'
            ];
        
        for(Employee__c emp:empList){
            sendEmailToEmployee(emp);
        }
    }
    
    public static Boolean sendEmailToEmployee(Employee__c emp){
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        
        mail.setToAddresses(new String[]{emp.Email__c});
        mail.setReplyTo('inese@scandiweb.com');
        mail.setSenderDisplayName('Scandiweb Salesforce');
        mail.setSubject('Employee Info - ' + emp.Name);
        mail.setBccSender(false);
        mail.setUseSignature(false);
        mail.setCharset('UTF-8');
        
        String emailText = 
            '<p>Hello ' + emp.FirstName__c +',</p>'+
            
            '<p>Please, check information we have, and let us know if we should update it.'+
            '<br />You are welcome to email inese@scandiweb.com with new information'+
            '<br />Thank you in advance ;)</p>'+
            
            '<p>First Name: '+ emp.FirstName__c + '<br />'+
            'Last Name: '+ emp.LastName__c + '<br />'+
            'Birthdate: '+ emp.Birthdate__c + '<br />'+
            'Mobile Phone: '+ emp.MobilePhone__c + '<br />'+
            'Personal Email: '+ emp.PersonalEmail__c + '<br />'+
            'Skype: '+ emp.Skype__c + '</p>';
        
        List<CourseEnrollment__c> courseList = 
            [SELECT Course__r.Name, Status__c, DateExpectedOn__c, DateCompletedOn__c
             FROM CourseEnrollment__c
             WHERE Employee__c = :emp.Id
             ORDER BY Status__c DESC
            ];
        
        emailText = emailText + mailTextEmployeeCourseList(courseList);
        
        emailText = emailText + 
            '</p>'+
            '<p>Please excuse us for any issues, we are still in process of getting better ;)</p>';
        
        mail.setHtmlBody(emailText);
    
        try {
          List<Messaging.SendEmailResult> results = 
              Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
          
            if (!results.get(0).isSuccess()) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
          //System.debug('Exception has occurred: ' + e.getMessage());
            return false;
        }
    }
    
    public static String mailTextEmployeeCourseList(List<CourseEnrollment__c> courseList) {
        String text = '';
        
        text = text + '<p>Course List:';
        if (courseList.size() >= 1)
        {
            for (CourseEnrollment__c ce : courseList)
            {
                text = text + 
                    '<br />' + ce.Course__r.Name + ' (' + ce.Status__c + ')';
                
                if (ce.Status__c == 'Completed' && ce.DateCompletedOn__c != null) {
                    text = text + 
                      ' - ' + ce.DateCompletedOn__c.day() + '/' + ce.DateCompletedOn__c.month() + '/' + ce.DateCompletedOn__c.year();
                } else if (ce.Status__c == 'Expected' && ce.DateExpectedOn__c != null) {
                    text = text + 
                      ' - ' + ce.DateExpectedOn__c.day() + '/' + ce.DateExpectedOn__c.month() + '/' + ce.DateExpectedOn__c.year();
                }
                
            }
        } else {
            text = text + 
                '<br />There are no Courses assigned to you ;(';
        }
        
        return text;
    }
}

 
@Karanraj@Karanraj
Try the updated code below
global class EmployeeMonthlyEmail implements Schedulable {
    
    global void execute(SchedulableContext ctx) {
        List<Employee__c> empList = 
            [SELECT Id, Name, FirstName__c, LastName__c, Email__c, 
                    Birthdate__c, MobilePhone__c, PersonalEmail__c, Skype__c
             FROM Employee__c
             WHERE Status__c != 'Disabled'
            ];
        sendEmailToEmployee(empList);
     
    }
    
    public static Boolean sendEmailToEmployee(List<Employee__c> emplist){
       List<Messaging.SingleEmailMessage> mailList = new List<Messaging.SingleEmailMessage>();
       for(Employee__c emp:empList){
		    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        
        mail.setToAddresses(new String[]{emp.Email__c});
        mail.setReplyTo('inese@scandiweb.com');
        mail.setSenderDisplayName('Scandiweb Salesforce');
        mail.setSubject('Employee Info - ' + emp.Name);
        mail.setBccSender(false);
        mail.setUseSignature(false);
        mail.setCharset('UTF-8');
        
        String emailText = 
            '<p>Hello ' + emp.FirstName__c +',</p>'+
            
            '<p>Please, check information we have, and let us know if we should update it.'+
            '<br />You are welcome to email inese@scandiweb.com with new information'+
            '<br />Thank you in advance ;)</p>'+
            
            '<p>First Name: '+ emp.FirstName__c + '<br />'+
            'Last Name: '+ emp.LastName__c + '<br />'+
            'Birthdate: '+ emp.Birthdate__c + '<br />'+
            'Mobile Phone: '+ emp.MobilePhone__c + '<br />'+
            'Personal Email: '+ emp.PersonalEmail__c + '<br />'+
            'Skype: '+ emp.Skype__c + '</p>';
        
			List<CourseEnrollment__c> courseList = 
            [SELECT Course__r.Name, Status__c, DateExpectedOn__c, DateCompletedOn__c
             FROM CourseEnrollment__c
             WHERE Employee__c = :emp.Id
             ORDER BY Status__c DESC
            ];
        
			emailText = emailText + mailTextEmployeeCourseList(courseList);
        
			emailText = emailText + 
            '</p>'+
            '<p>Please excuse us for any issues, we are still in process of getting better ;)</p>';
        
			mail.setHtmlBody(emailText);
			mailList.add(mail);
       
	    }
		try {
          List<Messaging.SendEmailResult> results = Messaging.sendEmail(mailList);
            if (!results.get(0).isSuccess()) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
          //System.debug('Exception has occurred: ' + e.getMessage());
            return false;
        }	   


	  
    }
    
    public static String mailTextEmployeeCourseList(List<CourseEnrollment__c> courseList) {
        String text = '';
        
        text = text + '<p>Course List:';
        if (courseList.size() >= 1)
        {
            for (CourseEnrollment__c ce : courseList)
            {
                text = text + 
                    '<br />' + ce.Course__r.Name + ' (' + ce.Status__c + ')';
                
                if (ce.Status__c == 'Completed' && ce.DateCompletedOn__c != null) {
                    text = text + 
                      ' - ' + ce.DateCompletedOn__c.day() + '/' + ce.DateCompletedOn__c.month() + '/' + ce.DateCompletedOn__c.year();
                } else if (ce.Status__c == 'Expected' && ce.DateExpectedOn__c != null) {
                    text = text + 
                      ' - ' + ce.DateExpectedOn__c.day() + '/' + ce.DateExpectedOn__c.month() + '/' + ce.DateExpectedOn__c.year();
                }
                
            }
        } else {
            text = text + 
                '<br />There are no Courses assigned to you ;(';
        }
        
        return text;
    }
}

 
Gleb VrevskyGleb Vrevsky
@ S.Karanraj, thank you for your answer.
If fixed the issue with sendEmail limits, but the new issue is:  Too many SOQL queries: 101. Do you have any thoughts what can be optimized in the code to fix it as well?
Brandon H. BarrBrandon H. Barr
On line #41, there is a SOQL query. The exception will happen if the employee list size is larger than 99 (or less depending on other code in theis execution not listed here).

This SOQL query needs to be moved out of the FOR loop that starts on line #16. I would recommend making a MAP<Employee__c.Id, List<List<CourseEnrollment__c> before line #16 which would limit this code to 2 SOQL queries. 
@Karanraj@Karanraj
Ahh.. I didn't notice that. You can do that subquery.
Here is the updated code below 
global class EmployeeMonthlyEmail implements Schedulable {
    
    global void execute(SchedulableContext ctx) {
        List<Employee__c> empList = 
            [SELECT Id, Name, FirstName__c, LastName__c, Email__c, 
                    Birthdate__c, MobilePhone__c, PersonalEmail__c, Skype__c,(
SELECT Course__r.Name, Status__c, DateExpectedOn__c, DateCompletedOn__c FROM CourseEnrollment__r WHERE Employee__c = :emp.Id ORDER BY Status__c DESC)

             FROM Employee__c
             WHERE Status__c != 'Disabled'
            ];

        for(CourseEnrollment__c cenrol : [

        sendEmailToEmployee(empList);
     
    }
    
    public static Boolean sendEmailToEmployee(List<Employee__c> emplist){
       List<Messaging.SingleEmailMessage> mailList = new List<Messaging.SingleEmailMessage>();
       for(Employee__c emp:empList){
		    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        
        mail.setToAddresses(new String[]{emp.Email__c});
        mail.setReplyTo('inese@scandiweb.com');
        mail.setSenderDisplayName('Scandiweb Salesforce');
        mail.setSubject('Employee Info - ' + emp.Name);
        mail.setBccSender(false);
        mail.setUseSignature(false);
        mail.setCharset('UTF-8');
        
        String emailText = 
            '<p>Hello ' + emp.FirstName__c +',</p>'+
            
            '<p>Please, check information we have, and let us know if we should update it.'+
            '<br />You are welcome to email inese@scandiweb.com with new information'+
            '<br />Thank you in advance ;)</p>'+
            
            '<p>First Name: '+ emp.FirstName__c + '<br />'+
            'Last Name: '+ emp.LastName__c + '<br />'+
            'Birthdate: '+ emp.Birthdate__c + '<br />'+
            'Mobile Phone: '+ emp.MobilePhone__c + '<br />'+
            'Personal Email: '+ emp.PersonalEmail__c + '<br />'+
            'Skype: '+ emp.Skype__c + '</p>';
        
 		    emailText = emailText + mailTextEmployeeCourseList(e.CourseEnrollment__r);
			emailText = emailText + 
            '</p>'+
            '<p>Please excuse us for any issues, we are still in process of getting better ;)</p>';
        
			mail.setHtmlBody(emailText);
			mailList.add(mail);
       
	    }
		try {
          List<Messaging.SendEmailResult> results = Messaging.sendEmail(mailList);
            if (!results.get(0).isSuccess()) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
          //System.debug('Exception has occurred: ' + e.getMessage());
            return false;
        }	   


	  
    }
    
    public static String mailTextEmployeeCourseList(List<CourseEnrollment__c> courseList) {
        String text = '';
        
        text = text + '<p>Course List:';
        if (courseList.size() >= 1)
        {
            for (CourseEnrollment__c ce : courseList)
            {
                text = text + 
                    '<br />' + ce.Course__r.Name + ' (' + ce.Status__c + ')';
                
                if (ce.Status__c == 'Completed' && ce.DateCompletedOn__c != null) {
                    text = text + 
                      ' - ' + ce.DateCompletedOn__c.day() + '/' + ce.DateCompletedOn__c.month() + '/' + ce.DateCompletedOn__c.year();
                } else if (ce.Status__c == 'Expected' && ce.DateExpectedOn__c != null) {
                    text = text + 
                      ' - ' + ce.DateExpectedOn__c.day() + '/' + ce.DateExpectedOn__c.month() + '/' + ce.DateExpectedOn__c.year();
                }
                
            }
        } else {
            text = text + 
                '<br />There are no Courses assigned to you ;(';
        }
        
        return text;
    }
}



Gleb VrevskyGleb Vrevsky
Thank you for answers.
@S.Karanraj, with the updated query we are getting such error ->  http://www.screencast.com/t/3M90RJos0LYM, saying that CourseEnrollment__r is not a child of Employee, hence not possible to run a subquery. But actually, there is Master-Details relationship between Employee and Course Enrollment -> http://www.screencast.com/t/crP7No4f
View inside of Course Enrollment object -> http://www.screencast.com/t/ukvrx3slxVN
View inside of Employee object ->  http://www.screencast.com/t/YXncFfhsmJP

Is there anything I am missing?