Hello! Welcome to another interesting ‘world-class’ problem – resending approval emails that accept approvers’ replies. (I was, again, surprised that Salesforce doesn’t support this functionality natively.)

Start with the problem:

  • Approvers sometimes forget to reply to approval emails nor be bothered to login into salesforce to approve the record.
  • Once an item is submitted for approval, there is no way to remind the approver unless the raiser recalls the submission and resubmits.

This is causing a lot of headaches because manual emails and human interactions will be involved, and mistakes happen when there are back and forth communications.

Here is the real challenge we have, the executive team never want to have another login, we therefore need to make their life as easy as possible, and simply set up a workflow email alert won’t help much because it wouldn’t “read” the reply from the approvers.

In fact we just want to replicate the “Submit for Approval” button but only send the reminder email to the latest pending approver.

Solution design, to achieve this, we need:

  • an Email Template similar to the initial submission email that contains the record ID (which is later used to find the record)
  • an Apex class that implements Messaging.InboundEmailHandler to read the inbound email content
  • a Salesforce Email service to send and receive emails
  • a custom button on record detail page to send the email from the email service we set up (using URL Hack I wrote before)

Let’s get started!

Both email template and the button design is easy, I will just skip them, just don’t forget to include the detail link to the related record e.g. [instance].salesforce.com/[your-record-id], we are going to use this URL to allocate the record in salesforce.

Now in step two, we need to write some codes.

The Apex class will have to implement Messaging.InboundEmailHandler which contains a global method called handleInboundEmail that takes two inputs and returns Messaging.InboundEmailResult.

global class ObjectInboundEmailHandler implements Messaging.InboundEmailHandler {
    global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
    }
}

We are going to mainly focus on Messaging.InboundEmail email and try to dig some information from it.

Before that, let me introduce regex for those who aren’t familiar with it, here is the link to Wikipedia (please donate some spare changes to support them 🙂 ), basically we are going to use regex to extract the record ID.

Let’s assume your object ID always starts with “abc”, then you can use the following code to find record id inside the email body (assuming you did not forget to include the full URL of the record in the email body as mentioned above, which makes sense).

Pattern p = Pattern.compile('abc.{12}');
Matcher pm = p.matcher(email.plainTextBody.trim());
String ObjectId = null;
if (pm.find()){ // Object id found
    System.debug(pm.group(0));
    ObjectId = pm.group(0);
}
System.debug('ObjectId ' + ObjectId);

It’s not difficult to read the response from the first line and second line of the email (we do this because we want to replicate the standard button, however, feel free to add more logics on top of that).

Once we have the record ID and the response, we can then apply it to the following SOQL query to find the latest approval step for the record and action it on behalf of the email sender (the approver) based on user response. I’ve also added a simple validation to make sure the email sender is a valid salesforce user.

String res = email.plainTextBody.trim().split('\n')[0];
if (res != null){
    String action = null;
    List<ProcessInstance> pis = [
        SELECT Id, (SELECT Id, ActorId, ProcessInstanceId FROM Workitems)
        FROM ProcessInstance
        where TargetObjectId =: ObjectId
    ];
    // check sender email to see if sender is a salesforce user
    System.debug('email.fromAddress ' + email.fromAddress);
    List<User> users = [select id from user where email =: email.fromAddress];
    User user = users != null && !users.isEmpty() ? users[0] : null;
    System.debug('user ' + user);
    if (user == null) {
        /* your own error handling */
        return null;
    }
    // attach the email to Object
    insert new Task(
        ownerid = user.id,
        Description =  email.plainTextBody,
        Priority = 'Normal',
        Status = 'Complete',
        subject = email.subject,

        whatId = ObjectId
    );

    // now standardise the action
    if (
        res.trim().toLowerCase() == 'yes' ||
        res.trim().toLowerCase() == 'approve' ||
        res.trim().toLowerCase() == 'approved'
    ) {
        action = 'Approve';
    }
    else if (
        res.trim().toLowerCase() == 'no' ||
        res.trim().toLowerCase() == 'reject' ||
        res.trim().toLowerCase() == 'rejected'
    ) {
        action = 'Reject';
    }
    if (action != null){
        for (ProcessInstance pi : pis){
            if (pi.Workitems != null && !pi.Workitems.isEmpty()){
                ID tmp = pi.Workitems[0].id;
                Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
                req.setComments(comment);
                req.setAction(action);
                req.setWorkitemId(tmp);
                Approval.ProcessResult ProcessResult = Approval.process(req);
            }
        }
    }
    else{
        /* your own error handling */
        return null;
    }
}

(Note that the actual approver is always going to be you — the inbound email handler runner, that’s why I saved an email object as a reference, you may also want to append the actual approver’s name in the comment section.)

Now step three, the email service. It’s actually very simple:

  1. Go to setup page (our favourite as a Salesforce Admin/Dev)
  2. In the search box type in “email services” and click on it
  3. Click on “New Email Service”
  4. Give it a name and put “ObjectInboundEmailHandler” (or your custom class name) under Apex Class
  5. Put your email under Route Error Emails to This Email Address (optional)

You will also need to verify this newly created email address as an Organization-Wide Email Address in order to send reminder email from it.

And …

There you have it!

The final product is a detail page button that sends a reminder email from a custom email service that has the ability to read approvers’ responses and approve/reject the current step for you, automatically.

screen-shot-2016-12-18-at-12-19-01-pm