0

I am deactivating no of users and when user is deactivated then all of the open opportunities of user has been assigned to their Manager.

But I am getting below error message: Error Message:

Apex script unhandled exception by user/organization: / Source organization: (null) Failed to invoke future method 'public static void changeOwner(List)' on class 'ChangeOpportunityOwner' for job id ''

caused by: System.LimitException: Too many Email Invocations: 11

Below is the Apex Code:

public without sharing class ChangeOpportunityOwner {       
  @Future
  public static void changeOwner(List<Id> usrIds){
    Boolean isUpdate=false;
    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    Map<Id, Boolean> mapUserToActive = new Map<Id, Boolean>();
    try{
       for (User u1 : [SELECT Id, isActive FROM User]) { 
         mapUserToActive.put(u1.Id, u1.isActive);
       }
       List <user>usrs= [Select Id,Name,ManagerId,IsActive from user where Id=:usrIds];
       List<Opportunity> finalOptList=new List<Opportunity>();
       for(User u:usrs){   
         List<Opportunity>optys=[Select Id,OwnerId,StageName from Opportunity where OwnerId=:u.Id]; 
         for(Opportunity opt:optys){
           if(opt.StageName!='Closed Won' && opt.StageName!='Closed Lost')
           {
              if (mapUserToActive.get(u.ManagerId)== true &&                 mapUserToActive.get(u.ManagerId)!=null) {
                 opt.OwnerId=u.ManagerId;
              }
              else{ 
                  User usr= [Select Id,Name,ManagerId from user where Name='Manager’];
                  opt.OwnerId=usr.Id;
               }
      finalOptList.add(opt);
    // Set list of people who should get the email
      List<String> sendTo = new List<String>();
      sendTo.add(u.ManagerId);
      mail.setToAddresses(sendTo);
    //  Set who the email is sent from
      mail.setReplyTo('flow@test.com');
      mail.setSenderDisplayName('Support');
     // Set email contents - you can use variables!
      mail.setSubject('Open Opportunities transferred to you');
      String body = 'Dear Manager,<br> ';
      body +='<br>';
      body += 'Please note that '+u.name+'  has been deactivated as a Salesforce User hence all of its <br> Open Opportunities has been assigned to you if any.<br>';
      body +='<br>';
      body +='Kind regards,<br>';
      body +='<br>';
      body +='SFDC Support';
      mail.setHtmlBody(body);
isUpdate=true;
}
}
if(isUpdate)
Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail},false);
}
if(isUpdate)
{
update finalOptList;
}
}
catch(Exception e)
{
system.debug('An Exception Occured: '+e.getMessage());
}
}


@InvocableMethod(label='Change Opportunity Owner' description='It Change the owner of opportunity when owner is inactive')
public static void changeOwnerFuture(List<Id> usrIds)
{
changeOwner(usrIds);
}
} 

Can somebody please help me what is wrong in apex code? The opportunity reassignment email that I am sending via apex,it crossed the limit...

Below updated code fix the Too many email invocation error but when I deactivate multiple users and when manager receives an email,it displays only first user information. Lets say I am deactivating 3 users A,B,C has same manager and when manager receives an email,total 3 mails received but in all emails it displays User A's information. Updated Code:

public without sharing class ChangeOpportunityOwner {       
@Future
public static void changeOwner(Id[] usrIds){
boolean isUpdate=false;
boolean isUpdate1=false;
boolean isUpdate2=false;
Id genericManagerId = [SELECT Id FROM User WHERE Name = 'Dhananjay Patil'].Id;
String genericName=[Select Id,Name from User where Id=:genericManagerId].Name;
Map<Id,User> usrMap=new Map<Id,User>([Select Id,Name,Manager.Name from User where Id=:usrIds]);
Opportunity[] opps = [SELECT Owner.Name,Owner.Manager.Name,Owner.ManagerId,Owner.Manager.IsActive FROM Opportunity WHERE OwnerId =:usrIds AND IsClosed = FALSE];
Set<Id> managerIds = new Set<Id>();
for(Opportunity opp: opps) {
if(opp.Owner.ManagerId != null && opp.Owner.Manager.IsActive) {
opp.OwnerId = opp.Owner.ManagerId;
managerIds.add(opp.OwnerId);
isUpdate=true;
} else {
opp.OwnerId = genericManagerId;
managerIds.add(opp.OwnerId);
isUpdate1=true;
}

}

Messaging.Email[] messages = new Messaging.Email[0];
Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
Id templateId=[Select Id,DeveloperName,body,HtmlValue from EmailTemplate where DeveloperName=:'Assign_Opportunities_to_New_Owner'].Id;
EmailTemplate templ=[Select Id,HtmlValue,body from EmailTemplate where Id=:templateId];
OrgWideEmailAddress owa=[Select Id,Address,DisplayName from OrgWideEmailAddress where Address='test@test.com'];

for(User u1:usrMap.values()){ 
message.setSubject('Open Opportunities transferred to you');
message.setTargetObjectId(u1.managerId);
message.setOrgWideEmailAddressId(owa.Id);
message.setSaveAsActivity(false);
message.setTemplateId(templateId);
//Setting PlainText Body
string test = '';
test=templ.body.replace('{!Opportunity.OwnerFullName}',u1.Name);
message.setPlainTextBody(test);
messages.add(message);
isUpdate2=true;
// } 
}
If((isUpdate||isUpdate1)&&isUpdate2){
Messaging.sendEmail(messages, false);
Database.update(opps, false);
}
}
@InvocableMethod(label='Change Opportunity Owner' description='It Changes the owner of opportunity when owner is inactive')
public static void changeOwnerFuture(Id[] usrIds)
{
changeOwner(usrIds);
}
}
  • 3
    First of all you need to remove the SOQL statement from the for loop – RCS Jan 2 '17 at 12:20
