Get Our Latest Thoughts & Opinions

Auto Follow High Amount Opportunities in Chatter

  • colm_barry

One of the ideas we had is to create a feature which will automatically allow users to follow opportunities if they’re higher than a certain threshold. This is how management will follow automatically the key deals in the company and it will make the sales managers use chatter once they know their managers are automatically following their big deals and getting updates on everything.

How is it working?

To make this feature more flexible and configurable we built it with three parts:

  • Custom Metadata Types. Used to store the settings.
  • Apex-classes. This causes a user to follow any record in chatter.
  • Process Builder. Used to invoke the apex class when an object goes over the criteria that are configured for the user. We can configure the process builder to make users follow records in any situation.

One of the things the apex code is responsible for is to make sure users follow record only if the user has at least read access to the record.

We’ve also made is configurable per user so every user has their own threshold. This means some users can auto follow only they very high opportunities and some users will follow most opportunities.

Let’s view closely each part of the feature.

Custom Metadata Types

This metadata type is used to store settings about the criteria when a user should become subscribed to a record.

This is what each field mean:

Criteria Field – The API name of the field on the User object. This is a value we’ll use to compare against a record field for each user. (For example, “LastName”).

Is User Field – this defines if the “Criteria Field” setting is related to a field on user or “Criteria Field” stores a concrete value (For example, if “Is User Field” is true, we can put in “Criteria Field” a value like “LastName”. If “Is User Field” is false, we can just put a number of a string in “Criteria Field”).

Operator – represents the logic operator used in the criteria. The list of all possible logic operators is shown on the image below.

Record Type – used to store the API name of the record type of the object we want to get users to follow. If this field is empty – all record types meet the criteria. If it is necessary to use multiple record types then you can separate them with a semicolon “;”.

SObject – stores the API name of the SObject (For example, “Opportunity”).

SObject Field – stores the API name of the field on SObject we’re comparing (For example, “Amount”).

To see an example of the whole completed setting:

This criteria would subscribe users to opportunities that their name start with the user last name.

Process Builder

We use process builder to start the main logic of our feature. We preferred to use a process builder and not a trigger because then a Salesforce admin can configure when this logic runs without changing even one line of apex code.  The main class which should be called from the process builder is AutoFollowRecordsOnChatterHandler (don’t forget to pass the ids of the processed records in the process).

For example, it’s possible to create a process that will run the logic of the feature when the opportunity amount changes.

“Amount is changed“ criteria would be this:

The action is a call of the apex class:

Note, that highlighted with a red line part of the action is very important.

Apex classes

Apex classes are generally used to define if a user has at least read access to the record and then create a subscription to the record if the user answers the following criteria of course.

The main class that is called from the process builder is AutoFollowRecordsOnChatterHandler

It has one method, annotated with the “@InvocableMethod” annotation. With this annotation, the method can be called from the process builder.

The class AutoFollowRecordsOnChatter has one public method called subscribeUsersToRecords with one parameter – list of ids of updated objects.

After that, based on the settings and updated records the method getWhoToWhatWantsToSubsribeBasedOnSetting returns a map with ids of the user as a key and set of ids of records as a value. This map represents “which users need to become subscribed to which records”.

