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
ApexNewbie77ApexNewbie77 

Getting Too many Email Invocations: 11 error. How to get around this?

Hello,
I am getting this error: Too many Email Invocations: 11

Is there any way around this? I tried to run a batch with a scope of 1 but its looks like I need to keep it at 200 otherwise I get this error: 

No more than one executeBatch can be called from within a test method. Please make sure the iterable returned from your start method matches the batch size, resulting in one executeBatch invocation.

Below is my code:

Batch Class:
 

global class BatchRenewal implements Database.Batchable<sObject>{
    
    public String query = 'SELECT Id, Name, Contract_Start_Date__c, Contract_End_Date__c, Contract_Amount__c FROM Account WHERE Contract_End_Date__c != NULL AND Contract_Amount__c != NULL';
    
    global Database.queryLocator start(Database.BatchableContext bc){
        return Database.getQueryLocator(query); // you can modify the query as per your requirement.
    }
    
    global void execute (Database.BatchableContext BC, List<Account> acct)
    {
            
            Messaging.SingleEmailMessage AlertOver = new Messaging.SingleEmailMessage(); //Alert when greater than or equal to 2500
            Messaging.SingleEmailMessage AlertUnder = new Messaging.SingleEmailMessage(); //Alert  when less than 2500
            
            String[] toAdd1 = new String[] 
            {
               test@test.com
            };
            
            String[] toAdd2 =  new String[] 
            {
                test1@test.com
            };
            
            //Account[] acct = [SELECT Id, Name, Contract_Start_Date__c, Contract_End_Date__c, Contract_Amount__c FROM Account WHERE Contract_End_Date__c != NULL AND Contract_Amount__c != NULL];
            
            for(Account a: acct)
            {
                String sURL = URL.getSalesforceBaseUrl().toExternalForm()+ '/'+a.Id;
                Date tod = System.today();
                System.debug('%%%%%%%%% '+tod);
                Date contEnd = a.Contract_End_Date__c;
                System.debug('&&&&& '+contEnd);
                Integer dayDiff = contEnd.daysBetween(tod);
                System.debug('******* '+dayDiff);
                
                
                if(dayDiff == 60)
                {    
                    Integer days = 60;
                    
                    if(a.Contract_Amount__c >= 2500)
                    {
                        String bod = 'The following account will need to be renewed in '+days+' days.<br/>';
                        bod += 'Name: '+a.Name+ '<br/>';
                        bod += 'Contract Start Date: '+a.Contract_Start_Date__c+ '<br/>';
                        bod += 'Contract End Date: '+a.Contract_End_Date__c+ '<br/>';
                        bod += 'Contract Amount: $'+a.Contract_Amount__c+ '<br/>';
                        bod += 'Link: '+sURL+ '<br/>';
                        
                        AlertOver.setSubject('ALERT: '+a.Name+ ' has a contract expiring in '+days+' days.');
                        AlertOver.setToAddresses(toAdd1);
                        AlertOver.setSaveAsActivity(False);
                        AlertOver.setHtmlBody(bod);
                        
                        try {
                                Messaging.sendEmail(new Messaging.Email[] {
                                    AlertOver
                                });
                            } catch (Exception e) {
                                System.debug('An unexpected error has occurred: ' + e.getMessage());
                            }
                    
                    }
                    else
                    {
                        
                        String bod = 'The following account will need to be renewed in '+days+' days.<br/>';
                        bod += 'Name: '+a.Name+ '<br/>';
                        bod += 'Contract Start Date: '+a.Contract_Start_Date__c+ '<br/>';
                        bod += 'Contract End Date: '+a.Contract_End_Date__c+ '<br/>';
                        bod += 'Contract Amount: $'+a.Contract_Amount__c+ '<br/>';
                        bod += 'Link: '+sURL+ '<br/>';
                        
                        AlertUnder.setSubject('ALERT: '+a.Name+ ' has a contract expiring in '+days+' days.');
                        AlertUnder.setToAddresses(toAdd2);
                        AlertUnder.setSaveAsActivity(False);
                        AlertUnder.setHtmlBody(bod);
                        
                        
                        try {
                                Messaging.sendEmail(new Messaging.Email[] {
                                    AlertUnder
                                });
                            } catch (Exception e) {
                                System.debug('An unexpected error has occurred: ' + e.getMessage());
                            }
                        
                    }
                }
                
                if(dayDiff == 30)
                {
                    Integer days = 30;
                    
                    if(a.Contract_Amount__c >= 2500)
                    {
                        
                        String bod = 'The following account will need to be renewed in '+days+' days.<br/>';
                        bod += 'Name: '+a.Name+ '<br/>';
                        bod += 'Contract Start Date: '+a.Contract_Start_Date__c+ '<br/>';
                        bod += 'Contract End Date: '+a.Contract_End_Date__c+ '<br/>';
                        bod += 'Contract Amount: $'+a.Contract_Amount__c+ '<br/>';
                        bod += 'Link: '+sURL+ '<br/>';
                        
                        AlertOver.setSubject('ALERT: '+a.Name+ ' has a contract expiring in '+days+' days.');
                        AlertOver.setToAddresses(toAdd1);
                        AlertOver.setSaveAsActivity(False);
                        AlertOver.setHtmlBody(bod);
                        
                        try {
                                Messaging.sendEmail(new Messaging.Email[] {
                                    AlertOver
                                });
                            } catch (Exception e) {
                                System.debug('An unexpected error has occurred: ' + e.getMessage());
                            }
                        
                    }
                    else
                    {
                        
                        String bod = 'The following account will need to be renewed in '+days+' days.<br/>';
                        bod += 'Name: '+a.Name+ '<br/>';
                        bod += 'Contract Start Date: '+a.Contract_Start_Date__c+ '<br/>';
                        bod += 'Contract End Date: '+a.Contract_End_Date__c+ '<br/>';
                        bod += 'Contract Amount: $'+a.Contract_Amount__c+ '<br/>';
                        bod += 'Link: '+sURL+ '<br/>';
                        
                        AlertUnder.setSubject('ALERT: '+a.Name+ ' has a contract expiring in '+days+' days.');
                        AlertUnder.setToAddresses(toAdd2);
                        AlertUnder.setSaveAsActivity(False);
                        AlertUnder.setHtmlBody(bod);
                        
                        try {
                                Messaging.sendEmail(new Messaging.Email[] {
                                    AlertUnder
                                });
                            } catch (Exception e) {
                                System.debug('An unexpected error has occurred: ' + e.getMessage());
                            }
                        
                    }    
                }
            }
            
        }
        
       
   
    
    global void finish(Database.BatchableContext BC){      
        
    }
}


