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

Task Trigger Email

Hi All,


I need some help with my trigger.  I have the following trigger that sends an email to the task creator when the task assignee has completed the task.  I would like to include the name of the Related To object in the email.  I have the WhatId in my code but this only pulls the Id of the record.  I tried WhatName but i dont think there is such a field.  




trigger TaskSendEmail on Task (before 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 == '012G00000017Bb5' && tsk.Status == 'Completed' &&
tsk.OwnerId <> 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]);

for(Task tsk : Trigger.New){

if(tsk.RecordTypeId == '012G00000017Bb5' && tsk.Status == 'Completed'
tsk.OwnerId <> tsk.CreatedById)

User theUser = userMap.get(tsk.CreatedById);

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 Completed'); // 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 = 'The following Task has been Completed: \n\n';
template+= 'Related To: {0}\n';
template+= 'Subject: {1}\n';
template+= 'Comments: {2}\n\n';
template+= 'Click the link to access the record:{3}\n';
String duedate = '';

if (tsk.ActivityDate==null)
duedate = '';
duedate = tsk.ActivityDate.format();

List<String> args = new List<String>();

// Here's the String.format() call.
String formattedHtml = String.format(template, args);


Messaging.SendEmail(new Messaging.SingleEmailMessage[] {mail});



You would need to use the getSobjectType method to access the getDescribe method to call the getLabel method to convert whatever ID is currently populated in WhatId to the corresponding object label.  Here, I just wrote a working trigger real quick below that updates the subject field of a task with the "Related To" object name.  You can use it on a task trigger in your developer org or sandbox.


trigger convertIdToObjectName on Task (before insert, before update) {

    for(task t :{
    t.Subject = t.WhatId.getSObjectType().getDescribe().getLabel();



This is not quite what i was looking for.  To clarify, I need the record's name, not the name/label of the object.  For example, if the the Task is related to an Opportunity with a name of "OPP - 5-1-12", I want that name to show up on the email notification.  


I had a similar problem before - trying to get the actual Text Name of the Owner of the task, rather than the ID.


I am fairly sure that because WhatId / WhoId are polymorphic fields (eg. they could relate to various different objects), you cannot traverse them.


The only way I found to do was to SOQL the Id and retrieve the *actual* record you need (in your case, the Opportunity). Chances are you will want to do this in other places, and you will want to make sure it is bulkified - so might be best to make a short helper method, along the lines of the below. 



Set<Id> taskWhatId = new Set<Id>();  //  fill this with the incoming "WhatId"

Map<Id, Opportunity> oppIdMap = new Map<Id, Opportunity>([SELECT Id, Name FROM Opportunity WHERE Id =: taskWhatId]); // do a SOQL to retrieve the actual opportunity records, put results in map with the corresponding Opportunity Id.

Map<Id, String> taskOppTextNameMap = new Map<Id,String>(); // this is the map we will fill with WhatId | Opportunity.Name

// loop through the Task WhatIds, retrieve the related opportunity from oppIdMap (based on matching Id), get the Opportunity Name

	for(Id thisWhatId : taskWhatId) {
		if(oppIdMap.containsKey(thisWhatId)) {
			taskOppTextNameMap.put(thisWhatId, oppIdMap.get(thisWhatId).Name);



You can pass a set of the Task WhatIds to this helper method, and it can return the map of Task.WhatId | Opportunity.Name. You can then just to a "get" on the map for each task and retrieve the relevant Opportunity name.


Bit of a pain, but thats the only way I could do it. If bulking isn't an issue, you could just run the SOQL directly in your code, for that one Task - but I would not recommend it!



Ohhh, my bad.  Well you could definitely go carmant's route, but it would only really work for opportunities, because you're specifying the object in the query.  You could do a variation of the t.WhatId.getSObjectType().getDescribe().getName() method in order to extract the API object name of the ID in order to generate a dynamic SOQL query using Database.Query()... but ehh, that's too much typing.


Instead, I'd just specify the object relationship in the SOQL query fields, as opposed to specifying what object you want to query statically using the "from" portion of your SOQL statement.  That way you can query from the Task, instead of the *potential* Related To objects (which will constantly be changing, not to mention the addition of custom objects in the org would require updates to your code).  Then you could dynamically extract the record "Name" of any ID in your WhatId or WhoId fields now or in the future.


Here I'll just make this simple.  Here's the class and trigger you would need if you were making a basic trigger to accomplish this.  Here is the class...

public with sharing class TriggerFlags {
   public static boolean allowTaskUpdate = true;

 Here is the trigger...

trigger extractWhatName on Task (after insert, after update) {


  set<Task> records = new set<Task>();
  list<Task> updated = new list<Task>();

  for(Task t : [select Id, What.Name from Task where Id IN: records]){
   t.Subject = t.What.Name;
  TriggerFlags.allowTaskUpdate = false;
  update updated;
  TriggerFlags.allowTaskUpdate = true;

 Since you're not actually saving anything to the record in *your* particular case (you're just sending an email), you can actually forget the class portion of my code (the TriggerFlags stuff).  But if you want to create it as a trigger in your developer org right now to take her for a test drive, then you need both parts so that the trigger doesn't loop back on itself once you do the DML update.  The key thing though is that, in order to do a dynamic extraction for the parent ID record names, the record needs to be saved (after insert / after update).  Otherwise, it would just display a null result in the task subject field.


Speaking of which, try updating your trigger to an After Update, and specify What.Name as the name of the record you want to extract.  Let me know if that works (it miiiiighhhtt... but you might need to actually perform a SOQL query to extract it like I did above, so it might not work... but then again, it might work, not sure.  Try it and see.)  Otherwise, the code above will definately work.