0

We have a Batch Apex that loops over 9 Custom objects and sends out one email to each user who are assigned in any one of the custom object. This should send out one email to each even though if they are assigned in Multiple objects. The Batch Runs fine however for few users, it is sending out Multiple emails. This happens only with few users but not all. I verified the code and tested in Sandbox, and its the same users who are impacted in production. Here is the code of Schedule batch and first 2 object loops, this goes on for other objects. Not sure why this is firing for multiple emails for some users even though I'm storing the user id's to avoid duplicate emails.

global class scheduledBatchable implements Schedulable, Database.Stateful   {

global void execute(SchedulableContext sc) { 
   PatientRelationReviewBatchable PRR=new PatientRelationReviewBatchable();
   Id batchId = Database.executeBatch(PRR);
} 

}

global class PatientRelationReviewBatchable Implements Database.Batchable <sObject> ,Database.Stateful {

/*public PatientRelationReviewBatchable(Set<Id> PrevuserIds){
    this.PrevuserIds = PrevuserIds;
}*/

global Set<Id> prevUserIds; 
Set<Id> userIds;
public static final String PATIENT_FEEDBACK_REVIEW_STATUS = 'In Progress';
public static final String PATIENT_FEEDBACK_REVIEW_STATUSNEW = 'New';

public PatientRelationReviewBatchable(){
    prevUserIds = new Set<Id>();
    userIds = new Set<Id>();
}

global Database.QueryLocator Start( Database.BatchableContext BC) {
    
    String query = 'SELECT id, Assigned_Reviewer__c, Status__c FROM Patient_Feedback_Review__c  WHERE Status__c =: PATIENT_FEEDBACK_REVIEW_STATUS OR Status__c =: PATIENT_FEEDBACK_REVIEW_STATUSNEW';
    return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<Patient_Feedback_Review__c> pfrList){
    userIds = new Set<Id>();
    for(Patient_Feedback_Review__c pfr: pfrList){
        if(pfr.Assigned_Reviewer__c!= null){
            if(!prevUserIds.contains(pfr.Assigned_Reviewer__c)){
                userIds.add(pfr.Assigned_Reviewer__c);
                prevUserIds.add(pfr.Assigned_Reviewer__c);
            }
        }
    }
    EmailTemplate template = [SELECT id,Name, DeveloperName FROM EmailTemplate WHERE DeveloperName = 'Patient_relations_Reviews'];
    
    List<OrgWideEmailAddress> lstEmailAddress=[select Id from OrgWideEmailAddress WHERE Address='tripss@rush.edu'];
    Messaging.SingleEmailMessage[] mails = new Messaging.SingleEmailMessage[0];
    for(Id userId: userIds) {
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setTargetObjectId(userId);
        mail.setSaveAsActivity(false); 
        mail.setTemplateId(template.id);
        mails.add(mail);
        mail.setOrgWideEmailAddressId(lstEmailAddress[0].Id);
    }
    Messaging.sendEmail(mails);
}

global void finish(Database.BatchableContext BC) {
    PatientEventReviewBatchable PER=new PatientEventReviewBatchable(prevUserIds);
    Id batchId = Database.executeBatch(PER);
}

}

global class PatientEventReviewBatchable implements Database.Batchable <sObject> ,Database.Stateful {

global Set<Id> prevUserIds; 
Set<Id> userIds;
public static final String PATIENT_EVENT_REVIEW_STATUS = 'In Progress';
public static final String PATIENT_EVENT_REVIEW_STATUSNew = 'New';

public PatientEventReviewBatchable(Set<Id> prevUserIds){
    this.prevUserIds = prevUserIds;
    userIds = new Set<Id>();
}



global Database.QueryLocator Start( Database.BatchableContext BC) {
    
    String query = 'SELECT id, Assigned_Reviewer__c, Status__c FROM Review__c   WHERE Status__c = :PATIENT_EVENT_REVIEW_STATUS OR Status__c= :PATIENT_EVENT_REVIEW_STATUSNew' ;
    return Database.getQueryLocator(query);
}
 
global void execute(Database.BatchableContext BC, List<Review__c> ListReview) {
    
    EmailTemplate template = [SELECT id,Name, DeveloperName FROM EmailTemplate WHERE DeveloperName = 'Patient_relations_Reviews'];
    List<OrgWideEmailAddress> lstEmailAddress=[select Id from OrgWideEmailAddress WHERE Address='tripss@rush.edu'];
    Messaging.SingleEmailMessage[] mails = new Messaging.SingleEmailMessage[0]; 
    
    Set<Id> userIds = new Set<Id>(); 
    
    for(Review__c rev: ListReview ){
        if(rev.Assigned_Reviewer__c!= null){
            if(!prevUserIds.contains(rev.Assigned_Reviewer__c)){
                userIds.add(rev.Assigned_Reviewer__c);
                prevUserIds.add(rev.Assigned_Reviewer__c);
            }
        }
    }
    
    for(Id userId: userIds) {
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setTargetObjectId(userId);
        mail.setSaveAsActivity(false); 
        mail.setTemplateId(template.id);
        mails.add(mail);
        mail.setOrgWideEmailAddressId(lstEmailAddress[0].Id);
    }
    Messaging.sendEmail(mails);
}

global void finish(Database.BatchableContext BC) {
    CommiteeEventReviewBatchable CER=new CommiteeEventReviewBatchable(prevUserIds);
    Id batchId = Database.executeBatch(CER);
}

}

4
  • Is it possible there are two Batch chains running that overlap? Is the exclusion logic in execute() identical for all nine batch classes? Do any of the finish() methods differ in how they pass the ids across? I don't see anything obviously wrong in this code.
    – David Reed
    Jun 9 at 18:32
  • As a side note, this does look to me like an opportunity to write a single batch class that accepts parameters for its object, query, etc. and then works dynamically, to reduce the scope of code you need to examine.
    – David Reed
    Jun 9 at 18:33
  • All execute Methods has the same list name ListReview, except first one PatientRelationReviewBatchable which has pfrList and all Finish methods use prevUserIds. Would that make any difference and cause this problem?
    – Saaki
    Jun 9 at 18:51
  • Side question... I've never seen a List created this way: Messaging.SingleEmailMessage[] mails = new Messaging.SingleEmailMessage[0]; (I've not seen the zero). I've seen [] for no specific number of elements, and I've seen it with a number >0 and then later code to explicitly add specific elements (e.g. myList.add(1, someValue)). Is [0] functionally the same as []?
    – Moonpie
    Jun 9 at 20:17
2

You're sending emails in the execute method of your Batch class, so it's going to send emails for every batch of records. Most likely, the users who are getting it multiple times are probably the users in the first batch. They get the email when the first batch executes and the again on successive batches. The users only getting the email once are probably in the last batch. If you move the emails to the finish method, it will only fire once.

3
  • Sorry, not sure if i understand it. Are you saying i should move email method to finish() method for all 9 batches?
    – Saaki
    Jun 9 at 21:51
  • Yes. To expand a bit on the problem. You're implementing the Database.Stateful interface, which means that userIds is not getting cleared when each batch of records is run. So the list of users is increasing every time the batch runs, with the users in the first batches get multiple emails, and the users in the last batch getting one email. If you need the email send to specifically be a part of the execute method and run for each batch, then you'll want to move the userIds variable to the execute method so that it gets reset on each batch. Jun 9 at 21:59
  • Thank you! I did little digging in to the users who were getting multiple emails and all of them were qualifying in 2 batches. I was not storing userIds in the execute method for 2 those 2 batches which triggered multiple emails.
    – Saaki
    Jun 11 at 13:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.