3

I have a scenario where I need to inform the record owners via an email once a validation error has occured. The validation occurs in the trigger.

Here is what the code looks like -

trigger ValidateData on Account(before insert, before update) {
    Map<Id,String> mapAcctIdToErrStr = new Map<Id,String>();
    for(Account a : Trigger.New) {
       //example validation rule below
       //I need to know which records are failing so that I can send an email
       //I capture this information in the map before 'addError'
       //then I process this map
       if(a.Industry == null) {
           mapAcctIdToErrStr.put(a.Id, 'The Industry field is mandatory.');
           a.addError('The Industry field is mandatory.');
       }
    }
    //now process the error map and send emails
    //some logic to process the map
    //finally a 'sendEmail' call to send out the email alerts
    //although there is no exception and the 'sendEmail' is invoked
    //no mail is being sent out from Salesforce
    //on inspecting the Email Logs, we can see no email has been generated
    Messaging.sendEmail(mail);
}

My analysis is that the mail is not being sent out since the transaction is being rolled back because of the addError.

Also, I have tried to send the email from the future method. But even the future method does not get invoked in this scenario.

Please share your thoughts if you have come across such a scenario or suggest some ideas so that I can try them out.

  • Regardless of the technical aspect of this, this sounds like a very bad idea, after a while people will just ignore these notifications since they will be considered spam... – PepeFloyd Jul 7 '15 at 13:31
  • I understand this looks to be a bad idea on the front, but this is of real importance to the users. The validation is the rarest of rarest cases and the emails would not be that frequent. – Chetan Gupta Jul 7 '15 at 14:34
  • Got a confirmation from Salesforce that this is a known issue. success.salesforce.com/ideaView?id=08730000000l0UcAAI – Chetan Gupta Jul 8 '15 at 15:23
1

Please just use try{}catch{} block to handle Exception. Then send email from inside catch or finally - it should work

  • Can you please elaborate more on the statements that I should surround with the try-catch block? There are no exceptions being thrown in the code. – Chetan Gupta Jul 7 '15 at 14:41
  • See Trigger Exceptions from the Apex Code Developer's Guide. In a trigger context, the adderror method is considered an exception that causes a DML rollback. – crmprogdev Jul 7 '15 at 16:11
1

I think the solution here needs a re-think.

For example why does the record owner want an "Error email" when a validation rule fails on a record? Is it because they want to be notified when an error has occurred? Or is it because they want control over the change of the record they own?

In the case of the record owner wanting control over the record - I would suggest that you recommend to your customer using an approval process. The owner does not have to worry about business data validation rules - but may be interested in changes to the record they own and want to approve it. https://help.salesforce.com/HTViewHelpDoc?id=what_are_approvals.htm

If it's because a notification of errors are required - then this really should be a managed exception. Put your block of code in a try/catch/finally block, log when an error occurs, and if required send an email to notify someone that the error has happened. I would be careful about sending emails on validation errors - this may happen alot - and there are valid business reasons to have validation errors displayed to users (to enforce certain data cleanliness, or required data for policy purposes). Do you really want to notify people of when this happens? What is the business value of it? https://developer.salesforce.com/page/An_Introduction_to_Exception_Handling


The only other thing I can think of is you require data on how many times people are hitting validation errors. This may imply the following

  • the rules are too complex and users are not aware of them
  • Training of users on the system may not be suffiecient
  • The validation errors are not clear enough to the users on the UI

In that case you may want to capture every time a validation rule happens (maybe in a custom object that collects behaviours across your implementation - and would capture basic things like the record ID, type of error, ID of the error meta-data, the user that executed it, and a timestamp). It will allow you to build some behaviour reports.

However, this is quite an expensive solution for what is really simple validation rule errors.

Hope that helps.

0

There are what I refer to as Data Errors, meaning data that doesn't conform to the requirements of the application that will cause an exception to occur if not caught. When caught, these data errors generally need human intervention. It's not a cause of faulty programming, it's a case of GIGO (Garbage In - Garbage Out). This is most often seen when data is entered via the data loader as opposed through the API input screens where normal validation rules would catch them and the User would then be able to make corrections.