<code class="rainbow" data-language="java">private Map<id, set=""> getWhoToWhatWantsToSubsribeBasedOnSetting(Auto_Follow_Up__mdt setting, List<user> users, List<sobject> selected_objects) {
    Map<id, set=""> result = new Map<id, set="">();
    for(sObject sobj: selected_objects) {
        SObjectType stype = sobj.getSobjectType();
        for(User usr:users) {
            if(isUserFieldForCriteriaisPopulated(setting, usr) && doesCriteriaIsMet(setting, usr, sobj, stype)) {
                Set<id> objects_ids = new Set<id>();
                if(result.containsKey(usr.Id)) {
                    objects_ids = result.get(usr.Id);
                } else {
                result.put(usr.Id, objects_ids);
    return result;
This method goes through the users and the records that have been updated. The method checks if the required criteria field on a user is populated, if it’s not – the user shouldn’t become subscribed to record if it is then the code checks if the user should become subscribed to record based on the condition.

A user can follow a record only if the user has at least read access to the record. It is possible for a user to have read permission to a record in different ways, for example when a record is shared with user (manual sharing, sharing rules, owner etc.), object has at least public read-only OWD, user’s profile has “View All” on the object, or “View All Data” permission.

The SharingAccessChecker class checks if the user has access to record via sharing model.

This method checks if OWD is at least public read-only.

If it’s a custom object – then check if the shared object is accessible in the organisation (if it’s not – then check if it’s at least public read-only OWD):

<code class="rainbow" data-language="java"><span class="entity function">if</span>(describe_result.<span class="entity function">isCustom</span>()) {
    <span class="keyword">return</span> <span class="entity function">isShareObjectOrgAccessible</span>(sobject_type);
If the object doesn’t have read OWD, then the share objects for each user and each updated record is checked.

The AllDataVisibilityPermissionChecker class checks if a user has access to a record via profile permissions.

The ChatterFeedFollower class creates a subscription for the user to the record if the user isn’t subscribed to the record yet.

<code class="rainbow" data-language="java"><span class="keyword">public</span> <span class="keyword">void</span> <span class="entity function">selectAlreadyExistedSubscriptions</span>(<span class="entity class">List</span><span class="operator"><</span><span class="entity class">Id</span><span class="operator">></span> record_id_list, <span class="entity class">List</span><span class="operator"><</span><span class="entity class">Id</span><span class="operator">></span> user_id_list){
    <span class="entity class">List</span><span class="operator"><</span><span class="entity class">EntitySubscription</span><span class="operator">></span> subscriptions <span class="operator">=</span> [<span class="constant">SELECT</span> <span class="entity class">ParentId</span>, <span class="entity class">SubscriberId</span> <span class="constant">FROM</span> <span class="entity class">EntitySubscription</span> <span class="constant">WHERE</span> <span class="entity class">ParentId</span> <span class="constant">IN</span><span class="operator">:</span>record_id_list <span class="constant">AND</span> <span class="entity class">SubscriberId</span> <span class="constant">IN</span><span class="operator">:</span>user_id_list <span class="constant">LIMIT</span> <span class="integer">999</span>];
    <span class="entity function">for</span>(<span class="entity class">EntitySubscription</span> subscription<span class="operator">:</span>subscriptions) {
        <span class="entity class">Set</span><span class="operator"><</span><span class="entity class">Id</span><span class="operator">></span> record_ids <span class="operator">=</span> <span class="keyword">new</span> <span class="entity function">Set<id></id></span>();
        <span class="entity function">if</span>(<span class="operator">!</span><span class="entity class">ListRecordIdsBySubscribedUsersIdsMap</span>.<span class="entity function">containsKey</span>(subscription.<span class="entity class">SubscriberId</span>)) {
            record_ids.<span class="entity function">add</span>(subscription.<span class="entity class">ParentId</span>);
        } <span class="keyword">else</span> {
            record_ids <span class="operator">=</span> <span class="entity class">ListRecordIdsBySubscribedUsersIdsMap</span>.<span class="entity function">get</span>(subscription.<span class="entity class">SubscriberId</span>);
            record_ids.<span class="entity function">add</span>(subscription.<span class="entity class">ParentId</span>);
        <span class="entity class">ListRecordIdsBySubscribedUsersIdsMap</span>.<span class="entity function">put</span>(subscription.<span class="entity class">SubscriberId</span>, record_ids);
The first parameter of the method is a list of updated record ids, the second parameter is a list of users which should subscribe to the record. The result of this method is ListRecordIdsBySubscribedUsersIdsMap map, that contains a user id as a key, and as a value set of record ids on which user is already subscribed.
<code class="rainbow" data-language="java"><span class="keyword">public</span> <span class="keyword">void</span> <span class="entity function">subscribeToRecord</span>(<span class="entity class">Id</span> record_id, <span class="entity class">Id</span> user_id){
    <span class="entity function">if(!isUserAlreadySubscribed</span>(record_id, user_id)) {
        <span class="entity function">createNewSubscription</span>(record_id, user_id);
This method subscribes a user to a record if the user isn’t already subscribed to record.

The full list of the used metadata in this feature can be viewed on Github.

Also feel free to install this feature with the unmanaged package.

Get In Touch

Discover how Pexlify can create digital experiences that transforms and optimises your business with Salesforce.

Get Started