Quick fix for Web to Case Assignment Rules

While preparing a Salesforce Service Cloud demo for a client recently, a colleague of mine who is an admin lamented that Case assignment rules were not firing for a Web to Case deployment he had put together. I thought this was odd, Salesforce documentation clearly states:

Using an Assignment Rule for Web-to-Case, Email-to-Case, or On-Demand Email-to-Case:

In Professional, Enterprise, Unlimited, Performance, and Developer Edition organizations, web- and email-generated cases are automatically assigned to users or queues based on criteria in your active case assignment rule.

Hmmm. After some additional research he was able to find this known issue:

https://trailblazer.salesforce.com/issues_view?id=a1p3A000000JWhVQAW

Although this is worded very specifically around a particular use-case, we were able to find other references to complaints of the same issue impacting Web to Case in general. We also found some threads which discussed various work-arounds such as creating Process Builder or Flow to essentially re-create the assignment rules logic for these Cases – but that seemed like WAY too much work!

Fortunately I was able to throw together some quick-and-dirty Apex to solve the problem. Specifically, our solution was to asynchronously update the Case after it was created with specific DML Options set to fire default assignment rules:

trigger CaseTrigger on Case (after insert) {
    CaseFireAssignment.fireAssignment((List<Case>)trigger.new);
}
public class CaseFireAssignment {
    public static void fireAssignment(List<Case> cases) {
        fireAssignment(JSON.serialize(cases));
    }
    
    @future
    public static void fireAssignment(String caseJson) {
        List<Case> cases = (List<Case>)JSON.deserialize(caseJson, List<Case>.class);
        List<Case> casesToTouch = new List<Case>();
        for (Case c: cases) {
            if (c.Origin == 'Web') {
                casesToTouch.add(c);
            }
        }
        if (casesToTouch.size() > 0) {
            Database.DMLOptions dmo = new Database.DMLOptions();
            dmo.assignmentRuleHeader.useDefaultRule= true;      
            Database.update(casesToTouch, dmo);
        }
    }
}

Ok, so what’s going on here?

  • First, we have a simple trigger to respond to Cases being created. In keeping with the best practice of NOT having logic in the trigger itself, we make a call to an Apex class. In a real-world implementation in which we would likely have more logic hanging off triggers than just this, we would probably be using a more robust trigger-handler framework and would simply plug this code in there.
  • the CaseFireAssignment class actually has two methods called fireAssignment(). This is an example of “overloading” – something we do very frequently in object-oriented programming to give us flexibility in how callers can use our code. In this case one of the methods is a convenience so that callers don’t have to worry about a quirk in how we call a @future method.
  • To call the @future method we do need to serialize (convert) the List of Cases to a JSON string because parameters of @future methods “must be primitive data types, arrays of primitive data types, or collections of primitive data types. Methods with the future annotation cannot take sObjects or objects as arguments.” Note that we would not be so limited if we used a Queueable instead, but that would make this example more complex!
  • Even though we’ve called the @future method synchronously from within the Case after insert trigger at this point, it will not execute right away. After some short period of time (determined by Salesforce) the method will be executed. At that point we re-constitute our List of Cases by deserializing the JSON, loop through them to make sure we only process Web to Case (reminder that the trigger fires for ANY Case creation), and then finally do the real work.
  • The whole point of all of this shenanigans now happens:
Database.DMLOptions dmo = new Database.DMLOptions();
dmo.assignmentRuleHeader.useDefaultRule= true;      
Database.update(casesToTouch, dmo);

The Database.DMLOptions.AssignmentRuleHeader class has two fields on it – one which lets you specific the ID of a specific Assignment Rule to run, and one which specifies to use the default rule. The latter was perfect for our use-case.

It is worth noting that Salesforce does now say that the fix for this problem is scheduled for Winter ’21. In the meantime though, this will work!