Friday 15 July 2011

How to handle exceptions when using the Messaging.sendEmail Apex method

Apex, the native Salesforce OO language, provides methods to create and send emails. You can define your own body, or create standard templates using Visualforce, the Salesforce page markup language. Messages can be sent using the Messaging.sendEmail method. If any problems occur while attempting to send these emails, exceptions are thrown by the method.

Let's have a look at the method definition, taken directly from the Salesforce documentation.

Messaging.sendEmail(new Messaging.Email[] { mail } , opt_allOrNone);

The documentation explains that the all_or_none argument allows you to choose to send successful emails even if some fail. This sounds pretty straightforward, but what the documentation doesn't explain is that the value of this argument actually affects how errors are reported back to the calling program.

If the argument value is "true" or simply not populated, then all exceptions are thrown, and can be caught, reported and dealt with using a traditional try/catch structure.

    try{
      Messaging.sendEmailResult[] sendEmailResults = 
        Messaging.sendEmail(new Messaging.Email[] { mail });
    } catch(System.EmailException ex){
      // Exceptions are passed to here.
    }

However, if the value is set to "false" none of the exceptions are thrown. These are instead stored inside the Messaging.SendEmailResult collection that is passed back from the method call.

    
   Messaging.sendEmailResult[] sendEmailResults = 
      Messaging.sendEmail(new Messaging.Email[] { mail }, false);

   for(Messaging.SendEmailResult sendEmailResult: 
        sendEmailResults){
            
     if(!sendEmailResult.isSuccess()){
        // deal with failure here.
     }
   }
 

The following page and controller combo includes actions that allow you to directly see how calling the Messaging.sendEmail method with different argument values affects error handling. A sample combination of successful and failing emails are generated and passed to the method for testing.

Page:
<apex:page controller="EmailErrorHandlingExampleController">

    <apex:sectionHeader title="Email Error Handling Example"/>
    
    <apex:form >
        <apex:commandButton 
          action="{!sendSampleEmailsAllOrNothing}" 
          value="Send emails all or nothing"/>
        <apex:commandButton 
          action="{!handleIndividualSendEmailErrors}" 
          value="Send emails individually"/>
    </apex:form>    
    
    <apex:pageMessages />
    
</apex:page>

Controller:
public class EmailErrorHandlingExampleController {

  // Send some emails all or nothing, if one fails, all fail.
  public PageReference sendSampleEmailsAllOrNothing(){
    
    List<Messaging.SendEmailResult> sendEmailResults = 
      new List<Messaging.SendEmailResult>{};
    
    Integer numberOfSuccessfulMessages = 0;
    
    try{
      // note the all_or_none option has not been set 
      //(default value is true)
      // an email exception should be thrown, 
      // and the results list should not be populated. 
      sendEmailResults = 
        
        Messaging.sendEmail(getSampleEmailMessages());
     
    } catch(System.EmailException ex){
      // This message should be added to the page, 
      // as an exception is caught
      Apexpages.addMessage(new ApexPages.Message(
        ApexPages.Severity.ERROR, 
        'Caught email exception: ' + ex));
    }
        
    // This section of code should be skipped, 
    // as the result set is empty.
    for(Messaging.SendEmailResult sendEmailResult: 
        sendEmailResults){
            
      if(sendEmailResult.isSuccess()){
        numberOfSuccessfulMessages++;
      }
      else {
        for (Messaging.Sendemailerror sendEmailError : 
             sendEmailResult.getErrors()){
          
          Apexpages.addMessage(new ApexPages.Message (
            ApexPages.Severity.ERROR, 
            'Send Email Result Error: ' + 
            sendEmailError.Message));
        }
      }
    }
        
    // This page messsage should confirm 
    // no messages have been sent.
    Apexpages.addMessage(new ApexPages.Message (
      ApexPages.Severity.INFO, 
      'You should have just received ' 
      + numberOfSuccessfulMessages + ' emails'));
    
    return null;
  }
    
