I have configured finish method to send error files on csv upload. It works ok for 200 records but if the sheet exceeds records more than 200 say 2000 I am getting emails for every job that has finished.

So after the job is finished I have 5 error files in my mailbox. Is there a any way to handle this.

 global void finish(Database.BatchableContext BC)
            asyncapexjob a = [select Status,id ,apexclassid,jobitemsprocessed,totaljobitems,numberoferrors,createdby.email from asyncapexjob where id=:BC.getJobId()];
                string body = 'Dear User \n'
                    +'Your bulk upload of Sites' 
                    +'has finished. \n'
                    +'There were '
                    +' errors. Please find the error list attached .';
                string finalstr ='sno,Name,red,error \n';
                string subject ='error file subject';
                string attname = 'errorfile.csv';
                for(decimal id:errormap.keyset())
                    string err = errormap.get(id);
                    object1__c s = (object1__c)idtosobjectmap.get(id);
                    string recng = '"'+s.sno__c+'","'+s.Name+'","'+s.red__c+'","'+err+'"\n';
                    str = str+recng;
                messaging.SingleEmailMessage email = new messaging.SingleEmailMessage();
                messaging.EmailFileAttachment efa = new messaging.EmailFileAttachment();
                email.setToAddresses(new string[] {a.createdby.email});
                email.setFileAttachments(new messaging.EmailFileAttachment[] {efa});
                messaging.SendEmailResult [] r = messaging.sendEmail(new messaging.SingleEmailMessage[] {email});

  • List<Database.SaveResult> srs = Database.insert(list1, false); integer Index = 0; for (Database.SaveResult sr : srs) { if (!sr.isSuccess()) { String errmsg = sr.geterrors()[0].getmessage(); errormap.put(list1[index].Row__c,errmsg); idtosobjectmap.put(list1[index].Row__c,list1[index]); } index++; } } I am already logging the errors. The problem here is the finish method is firing for every batch. For 400 records - 2 emails are triggered
    – Badduboy
    Sep 12, 2020 at 6:24
  • Are you invoking the batch from a trigger? If so, any more than 200 objects in a DML will mean the DML gets chunked, and the trigger run multiple times, each with a maximum of 200 objects from the full set. This in turn would invoke your batch with that subset and you would then see finish called multiple times. Invoking a batch directly in a trigger should generally be avoided...
    – Phil W
    Sep 12, 2020 at 7:20
  • I am trying to insert records in bulk from a CSV file. So I used a staging object to dump the CSV data and in the after insert trigger of the staging object, I am invoking batch class to create the necessary records. Can you suggest a better approach for this?
    – Badduboy
    Sep 12, 2020 at 9:14
  • Then that is the reason for this. I suggest you change the way you invoke the batch. I will post an answer in a bit.
    – Phil W
    Sep 12, 2020 at 9:52
  • A question for you first: the records - is there a way to identify those that need to be processed?
    – Phil W
    Sep 12, 2020 at 10:00

1 Answer 1


Since you are inserting large numbers of objects, your trigger is invoked multiple times. Triggers for custom objects receive at most 200 records at a time. This is why you get multiple executions of your batch and thus multiple emails.

In order to address this, I would do the following:

  • Provide an after insert trigger for the staging object
  • Have this look to see if there is an instance of the batch already running (use the AsyncApexJobs table to check this) and, if not, use System.scheduleBatch to schedule its execution in 1 minute time. The reason to do this is two fold:
    • You want to avoid unnecessary batch executions to reduce the number of unnecessary async executions (you are limited to 250000 per day).
    • You cannot schedule more than one instance of the batch using the same job name, so this will filter out many of the additional executions caused by chunking of bulk loading trigger execution. You will need to catch the exception that will happen when a job is already scheduled.
  • Have the batch start check to see if there is already an instance of the batch running (just like the trigger) and if there is return an empty query locator (like "SELECT Id FROM Staging_Object__c WHERE Id = NULL"). This ensures that you don't actually have multiple batch instances trying to do the processing. This must ensure the batch state indicates that it should do nothing in finish. If there is no existing running batch instance then return a query locator that finds the still-to-be-processed staging objects.
  • Have the batch execute mark the staging objects as processed (or delete them).
  • Have the batch finish do two things:
    • Send the required email if there were issues and
    • Perform a COUNT query with the same criteria as the start method query locator to see if there are any new imported staging objects to process and, if there are, use System.scheduleBatch with the same job name as the staging object trigger used (again catching any exception) again with a 1 minute delay, in order to ensure that new staging objects are processed in a second execution.

Because of the use of a 1 minute schedule delay in the trigger, multiple invocations of the trigger will generally still result in just one execution of the batch.

Because of the test in start to see if there's a batch already running, you won't have multiple instances running together and generating separate (or even duplicate) error files.

Because of the re-scheduling in finish, if new staging objects have appeared since the batch started these will get processed in a follow-up execution. Yes, in this case you will get two fail emails (for example), but this should be relatively rare assuming the whole bulk load and staging object creation takes less than 60 seconds. You could always increase the delay to reduce the chance of this happening too.

What I've described is a cut-down version of what we call an "adaptive batch". You can find more detail on that here.

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