When someone is uploading a 1000 or 100000 records, as programmers, we don't want to have our application cause their upload to fail because of an exception. Instead, we want to capture it and bring it to someone's attention to go and manually fix. At least I suspect that's where you're coming from.

What I've done for clients in those situations is to create a central data error handling class to use in a try-catch block. When it catches an exception or known error, it adds it to a collection and at the end of the trigger, the entire collection is sent to the error handler class where all of the errors are then forwarded via email to an admin for follow-up.

Here's the basic format that I came up with for creating the collection of errors that's sent to the Handler class:

MapName.put(sObject.Id,'String containing TriggerName, Line Number, system.now(), error message with any variable values than can be included');

In your situation, with this being a BeforeInsert, BeforeUpdate trigger, an Id isn't going to be available in the BeforeInsert context. If there's an external Id or other field you can use to relate it to, you'd want to use that for the BeforeInsert context.

Here's an actual taken from one of my triggers:

ProcErrs.put(opp.Id,'Opps2EventsTigger AfterUpdate Error on line 785 at '+ string.valueOf(system.now()) +' Possible Data Error: confirm Event Time of '+ strtTm +' for Event ID '+ ev.Id +' with Manager of Opportunity ID '+ opp.Id );

Below is a simplified version to illustrate how the error handler class processed errors and sent emails to an admin for follow-up:

public class Automation_Error_Handler {

    public map<Id, String> errmap {get;}
    public map<Id, string> emlmp;   
    public map<Id, string>mssgmap;
    public string trigname;


    public void Automation_Error_Handler(map<Id,String>errmap)
    {
        map<Id, string>emlmp = (map<Id,string>)errmap;
        set<Id>Idset = new set<Id>();
        Idset = errmap.keyset();
        String trigname;
        String es;

        for(Id ids: Idset){
            system.debug('ID = '+ ids + ' ErrMssg = '+ errmap.get(ids) );
        }

        for(Id ObjId: Idset){

            es = errmap.get(ObjId);
            system.debug('es = '+ errmap.get(ObjId));

            if(es.startsWith('Opps2EventsTrigger') == true) trigname = 'Opps2EventsTrigger';

        } /* end for(Id i: Idset) */

        if(emlmp.isEmpty() == false){

            Automation_Error_Handler .send_Error_Emails(trigname, emlmp);

        } /* end if(emlmp.isEmpty() == false) */

    } /* end method */  

    private static void send_Error_Emails (String trigname, map<Id,String>mssgmap) {
        PrimAdmin_CustSttng__c Admin = PrimAdmin_CustSttng__c.getValues('PrimAdmin');

        string subject;
        set<Id>Idset = new set<Id>();
        Idset = mssgmap.keyset();
        if(trigname == 'Opps2EventsTrigger'){
            subject = 'Automation Errors Handled by trigger '+ trigname +' on Opp Id: ';
        }
        // was a series of elseif statments here
        string body2;
        string body = ' ';
        string subject2;
        for(Id Iss: Idset){

            subject2 = subject + Iss + ', '; // Id

            body2 =  body + mssgmap.get(Iss) +'. \n' ;

            subject = subject2;
            body = body2;
        }
        system.debug('subject = '+ subject );
        system.debug('body = '+ body );

        Messaging.SingleEmailMessage outbound = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {string.valueOf(Admin.Email__c) };
        outbound.setSenderDisplayName('Apex Automation Error Handler');
        outbound.setToAddresses(toAddresses);
        outbound.setSubject(subject);
        outbound.setUseSignature(false);
        outbound.setPlainTextBody(body);
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] {outbound });

    } /* end method */


} /* end class */ 

Note that the Admin's email address was held in a custom setting for convenience. A list could have been used for sending this to a list of admins with cc's to other users. The possibilities are endless depending on how complex you want to make it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.