  // Send some emails on an individual basis. 
  // If one fails, only that one fails.
  public PageReference handleIndividualSendEmailErrors(){
        
    List<Messaging.SendEmailResult> sendEmailResults = 
      new List<Messaging.SendEmailResult>{};
    
    Integer numberOfSuccessfulMessages = 0;
    
    try{
      // note the all_or_none option has been set to false
      // an email exception should not be thrown, 
      // but rather stored in the result.
      sendEmailResults = 
        Messaging.sendEmail(getSampleEmailMessages(),false);
    
    } catch(System.EmailException ex){
      // This section should never be accessed, 
      // as an exception is never thrown.
      Apexpages.addMessage(new ApexPages.Message (
        ApexPages.Severity.ERROR, 
        'Caught email exception: ' + ex));
    }
        
    // This section of code should be run through four times, 
    // one for each populated result.
    for(Messaging.SendEmailResult sendEmailResult: 
        sendEmailResults){
            
      if(sendEmailResult.isSuccess()){
        numberOfSuccessfulMessages++;
      }
      else {
        for (Messaging.Sendemailerror sendEmailError : 
             sendEmailResult.getErrors()){
              
          Apexpages.addMessage(new ApexPages.Message (
            ApexPages.Severity.ERROR, 
            'Send Email Result Error: ' 
            + sendEmailError.Message));
        }
      }
    }
        
    // This message should indicate that 
    // two messages were successfully sent.
    Apexpages.addMessage(new ApexPages.Message (
      ApexPages.Severity.INFO, 
      'You should have just received ' + 
      numberOfSuccessfulMessages + ' emails'));
      
    return null;
  }

  // Get the current users emailAddress
  String currentUserEmailAddress {
        
    get{
      if (currentUserEmailAddress == null){
        
        // If the value has not been populated, retrieve it
        // using a SOQL query and the userinfo object.
        User currentUser = [SELECT email
                            FROM user
                            WHERE Id = :userInfo.getUserId()
                            LIMIT 1];
                                    
          currentUserEmailAddress = currentUser.email;
      }
        
      return currentUserEmailAddress;
    }
      
    set;
  }   

  // Generate a sample set of emails, 
  // some with errors, some without
  List<Messaging.SingleEmailMessage> getSampleEmailMessages(){
        
    // Simple success email message sent to the current user.    
    Messaging.SingleEmailMessage sampleSuccessEmail1 =
      new Messaging.SingleEmailMessage();
        
    sampleSuccessEmail1.setToAddresses(
      new List<String>{currentUserEmailAddress});
    
    sampleSuccessEmail1.setSubject('Just to let you know');
    
    sampleSuccessEmail1.setPlainTextBody(
      'This is a successful test email');
        
    // Simple fail message sent to the current user.    
    Messaging.SingleEmailMessage sampleFailEmail1 = 
      new Messaging.SingleEmailMessage();
        
    sampleFailEmail1.setToAddresses(
      new List<String>{currentUserEmailAddress});
    
    sampleFailEmail1.setSubject(
      'This is a failed email, it has no body!');
        
    // Simple fail message sent to no one.    
    Messaging.SingleEmailMessage sampleFailEmail2 =
      new Messaging.SingleEmailMessage();
        
    sampleFailEmail2.setToAddresses(new List<String>{});
    
    sampleFailEmail2.setSubject('This is a failed email');
   
    sampleFailEmail2.setPlainTextBody(
      'It is adressed to no one!');
        
    // Another Simple success email message 
    // sent to the current user.    
    Messaging.SingleEmailMessage sampleSuccessEmail2 = 
      new Messaging.SingleEmailMessage();
        
    sampleSuccessEmail2.setToAddresses(
    
      new List<String>{currentUserEmailAddress});
      
    sampleSuccessEmail2.setSubject(
      'Just to let you know (again)');
      
    sampleSuccessEmail2.setPlainTextBody(
      'This is another successful email');
        
    return new List<Messaging.SingleEmailMessage>{
      sampleSuccessEmail1,
      sampleFailEmail1,
      sampleFailEmail2,
      sampleSuccessEmail2};
  }
}

The following page messages should appear on the screen when you click the "Send emails all or nothing" command button:



Whereas these page messages should appear when the "Send emails individually" command button is clicked:



Being aware of the impact of the all_or_none argument on exception handling will help you to manage errors more effectively, and avoid writing redundant error handling code that will never be called.