+ Start a Discussion
Daniel Huckins 27Daniel Huckins 27 

WhatId is not available for sending emails to UserIds contradiction

Hello all, 
long time reader, first time poster.
I have a trigger that runs before update, gets some related users and sends them an email and then updates a field for a 'Sent
 email.
Good - When the records are manually changed (testing functionality), everything works great.
Bad - Test class produces the following error:
System.DmlException: Update failed. First exception on row 0 with id a0Ee0000006KoJZEA0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, EventSessionTrigger: execution of BeforeUpdate
caused by: System.EmailException: SendEmail failed. First exception on row 0; first error: INVALID_ID_FIELD, WhatId is not available for sending emails to UserIds.: []


There is documentation here (https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_email_outbound_single.htm#apex_Messaging_SingleEmailMessage_setWhatId) that says why the test error is coming up, BUT the question is why does it work in practice and not in the test?
The overall goal, would be to have it work in both. 
 
//Test class
@isTest
private class EventSessions_Test
{
  @testSetup static void testSetup()
  {
    Profile p =
      [SELECT Id FROM Profile
       WHERE Name = 'Customer Service Managers'];
    User u1 = new User(
      Alias = 'standt', Email = 'dhuckins@argyleforum.com',
      EmailEncodingKey = 'UTF-8', LastName = 'Testing',
      LanguageLocaleKey = 'en_US',
      LocaleSidKey = 'en_US', ProfileId = p.Id,
      TimeZoneSidKey = 'America/Los_Angeles',
      UserName = 'argyletestuser1@bar.com'
    );
    insert u1;
    EmailTemplate template = new EmailTemplate(
      Name = 'Test template',
      Subject = 'test subject',
      DeveloperName = 'Test_template_for_attendance_alerts',
      TemplateType = 'text',
      FolderId = '00l40000001pLA5AAM',
      IsActive = true
    );
    insert template;
  }
  @isTest static void test_method_one()
  {
    EmailTemplate template =
      [SELECT Id
       FROM EmailTemplate
       WHERE DeveloperName = 'Test_template_for_attendance_alerts'
                             LIMIT 1];
    User u1 =
      [SELECT Id
       FROM User
       WHERE Email = 'dhuckins@argyleforum.com'
                     LIMIT 1];
    Account account = new Account(
      Name = 'Test Account',
      Primary_Membership__c = 'CIO'
    );
    insert account;
    Contact contact = new Contact(
      FirstName = 'Test',
      LastName = 'Contact',
      AccountId = account.Id,
      Email = 'email@email.com'
    );
    insert contact;
    Events__c event = new Events__c(
      Name = 'Test Event ',
      Related_Membership__c = 'CIO',
      Event_Date__c = Date.today(),
      Programming__c = u1.id,
      Client_Name__c = account.Id,
      Is_there_a_dinner_with_this_meeting__c = 'No',
      Meeting_Type__c = 'Full Day'
    );
    insert event;
    Event_Session__c session = new Event_Session__c(
      Name = 'Test SEssion',
      Related_Event__c = event.Id,
      Email_Alert_Padding__c = 10,
      Email_Alert_Status__c = 'Scheduled',
      Start_Time__c = Datetime.now(),
      Email_Alert_Template__c = template.Id
    );
    insert session;
    Client_Project_Management__c module = new Client_Project_Management__c(
      Name = 'Test module',
      Account__c = account.Id
    );
    insert module;
    Campaign_Attribute__c attribute = new Campaign_Attribute__c(
      Project__c = event.Id,
      Staff_responsible_for_client_at_event__c = u1.Id, // send an email to this one
      Client_Project_Name__c = module.Id
    );
    insert attribute;
    Argyle_Alerts_Entry__c entry = new Argyle_Alerts_Entry__c(
      Account__c = account.Id,
      Interested_Account__c = account.Id,
      Contact__c = contact.Id,
      Related_Project__c = event.Id
    );
    insert entry;
    Attendee_Project_Junction__c attendee = new Attendee_Project_Junction__c(
      Attendance_Status__c = 'Attended',
      Attendee_Name__c = contact.Id,
      Attendee_Type__c = 'Other/Neutral',
      Project__c = event.Id
    );
    insert attendee;
    session.Email_Alert_Status__c = 'Sending';
    Test.startTest();
    update session; // line of error
    Test.stopTest();
    Event_Session__c check =
      [SELECT Id, Email_Alert_Status__c
       FROM Event_Session__c
       WHERE Id = :session.Id];
    System.assertEquals('Sent', check.Email_Alert_Status__c);
  }
}
 
//trigger handler
public class EventSessions extends fflib_SObjectDomain
{
  public EventSessions(List<Event_Session__c> records)
  {
    super(records);
  }

  public override void onBeforeUpdate(Map<Id, sObject> existingRecords)
  {
    EventSessionService.emailAlertsRouter(
      (List<Event_Session__c>)records,
      (Map<Id, Event_Session__c>)existingRecords
    );
  }

  public class Constructor implements fflib_SObjectDomain.IConstructable
  {
    public fflib_SObjectDomain construct(List<SObject> sObjectList)
    {
      return new EventSessions(sObjectList);
    }
  }
}
 
//service class
public class EventSessionService
{
  public static void emailAlertsRouter(
    List<Event_Session__c> sessions,
    Map<Id, Event_Session__c> oldSessions)
  {
    List<Event_Session__c> emailTriggers = new List<Event_Session__c>();
    for (Event_Session__c es : sessions)
    {
      if (es.Email_Alert_Status__c == 'Sending' &&
          oldSessions.get(es.Id).Email_Alert_Status__c == 'Scheduled')
      {
        emailTriggers.add(es);
      }
    }
    prepareAttendanceAlerts(emailTriggers);
  }

  public static fflib_SObjectUnitOfWork newInstance()
  {
    return new fflib_SObjectUnitOfWork(
             new Schema.SObjectType[]
             {
               Event_Session__c.SObjectType
             });
  }

  public static void prepareAttendanceAlerts(List<Event_Session__c> emailTriggers)
  {
    //we need a map of the event session and
    //the ids of users responsible for client
    //at that event
    fflib_SObjectUnitOfWork uow = newInstance();
    Set<Id> eventIds = new Set<Id>();
    for (Event_Session__c session : emailTriggers)
    {
      eventIds.add(session.Related_Event__c);
    }
    Set<Id> userIds = new Set<Id>();
    //Messaging.SingleEmailMessage[] emails = new Messaging.SingleEmailMessage[] {};
    for (Campaign_Attribute__c ca :
         [SELECT Id, Client_Project_Name__r.Account__c,
          Staff_responsible_for_client_at_event__c
          FROM Campaign_Attribute__c
          WHERE Project__c IN :eventIds])
    {
      userIds.add(ca.Staff_responsible_for_client_at_event__c);
    }
    for (Event_Session__c es : emailTriggers)
    {
      for (Id target : userIds)
      {
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        email.setTemplateId(es.Email_Alert_Template__c);
        email.setSaveAsActivity(false);
        email.setTargetObjectId(target);
        email.setWhatId(es.Id);
        uow.registerEmail(email);
      }
      es.Email_Alert_Status__c = 'Sent';
      //uow.registerDirty(es);  //moved to onBeforeUpdate
    }
    uow.commitWork();
  }
}
 
Class	EventSessions_Test
Method Name	test_method_one
Pass/Fail	Fail
Error Message	System.DmlException: Update failed. First exception on row 0 with id a0Ee0000006KoJoEAK; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, EventSessionTrigger: execution of BeforeUpdate
caused by: System.EmailException: SendEmail failed. First exception on row 0; first error: INVALID_ID_FIELD, WhatId is not available for sending emails to UserIds.: []

Class.fflib_SObjectUnitOfWork.SendEmailWork.doWork: line 308, column 1
Class.fflib_SObjectUnitOfWork.commitWork: line 244, column 1
Class.EventSessionService.prepareAttendanceAlerts: line 78, column 1
Class.EventSessionService.emailAlertsRouter: line 31, column 1
Class.EventSessions.onBeforeUpdate: line 25, column 1
Class.fflib_SObjectDomain.handleBeforeUpdate: line 155, column 1
Class.fflib_SObjectDomain.triggerHandler: line 311, column 1
Class.fflib_SObjectDomain.triggerHandler: line 271, column 1
Trigger.EventSessionTrigger: line 18, column 1: []
Stack Trace	Class.EventSessions_Test.test_method_one: line 98, column 1



 
AshwaniAshwani
You can not set email.setTargetObjectId(target); to a record of User when setting a whatId. WhatId can only be set if setTargetObjectId is a contact.
Daniel Huckins 27Daniel Huckins 27
Hi Ashwani,

I agree that should be the case but that does not explain
a) why isn't sf throwing a compilation error
b) why does the code actually work!
Roger WickiRoger Wicki
That doesn't make any sense to me. I want to send out an email to the owners of assets. So whatId is logically the Asset ID and targetObjectId is logically the User ID. But I can't take a user because it states in the method description that i can take a user? Contradiction much... My Email Template uses User and Asset fields. What am I supposed to do?

Also, b) from Daniel Huckins 27 is a valid point...
Roger WickiRoger Wicki
I just found this and think it's the solution to everybody's problems
https://salesforce.stackexchange.com/a/144498/10330