3

You can only call Messaging.sendEmail 10 times per transaction, but you can put many Messaging.Email items in a list.

Messaging.Email[] messages = new Messaging.Email[0];
for(...) {
  Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
  // Set mail parameters
  messages.add(mail);
}
Messaging.sendEmail(messages);

Realistically, you've massively over-engineered your code. Here's how I would optimize your code:

@future public static void changeOwners(Id[] userIds) {
  Id genericManagerId = [SELECT Id FROM User WHERE Name = 'Manager'].Id;
  Opportunity[] opps = [SELECT Owner.ManagerId, Owner.Manager.IsActive FROM Opportunity WHERE OwnerId = :userIds AND IsClosed = FALSE];
  Set<Id> managerIds = new Set<Id>();
  for(Opportunity opp: opps) {
    if(opp.Owner.ManagerId != null && opp.Owner.Manager.IsActive) {
      opp.OwnerId = opp.Owner.ManagerId;
      managerIds.add(opp.OwnerId);
    } else {
      opp.OwnerId = genericManagerId;
    }
  }
  Messaging.Email[] messages = new Messaging.Email[0];
  for(Id managerId: managerIds) {
    Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
    message.setSubject('You have new opportunities.');
    message.setTargetObjectId(managerId);
    message.setSaveAsActivity(false);
    message.setPlainTextBody('You have new opportunities, etc, etc.');
    messages.add(message);
  }
  Messaging.sendEmail(messages, false);
  Database.update(opps, false);
}
  • If 1 user has more than 1 opportunities then I have to send only one notification email to thir manager. Messaging.sendEmail(messages); that u suggested,will it work in this case? – Dhananjay Patil Jan 2 '17 at 13:54
  • @DhananjayPatil You do have other problems with your code; you'll probably want something like a Map<Id, User[]> so you can keep track of all users deactivated by ManagerId, at which point you can add one email per manager. – sfdcfox Jan 2 '17 at 13:59
  • If you can see when Opportunity for loop ends,I am sending an email to the manager. if(isUpdate) Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail},false); If users are multiple then it sends no of notification email to the manager. – Dhananjay Patil Jan 2 '17 at 14:03
  • My Requirement is I have to deactivate list of users who is idle for More than 40 days.I take out the list of users from Report Tab and with the help of data loader I deactivated the user.When User Deactivate,it calls process builder which send first email alert notification to the inactive user and their manager email address.Then it calls my apex class method inwhich all open opportunities assigned to the manager and sends an Opportunity Assignment notification to the manager via apex.Means As Manager I receive 2 notifications 1 is user deactivation by email alerts service and then opt.. – Dhananjay Patil Jan 2 '17 at 14:08
  • @DhananjayPatil Basically, query the opportunities, loop over them to build a map based on manager Id, and then loop over the map's contents to build the emails. You might want to read more on bulkification. – sfdcfox Jan 2 '17 at 14:23
1

Avoid the soql and sendemail method within the for loop.I have refined your code as below.Redefine the logic as you need

Also what are you doing here?

for (User u1 : [SELECT Id, isActive FROM User]) { 
            mapUserToActive.put(u1.Id, u1.isActive);
        }

it will fetch all the users in the org . filter the users based on the condition

