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

setWhatId is inconsistent between api and salesforce UI

"Send an Email" through the salesforce UI allows you to specify a "Related To" record which can be of type custom object OR standard object.

However the WhatId property through the API only allows standard objects.  Is this expected?

-------------- from the apex language reference -------------------------
Optional. If you specify a contact for the targetObjectId field, you can specify a whatId as well. This helps to further ensure that merge fields in the template contain the correct data. The value must be one of the following types:
  • Account
  • Asset
  • Campaign
  • Case
  • Contract
  • Opportunity
  • Order
  • Product
  • Solution
  • Custom


I'm trying to find similar information. I have a contact as my target but I want to include information from a lead in the template.


Initially I assumed the WhatId would act as the related to option in the UI, so I could do this, but the documentation says otherwise....


So is this a good thread question being otherwise  ignored? Or am Ilooking at the problem from the wrong angle?



I solved my particular problem by writing a custom email parser. The code is "OK" - problems in a good parser are inherrant by (1) no access to the XSLT salesforce sues fro parsing email XML, and (2) lack of any "real" support for RegEx (yes, it exists but it's currently not great).


I use some code earlier to extratc the temaplate XML from salesfroce that the user edits and creates then I send it thorugh a custom parser to merge in each object. Ex. I can send in a Contact, a Lead and the Organisation information.


This gives me plenty of flexibility away from the rules enforced by salesforce limiting the use of template field mergers.



This is the merge method


public String mergeFields(String sourceText, SObject obj, String objectType)
		//This function helps get around email template restrictions
		To use: Select your email template text from the database and the objects you want to merge with it
		Send this method the text, object and tell it what the object is called (saves on APEX calles to the database to find out automatically)   
        The method will search the text looking for {!ObjectTypeName.SomeField} and replace it with an attempt to get the value from the passed object
        Why do this?
        It means you can have a template in the SF admin interface that can be edited and have a micture of dynamic information merged int it, instead of just one object
        So instead of just setting the target of an email as the contact, and using that to populate the fields, you can set the tareget as contact but a lead to the email body, and organisation as the signature even if the objects have no relation to one another.
        Why not make it "more" dynamic
        1) APEX describe restrictions which I used in my first version meant you could only execute this method a maximum of 10 times in one script
        2) Currently REGEX support in APEX is extremely poor (no way to cycle thorugh each match for example) so I'm doing a very basic indexOf match that is case sensitive
        3) Ideally Salesforce would make the internal transform method it uses for emails available to APEX, so we could transform the XML of an email several times against each object before using XSLT to generate the html. As you can see the html output of there is limited to no attachements and only basic html (no images).
        4) Again because of the hidden nature of the XSLT transform there's no (easy) way to combine the email template with the brand template, though one could extend this method by making it a class and adding the template ID.
        5) I'm now out of time to develop this further, but it is robust enough to work on most objects and string compatible fields.  
        System.debug('------------------------- Merge Attempt ----------------------'); 
       	System.debug('------------> Merge string is: '+sourceText); 
       	Integer a = sourceText.indexOf('{!'+objectType+'.');
       	System.debug('------------> First Index {!'+objectType+'. is: '+a); 
        Integer b = (a>-1 && a<sourceText.length() ? a + sourceText.substring(a).indexOf('}') : a);
       	System.debug('------------> First Index } is: '+b);    
        String token = (b>a && a>-1 ? sourceText.substring(a,b+1) : '');
        Integer c = token.indexOf('.');
        String field = (c>-1 ? token.substring(c+1).replace('}','') : '');
       	System.debug('------------> First token is: '+token); 
       	System.debug('------------> First field is: '+field); 
        while(token!='' && token!=null && field!='' && field!=null && a>-1 && b>-1 && b>a)
	        System.debug('------------> Merge Attempt:'+token+' on field:'+field); 
	        	if (obj.get(field)!=null)
					sourceText = sourceText.replace(token, (String)obj.get(field));
	        		System.debug('------------> Merge:'+token+' on field:'+field +' : with Value:' + obj.get(field));                   	
	            } else {
					sourceText = sourceText.replace(token, '');
	        		System.debug('------------> Merge:'+token+' : with Value:empty');
	        catch (Exception e)
	        	sourceText = sourceText.replace(token, '');	            	       	
	        	System.debug('------------> Merge failed: '+token+', replaced with empty string'); 
	       	a = sourceText.indexOf('{!'+objectType+'.');
	        b = (a>-1 && a<sourceText.length() ? a + sourceText.substring(a).indexOf('}') : a);
	        token = (b>a && a>-1 ? sourceText.substring(a,b+1) : '');
	        c = token.indexOf('.');
	        field = (c>-1 ? token.substring(c+1).replace('}','') : '');
        //Templates use CDATA to contain text as they expect an XSLT transform.
        //If APEX actually made this transform available then we could use that instead of this method
        //As it is, we are using this technique, so we have to strip the CDATA tags or our content is hidden.
        sourceText = sourceText.replace('<![CDATA[','');
        sourceText = sourceText.replace(']]>','');
        //correct poor markup provided by salesforce
        sourceText = sourceText.replace('<br>','<br/>');
        sourceText = sourceText.replace('<hr>','<hr/>');
		return sourceText;

 Which is called (this code is loosly copied and pasted) from:


//This first bit is just the getting the template stuff

//Lookup Email Template to use
EmailTemplate et = [Select e.TemplateType, e.TemplateStyle, e.Subject, e.Name, e.Markup,  e.IsActive, e.Id, e.HtmlValue, e.Encoding, e.DeveloperName, e.Description, e.BrandTemplateId, e.Body From EmailTemplate e where e.DeveloperName=:emailTemplate limit 1];

if (et!=null)
									//Add a static surround to the dynamic email body as I couldn't be bothered to work out all the brand email template stuff, expand on this if you wish										htmlBody = '<html><style>p{margin-top:0px; margin-bottom:0px;}</style><body  style=" background-color:#FFFFFF; bLabel:body; bEditID:b1st1;"><center ><table id="topTable" height="450" width="500" cellpadding="0" cellspacing="0" ><tr valign="top" ><td  style=" vertical-align:top; height:65; text-align:left; background-color:#FFFFFF; bLabel:header; bEditID:r1st1;"><img id="r1sp1" bLabel="headerImage" border="0" bEditID="r1sp1" src="" ></img></td></tr><tr valign="top" ><td  style=" height:1; background-color:#efefef; bLabel:accent1; bEditID:r2st1;"></td></tr><tr valign="top" ><td styleInsert="1" height="300"  style=" color:#000000; font-size:12pt; background-color:#FFFFFF; font-family:arial; bLabel:main; bEditID:r3st1;">';
										htmlBody += et.HTMLValue;
										htmlBody += '</td></tr><tr valign="top" ><td  style=" height:1; background-color:#efefef; bLabel:accent2; bEditID:r4st1;"></td></tr><tr valign="top" ><td  style=" vertical-align:top; height:100; text-align:left; background-color:#FFFFFF; bLabel:footer; bEditID:r5st1;"></td></tr><tr valign="top" ><td  style=" height:1; background-color:#ffffff; bLabel:accent3; bEditID:r6st1;"></td></tr></table></center><br><br><img src=""></body></html>';
										textBody = et.Body;
										subject = et.Subject;

//Now we call the merger
//Now if RegEx was powerful enough, or an XSLT parser was writen I could dynamically parse the XML from above and look up each object. One advantage to the below is I'm making as few SOQL calls (earlier on in my script) to get each object I want to merge.
										//Lead object
										htmlBody = mergeFields(htmlBody, l, 'Lead');	
										textBody = mergeFields(textBody, l, 'Lead');	
										subject = mergeFields(subject, l, 'Lead');
										//Contact object
										htmlBody = mergeFields(htmlBody, c, 'Contact');		
										textBody = mergeFields(textBody, c, 'Contact');		
										subject = mergeFields(subject, c, 'Contact');			                

//Now set this into an email object and you're done		                
										//Organisation object
										htmlBody = mergeFields(htmlBody, o, 'Organization');	
										textBody = mergeFields(textBody, o, 'Organization');	
										subject = mergeFields(subject, o, 'Organization');							




As of this writing, I have successfully written an apex trigger and using a custom object argument to setWhatId()
I don't know if this changed in the last 2 years or so, but the API documentation is clearly inaccurate on this point.