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
jpbenjpben 

Recursive Trigger

 

Hello,

 

I have a code below that sends an email when a task has been completed.  The issue I have is that the email is being sent twice.  This is due to a Workflow/Field Update on the Task. When the WF fires, the code below sends another email.  I've read that you can create a recursive trigger to prevent this from happening.  What is the proper way to write this recursive trigger based on my code below?  Thanks in advance. 

 

 

 

trigger TaskSendEmail on Task (after update)
{
// Don't forget this- all triggers in SF are bulk triggers and so
    // they can fire on multiple objects. So you need to process objects
    // in a FOR loop.
    Set<Id> CBIds = new Set<Id>();
   
    for(Task tsk: Trigger.New)
    {
    	if(tsk.RecordTypeId == '012Z00000004WFV' && tsk.Status == 'Completed')
        {
        	CBIds.add(tsk.CreatedById);
        }
    }
       
   
    // Build a map of all users who created the tasks.
    Map<Id, User> userMap = new Map<Id,User>([select Name, Email from User where Id in :CBIds]);// Creating map for users who satisfied the conditions
    for(Task tsk : Trigger.New)
    {
        if(tsk.RecordTypeId == '012Z00000004WFV' && tsk.Status == 'Completed')
        {
        	User theUser = userMap.get(tsk.CreatedById);  // you will not have the user records for all the tasks createdbyIds
        
	        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
	        String[] toAddresses = new String[] {theUser.Email};
	        mail.setToAddresses(toAddresses);    // Set the TO addresses
	        mail.setSubject('The following Task has been Closed');    // Set the subject
	        // Next, create a string template. Specify {0}, {1} etc. in place of actual values.
	        // You can replace these values with a call to String.Format.
	        String template = 'Hello {0}, \nYour task has been modified. Here are the details - \n\n';
	        template+= 'Subject - {1}\n';
	        template+= 'Due Date - {2}\n';
	        String duedate = '';
	        
	        if (tsk.ActivityDate==null)
	            duedate = '';
	        else
	            duedate = tsk.ActivityDate.format();
	            
	        List<String> args = new List<String>();
	        args.add(theUser.Name);
	        args.add(tsk.Subject);
	        args.add(duedate);
	        
	       
	        // Here's the String.format() call.
	        String formattedHtml = String.format(template, args);
	       
	        mail.setPlainTextBody(formattedHtml);
	        Messaging.SendEmail(new Messaging.SingleEmailMessage[] {mail});
	    }
    }
}
Jeff MayJeff May

One question -- why use a trigger? How about putting an Email Alert on your WF Rule so when the WF fires, and does its update, the Task notification gets sent?

Rahul SharmaRahul Sharma

Jeff is correct.

Avoid using apex whenever and wherever you can use standard functionality. 

 

 

For avoiding the recursion, There are two options:

1. Compare old and new values in entry criteria.

2. Controlling Recursive Triggers - This is the better way.

 

I tried to modify your trigger to check for old and new status value as entery criteria.

If this doesn't help, go for option#2

 

trigger TaskSendEmail on Task (after update)
{
// Don't forget this- all triggers in SF are bulk triggers and so
    // they can fire on multiple objects. So you need to process objects
    // in a FOR loop.
    Set<Id> CBIds = new Set<Id>();
   
    for(Task tsk: Trigger.New)
    {
    	if(tsk.RecordTypeId == '012Z00000004WFV' && tsk.Status == 'Completed' 
		&& Trigger.oldMap.get(tsk.Id).status != tsk.Status)
        {
        	CBIds.add(tsk.CreatedById);
        }
    }
       
   
    // Build a map of all users who created the tasks.
    Map<Id, User> userMap = new Map<Id,User>([select Name, Email from User where Id in :CBIds]);// Creating map for users who satisfied the conditions
    for(Task tsk : Trigger.New)
    {
        if(tsk.RecordTypeId == '012Z00000004WFV' && tsk.Status == 'Completed'
		&& Trigger.oldMap.get(tsk.Id).status != tsk.Status)
        {
        	User theUser = userMap.get(tsk.CreatedById);  // you will not have the user records for all the tasks createdbyIds
        
	        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
	        String[] toAddresses = new String[] {theUser.Email};
	        mail.setToAddresses(toAddresses);    // Set the TO addresses
	        mail.setSubject('The following Task has been Closed');    // Set the subject
	        // Next, create a string template. Specify {0}, {1} etc. in place of actual values.
	        // You can replace these values with a call to String.Format.
	        String template = 'Hello {0}, \nYour task has been modified. Here are the details - \n\n';
	        template+= 'Subject - {1}\n';
	        template+= 'Due Date - {2}\n';
	        String duedate = '';
	        
	        if (tsk.ActivityDate==null)
	            duedate = '';
	        else
	            duedate = tsk.ActivityDate.format();
	            
	        List<String> args = new List<String>();
	        args.add(theUser.Name);
	        args.add(tsk.Subject);
	        args.add(duedate);
	        
	       
	        // Here's the String.format() call.
	        String formattedHtml = String.format(template, args);
	       
	        mail.setPlainTextBody(formattedHtml);
	        Messaging.SendEmail(new Messaging.SingleEmailMessage[] {mail});
	    }
    }
}

 

On a side note: Never hardcode recordtypes in trigger, Either query them or fetch them from Schema using describe calls.

jpbenjpben

You cannot create an Email Alert fired by WF rule on Task object.

APathakAPathak

Hi.

trigger TaskSendEmail on Task (after update)
{
// Don't forget this- all triggers in SF are bulk triggers and so
    // they can fire on multiple objects. So you need to process objects
    // in a FOR loop.
    if(TriggerRecursionController.hasSentEmails == false)
	{
		hasSentEmails = true;
		Set<Id> CBIds = new Set<Id>();
   
		for(Task tsk: Trigger.New)
		{
			if(tsk.RecordTypeId == '012Z00000004WFV' && tsk.Status == 'Completed')
			{
				CBIds.add(tsk.CreatedById);
			}
		}
		   
	   
		// Build a map of all users who created the tasks.
		Map<Id, User> userMap = new Map<Id,User>([select Name, Email from User where Id in :CBIds]);// Creating map for users who satisfied the conditions
		for(Task tsk : Trigger.New)
		{
			if(tsk.RecordTypeId == '012Z00000004WFV' && tsk.Status == 'Completed')
			{
				User theUser = userMap.get(tsk.CreatedById);  // you will not have the user records for all the tasks createdbyIds
			
				Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
				String[] toAddresses = new String[] {theUser.Email};
				mail.setToAddresses(toAddresses);    // Set the TO addresses
				mail.setSubject('The following Task has been Closed');    // Set the subject
				// Next, create a string template. Specify {0}, {1} etc. in place of actual values.
				// You can replace these values with a call to String.Format.
				String template = 'Hello {0}, \nYour task has been modified. Here are the details - \n\n';
				template+= 'Subject - {1}\n';
				template+= 'Due Date - {2}\n';
				String duedate = '';
				
				if (tsk.ActivityDate==null)
					duedate = '';
				else
					duedate = tsk.ActivityDate.format();
					
				List<String> args = new List<String>();
				args.add(theUser.Name);
				args.add(tsk.Subject);
				args.add(duedate);
				
			   
				// Here's the String.format() call.
				String formattedHtml = String.format(template, args);
			   
				mail.setPlainTextBody(formattedHtml);
				Messaging.SendEmail(new Messaging.SingleEmailMessage[] {mail});
			}
		}
	}
	
}

 Trigger Recursion Controller Class

public class triggerRecursionController
{
	public static Boolean hasSentEmails = false;
}