Schedule Class:

global class ScheduleBatchRenewal implements Schedulable {
   global void execute(SchedulableContext sc) {
      BatchRenewal b = new BatchRenewal(); 
      database.executebatch(b, 1);
   }
}


Test Class:​

@isTest
private class BatchRenewalTest
{
    @testSetup
    static void setup() 
    {
        List<Account> acct = new List<Account>();
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Apple'+i;
            Integer am = 250 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,6,10),Contract_Amount__c=am));
        }
        
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Orange'+i;
            Integer am = 5000 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,6,10),Contract_Amount__c=am));
        }
        
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Banana'+i;
            Integer am = 250 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,7,10),Contract_Amount__c=am));
        }
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Grape'+i;
            Integer am = 5000 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,7,10),Contract_Amount__c=am));
        }
        
        insert acct;
        System.debug(Acct);
        
    }
    
    static testmethod void test() 
    { 
    	Test.startTest();
        BatchRenewal uca = new BatchRenewal();
        uca.query='SELECT Id, Name, Contract_Start_Date__c, Contract_End_Date__c, Contract_Amount__c FROM Account WHERE Contract_End_Date__c != NULL AND Contract_Amount__c != NULL';
        Id batchId = Database.executeBatch(uca, 200);
        
        ScheduleBatchRenewal sch = new ScheduleBatchRenewal();
        sch.execute(null);
        
        Test.stopTest();
    }
}