public without sharing class ChangeOpportunityOwner {       
@Future
public static void changeOwner(List<Id> usrIds){
    Boolean isUpdate=false;
    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    Map<Id, Boolean> mapUserToActive = new Map<Id, Boolean>();


         User usr= [Select Id,Name,ManagerId from user where Name='Manager'];


        Map <id,user> usrMap = new Map<id,user>();
        set<id> mgrIds=new set<id>();
         for(User u:[Select Id,Name,ManagerId,IsActive from user where Id=:usrIds])
         {
             usrmap.put(u.id,u);
             mgrIds.add(u.managerid);

         }
         Map<id,user> managerUsrMap = new Map<id,user>([Select Id,Name,ManagerId,IsActive from user where Id in :mgrIds and isactive=true]);
        map<id,List<opportunity>> usrOppMap = new Map<id,List<opportunity>>();
        List<Opportunity> finalOptList=new List<Opportunity>();
        List<Messaging.SingleEmailMessage> msgList = new List<Messaging.SingleEmailMessage>();

        for(opportunity o:[Select Id,OwnerId,StageName from Opportunity where OwnerId in :usrMap.keyset()])
        {
            if(usrOppMap.containskey(o.ownerid))
            {
                List<opportunity> oppList = usrOppMap.get(o.ownerid);
                oppList.add(o);
                usrOppMap.put(o.ownerid,oppList);
            }
            else{
                usrOppMap.put(o.ownerid,new List<Opportunity> { o });
            }
        }          
        for(User u:usrMap.values()){   

            for(Opportunity opt:usrOppMap.get(u.id){
                if(opt.StageName!='Closed Won' && opt.StageName!='Closed Lost')
                {
                    if (managerUsrMap.containskey(u.managerid)) {
                        opt.OwnerId=u.ManagerId;
                    }
                    else{ 
                        opt.OwnerId=usr.Id;
                                   }
                                   finalOptList.add(opt);
                                   // Set list of people who should get the email
                                   List<String> sendTo = new List<String>();
                                   sendTo.add(u.ManagerId);
                                   mail.setToAddresses(sendTo);
                                   //  Set who the email is sent from
                                   mail.setReplyTo('flow@test.com');
                                   mail.setSenderDisplayName('Support');
                                   // Set email contents - you can use variables!
                                   mail.setSubject('Open Opportunities transferred to you');
                                   String body = 'Dear Manager,<br> ';
                                   body +='<br>';
                                   body += 'Please note that '+u.name+'  has been deactivated as a Salesforce User hence all of its <br> Open Opportunities has been assigned to you if any.<br>';
                                   body +='<br>';
                                   body +='Kind regards,<br>';
                                   body +='<br>';
                                   body +='SFDC Support';
                                   mail.setHtmlBody(body);
                                   isUpdate=true;
                                   msgList.add(mail);
                                   }
                                   }


                                   }


                                       update finalOptList;



                                       Messaging.sendEmail(msgList,false);




}


                                   @InvocableMethod(label='Change Opportunity Owner' description='It Change the owner of opportunity when owner is inactive')
                                   public static void changeOwnerFuture(List<Id> usrIds)
                                   {
                                       changeOwner(usrIds);
                                   }

                                       } 
  • 1
    The try-catch isn't necessary, the query could be optimized, and the guards around the DML and sendEmail are no longer necessary. – sfdcfox Jan 2 '17 at 14:26
  • @PreyaMihandoss for (User u1 : [SELECT Id, isActive FROM User]) { mapUserToActive.put(u1.Id, u1.isActive); } in this loop I check Manager is active or not.If Manager is Active then I am assigning all opportunities to User's manager.If user does not have manager or manager is blank then I am assigning all open optys to the special business person – Dhananjay Patil Jan 2 '17 at 14:26
  • updated as per sfdcfox's comment and op's comment – Preya Jan 2 '17 at 14:53
  • Thanks Preya,This is also works..Thank You so much for your help.. – Dhananjay Patil Jan 3 '17 at 10:04
0

You can't send more than 10 emails with the sendEmail() method because of governor limits :

Total number of sendEmail methods allowed : 10

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm

If you want to send more than 10 emails in a process, you need to use batch :

http://forceguru.blogspot.fr/2011/03/how-to-send-more-than-10-e-mails.html

  • Indeed! I have already referred the link that you mentioned but having some issue how do i modify it in my existing code. – Dhananjay Patil Jan 2 '17 at 12:22
  • You need to use batch if you send more than 10 emails ! You have to rewrite your code, basing on the link I gave you in my answer. – SF_user Jan 2 '17 at 12:23
  • If You can see the code,I am using future invocable annotation.This method is called from my process builder and the reason that I am using future is because I am doing DML operation on Setup and Non-Setup object.So i am wondering If I use the batch here,will it call from my process builder or not? – Dhananjay Patil Jan 2 '17 at 12:29
  • You can use batch in future method ! – SF_user Jan 2 '17 at 12:37

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.