Any help would be greatly appreciated!

 
Glyn Anderson (Slalom)Glyn Anderson (Slalom)
The problem is that you can't invoke 'sendEmail' more than 10 times in a single transaction, but you can send more than one email each time you call 'sendEmail'.  So the trick is to accumulate the email messages and send them all at once.  To do this across the entire batch job, I have added the Database.Stateful interface to the list of interfaces implemented.  There's no methods to implement for this interface, but it allows the instance variables of the class to persist between batches.

I also moved the repeated code into a separate private method to make the code easier to maintain.  The 'sendAlert' method creates an email message and adds it to the list of messages to be sent.  All of the email messages are sent in the 'finish' method of the batch class.

I also incorporated the Scheduleable functionality into the batch class, so you can delete the ScheduleBatchRenewal class, if you want.
global class BatchRenewal implements Database.Batchable<sObject>, Database.Stateful, Scheduleable
{
    private List<Messaging.SingleEmailMessage> emailMessages
        = new List<Messaging.SingleEmailMessage>();
    private List<String> overRecipients = new List<String>{ 'test@test.com' };
    private List<String> underRecipients = new List<String>{ 'test1@test.com' };

    global Database.queryLocator start( Database.BatchableContext bc )
    {
        String fieldNames = String.join
        (   new List<String>
            {   'Id'
            ,   'Name'
            ,   'Contract_Start_Date__c'
            ,   'Contract_End_Date__c'
            ,   'Contract_Amount__c'
            }
        ,   ','
        );

        return Database.getQueryLocator
        (   String.join
            (   new List<String>
                {   'SELECT', fieldNames
                ,   'FROM Account'
                ,   'WHERE Contract_End_Date__c != null AND Contract_Amount__c != null'
                }
            ,   ' '
            )
        );
    }

    global void execute( Database.BatchableContext BC, List<Account> accounts )
    {
        Date today = System.today();
        for ( Account account : accounts )
        {
            Date contractEnd = account.Contract_End_Date__c;
            Integer daysUntilContractEnd = contractEnd.daysBetween( today );

            if ( daysUntilContractEnd == 60 ) sendAlert( account, 60 );
            if ( daysUntilContractEnd == 30 ) sendAlert( account, 30 );
        }
    }

    private void sendAlert( Account account, Integer days )
    {
        Messaging.SingleEmailMessage emailMessage = new Messaging.SingleEmailMessage();

        emailMessage.setSubject
        (   String.format
            (   'ALERT: {0} has a contract expiring in {1} days.'
            ,   new List<String>{ account.Name, String.valueOf( days ) }
            )
        );

        emailMessage.setToAddresses
        (   (   account.Contract_Amount__c >= 2500
            ?   overRecipients
            :   underRecipients
            )
        );

        emailMessage.setSaveAsActivity( false );

        emailMessage.setHtmlBody
        (   String.format
            (   String.join
                (   new List<String>
                    {   'The following account will need to be renewed in {0} days.'
                    ,   'Name: {1}'
                    ,   'Contract Start Date: {2}'
                    ,   'Contract End Date: {3}'
                    ,   'Contract Amount: ${4}'
                    ,   'Link: {5}'
                    }
                ,   '<br/>'
                )
            )
        ,   new List<String>
            {   String.valueOf( days )
            ,   account.Name
            ,   account.Contract_Start_Date__c.format()
            ,   account.Contract_End_Date__c.format()
            ,   String.valueOf( account.Contract_Amount__c )
            ,   URL.getSalesforceBaseUrl().toExternalForm() + '/' + account.Id
            }
        );

        emailMessages.add( emailMessage );
    }

    global void finish( Database.BatchableContext BC )
    {
        try
        {
            Messaging.sendEmail( new List<Messaging.Email>{ emailMessages }, false );
        }
        catch ( Exception exceptionCaught )
        {
            System.debug
            (   'An unexpected error has occurred: '
            +   exceptionCaught.getMessage()
            );
        }
    }

    global void execute( SchedulableContext sc )
    {
        Database.executebatch( new BatchRenewal() );
    }
}
Please let me know if this works for you.
ApexNewbie77ApexNewbie77

Hi Glyn,

There were a few errors I had to fix but I manage to get it down to one error:

Initial expression is of incorrect type, expected: Messaging.Email but was: List<Messaging.SingleEmailMessage>

Any idea on how that could be happening?

 

global void finish( Database.BatchableContext BC )
    {
        try
        {
            Messaging.sendEmail( new List<Messaging.Email>{ emailMessages }, false );
        }
        catch ( Exception exceptionCaught )
        {
            System.debug('An unexpected error has occurred: '+   exceptionCaught.getMessage());
        }
    }
Glyn Anderson (Slalom)Glyn Anderson (Slalom)
Yes, my bad.  Replace the "finish" method with this version:
global void finish( Database.BatchableContext BC )
    {
        try
        {
            Messaging.sendEmail( (List<Messaging.Email>) emailMessages, false );
        }
        catch ( Exception exceptionCaught )
        {
            System.debug
            (   'An unexpected error has occurred: '
            +   exceptionCaught.getMessage()
            );
        }
    }
ApexNewbie77ApexNewbie77

Hi Gyln,

First of all, thank you for all the help! It is greatly appreciated! I was running into a lot of errors and was having trouble executing the code in the anonymous window since the Schedule functionality was in the batch class. I had to make some changes for certain things to work and compile without any errors. I was also trying to get my test coverage up but for some reason my coverage will not hit the Database.queryLocator start method.

The issue I am receiving now shows me this:

User-added image

 

Below is my code:

Batch Class:

global class BatchRenewal implements Database.Batchable<sObject>, Database.Stateful//, Schedulable
{
    private transient List<Messaging.SingleEmailMessage> emailMessages
        = new List<Messaging.SingleEmailMessage>();
    private List<String> overRecipients = new List<String>{ 'test@test.com' };
    private List<String> underRecipients = new List<String>{ 'test1@test.com' };

    global Database.queryLocator start( Database.BatchableContext bc )
    {
        String fieldNames = String.join
        (   new List<String>
            {   'Id'
            ,   'Name'
            ,   'Contract_Start_Date__c'
            ,   'Contract_End_Date__c'
            ,   'Contract_Amount__c'
            }
        ,   ','
        );

        return Database.getQueryLocator
        (   String.join
            (   new List<String>
                {   'SELECT', fieldNames
                ,   'FROM Account'
                ,   'WHERE Contract_End_Date__c != null AND Contract_Amount__c != null'
                }
            ,   ' '
            )
        );
    }

    global void execute( Database.BatchableContext BC, List<Account> accounts )
    {
        if(accounts != NULL)
        {    
        	Date today = System.today();
            for ( Account account : accounts )
            {
                Date contractEnd = account.Contract_End_Date__c;
                Integer daysUntilContractEnd = contractEnd.daysBetween( today );
    
                if ( daysUntilContractEnd == 60 ) sendAlert( account, 60 );
                if ( daysUntilContractEnd == 30 ) sendAlert( account, 30 );
            }
        }
    }

    private void sendAlert( Account account, Integer days )
    {
        Messaging.SingleEmailMessage emailMessage = new Messaging.SingleEmailMessage();

        emailMessage.setSubject('ALERT: '+account.Name+ ' has a contract expiring in '+days+' days.');

        emailMessage.setToAddresses
        (   (   account.Contract_Amount__c >= 2500
            ?   overRecipients
            :   underRecipients
            )
        );

        emailMessage.setSaveAsActivity( false );

        String sURL = URL.getSalesforceBaseUrl().toExternalForm()+ '/'+account.Id;
        
        String bod = 'The following account will need to be renewed in '+days+' days.<br/>';
                        bod += 'Name: '+account.Name+ '<br/>';
                        bod += 'Contract Start Date: '+account.Contract_Start_Date__c.format()+ '<br/>';
                        bod += 'Contract End Date: '+account.Contract_End_Date__c.format()+ '<br/>';
                        bod += 'Contract Amount: $'+account.Contract_Amount__c+ '<br/>';
                        bod += 'Link: '+sURL+ '<br/>';
        
        
        emailMessage.setHtmlBody(bod);

        emailMessages.add( emailMessage );
    }

    global void finish( Database.BatchableContext BC )
    {
        try
        {
            Messaging.sendEmail( (List<Messaging.Email>) emailMessages, false );
        }
        catch ( Exception exceptionCaught )
        {
            System.debug
            (   'An unexpected error has occurred: '
            +   exceptionCaught.getMessage()
            );
        }
    }

    /*global void execute( SchedulableContext sc )
    {
        Database.executebatch( new BatchRenewal() );
    }*/
}


Schedule Class:

global class ScheduleBatchRenewal implements Schedulable {
   global void execute(SchedulableContext sc) {
      BatchRenewal b = new BatchRenewal(); 
      database.executebatch(b, 200);
   }
}


Test Class:​

@isTest
private class BatchRenewalTest
{
    @testSetup
    static void setup() 
    {
        List<Account> acct = new List<Account>();
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Apple'+i;
            Integer am = 250 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,6,10),Contract_Amount__c=am));
        }
        
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Orange'+i;
            Integer am = 5000 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,6,10),Contract_Amount__c=am));
        }
        
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Banana'+i;
            Integer am = 250 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,7,10),Contract_Amount__c=am));
        }
        for(Integer i=0;i<3;i++)
        {
            String nam = 'Grape'+i;
            Integer am = 5000 + i;
            acct.add(new Account(Name=nam, Type='RAW',Sales_Team__c='B2B', Contract_End_Date__c=Date.newInstance(2018,7,10),Contract_Amount__c=am));
        }
        
        insert acct;
        System.debug(Acct);
        
    }
    
    static testmethod void test() 
    { 
    	Test.startTest();
        BatchRenewal uca = new BatchRenewal();
        uca.query='SELECT Id, Name, Contract_Start_Date__c, Contract_End_Date__c, Contract_Amount__c FROM Account WHERE Contract_End_Date__c != NULL AND Contract_Amount__c != NULL';
        Id batchId = Database.executeBatch(uca, 200);
        
        ScheduleBatchRenewal sch = new ScheduleBatchRenewal();
        sch.execute(null);
        System.abortJob(batchID);
        Test.stopTest();
    }
}
ApexNewbie77ApexNewbie77
I notice the image was to small, hopefully this is a little better
User-added image
Glyn Anderson (Slalom)Glyn Anderson (Slalom)
The scheduler will execute the batch so you don't need to execute the batch separately (and then abort it).  Try this for the testmethod:
    static testmethod void test()
    {
    	Test.startTest();
        new ScheduleBatchRenewal().execute(null);
        Test.stopTest();
    }

 
Glyn Anderson (Slalom)Glyn Anderson (Slalom)
BTW:  If you get this working, I would really appreciate it if you mark one of my posts as the solution.  Thank you so much!
Glyn Anderson (Slalom)Glyn Anderson (Slalom)
Oh - and if you want to restore the Schedulable functionality to the batch class, just make sure your test method looks something like this:
    static testmethod void test()
    {
        Test.startTest();
        new BatchRenewal().execute((SchedulableContext) null);
        Test.stopTest();
    }
That will help the compiler pick the correct one of the two "execute" methods.