Create automatically renewal opportunities and inform the opportunity owners by email with a single email (Scheduled-Triggered flow & Apex job)

Business case:

On the first of every month, company X checks the end date of license contracts and manually creates renewal opportunities for all license records with a contract end date that is 2 months ahead. This process is very time-consuming, so the company asked the Salesforce (SF) expert how it could be automated. Johnny, the SF expert, comes up with a good solution design to build this according to Salesforce best practices.

Johnny first creates a formula checkbox field on the license object with the logic in it. Eventually, he will use this as a trigger in the Scheduled-Triggered flow.

Formula field on License object:

AND(

DAY(TODAY()) = 1,

YEAR(TODAY()) = YEAR(TODAY()),

ISPICKVAL(License_status__c, ‘BASE’),

OR(

AND(

MONTH(TODAY()) <= 10,

MONTH(Support_Contract_End__c) = MONTH(TODAY()) + 2,

YEAR(Support_Contract_End__c) = YEAR(TODAY())

),

AND(

MONTH(TODAY()) > 10,

MONTH(Support_Contract_End__c) = MOD(MONTH(TODAY()) + 2, 12),

YEAR(Support_Contract_End__c) = YEAR(TODAY()) + 1

)

)

)


Explanation of Formula Field: This checkbox will turn true for a License record when:

– It is the first of the month of this year.
– The status is ‘BASE’.
– For a license record that has a support contract end date that falls two months ahead of the current month, excluding November or December.
– If the current month is November or December, it checks the support contract end date that is two months ahead of the current month, adding one year to today’s year.

Scheduled-Triggered flow

– Schedule the start date and start time as desired. For example, if you want the creation of renewal opportunities to occur at 7 am, enter 7 AM as the start time.
– Ensure that this flow is applied to the ‘License’ object and use the formula field as a condition (where the formula field is not equal to null).
– Map fields from license records within the loop to the variable ‘opportunity record’. Then, as the second assignment, assign these variables to a new variable ‘record collection’ (of type opportunity).

 

For every renewal opportunity created, we will check the checkbox ‘Autocreated Renewal Oppy’. This checkbox needs to be created on the Opportunity object, and we will utilize it later in the Apex class.

Congratulations! You have now automated the process to create opportunities automatically when needed!

Notify opportunity owners by email

Let’s say 22 renewal opportunities are created by the flow, and these records are owned by 4 opportunity owners. The next requirement is to ensure that these 4 individuals receive a notification by email. Each opportunity owner needs to receive 1 personalized notification email stating that renewal opportunities have been created for them, along with a link to the report. Achieving this requirement with flow automation is impossible; therefore, we will create an Apex class for this purpose, which we will utilize in the Apex job.

Custom setting:
– before creating the Apex class, create a custom setting first where you can store the FolderID

Apex class:

public with sharing class SendRenewalOpportunityOverviewBatch implements Database.Batchable<sObject>, Schedulable, Database.Stateful {
private Map<String, String> emailAddresses;
private String folderId;
public Database.QueryLocator start(Database.BatchableContext bc){
List<EnvironmentFolderIdRenewal__c> folderIdRecords = [SELECT Id, Folder_Id__c FROM EnvironmentFolderIdRenewal__c WHERE Name = ‘FolderUrl’];
if(folderIdRecords.size() != 1){
throw new BatchException(‘Custom Setting voor FolderId kon niet worden gevonden. Neem contact op met uw System Administrator’);

}

this.folderId = folderIdRecords.get(0).Folder_Id__c;

this.emailAddresses = new Map<String, String>();

return Database.getQueryLocator([   SELECT  Id, Owner.Email, Owner.FirstName

FROM    Opportunity

WHERE   Autocreate_renewal_oppy__c = true]);

}

public void execute(Database.BatchableContext bc, List<sObject> scope){

List<Opportunity> opps = (List<Opportunity>)scope;

for(Opportunity o : opps){

this.emailAddresses.put(o.Owner.Email, o.Owner.FirstName);

o.Autocreate_renewal_oppy__c = false;

}

update opps;

}

public void finish(Database.BatchableContext bc){

List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();

for(String email : this.emailAddresses.keyset()){

list<String> toAddresses = new List<String>();

String firstName = this.emailAddresses.get(email);

String htmlBody = ‘<p>Hi ‘ + firstName + ‘,</p><p><br></p><p>Salesforce heeft vandaag automatisch renewal’;

htmlBody += ‘ opportunities aangemaakt op de stage \’Intake\’. Klik ‘;

htmlBody += ‘<a href=”https://YourSFSolution–dev.sandbox.lightning.force.com/lightning/r/Folder/’ + this.folderId + ‘/view?queryScope=userFolders”‘;

htmlBody += ‘ rel=”noopener noreferrer” target=”_blank”>hier</a> om naar het rapport te gaan.</p><p><br></p><p>Hopelijk je voldoende ‘;

htmlBody += ‘geïnformeerd te hebben.</p>’;

Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();

message.htmlBody = htmlBody;

message.subject = ‘Renewal opportunities aangemaakt!’;

toAddresses.add(email);

message.toAddresses = toAddresses;

messages.add(message);

}

Messaging.SendEmailResult[] results = Messaging.sendEmail(messages);

for(Messaging.SendEmailResult result : results){

if(!result.success){

system.debug(‘The email failed to send: ‘ + result.errors[0].message);

}

}

}

public void execute(SchedulableContext sc){

database.executeBatch(new SendRenewalOpportunityOverviewBatch());

}

public class BatchException extends Exception{}

}

This Apex class is designed to send email notifications to Opportunity owners when certain criteria are met. Let’s break down the key components:

  1. **Class Declaration**: The class is declared as `public with sharing` which ensures that the sharing rules of the current user are enforced.
  2. **Interfaces Implemented**: The class implements three interfaces:

– `Database.Batchable<sObject>`: This indicates that the class can be invoked by the Salesforce Batch Apex feature to process records asynchronously in batches.

– `Schedulable`: This indicates that the class can be scheduled to run at specific times.

– `Database.Stateful`: This allows the class to maintain state across transactions.

  1. **Instance Variables**:

– `emailAddresses`: A map to store email addresses and corresponding first names of Opportunity owners.

– `folderId`: A string variable to store the ID of a folder used in the email message.

  1. **Start Method**: This method queries `EnvironmentFolderIdRenewal__c` records to retrieve the `Folder_Id__c` where the name is ‘FolderUrl’. It initializes `folderId` with the retrieved folder ID and initializes `emailAddresses` map. It returns a query locator to fetch Opportunities where `Autocreate_renewal_oppy__c` is true.
  2. **Execute Method**: This method iterates over the list of Opportunity records received in the batch (`scope`). For each Opportunity, it adds the owner’s email and first name to the `emailAddresses` map and sets `Autocreate_renewal_oppy__c` field to false. After processing all records, it updates the list of Opportunities.
  3. **Finish Method**: This method constructs email messages for each email address stored in the `emailAddresses` map. It constructs the HTML body of the email with dynamic content, including the first name of the recipient and a link to a Salesforce report using the `folderId`. It then sends the email messages using the `Messaging.sendEmail` method. It also handles any email sending errors.
  4. **Schedulable Method**: This method is required by the `Schedulable` interface. It’s responsible for scheduling the batch job to run. In this case, it schedules an instance of the `SendRenewalOpportunityOverviewBatch` class to run immediately.
  5. **Inner Class BatchException**: This is a custom exception class used for error handling within the batch process.

Overall, this class orchestrates the sending of email notifications to Opportunity owners based on specific criteria and integrates with Salesforce Batch Apex and Scheduling features for asynchronous processing.

Scheduling the Apex class in production

After deploying every related metadata to production, you need to make sure to run 1 script to schedule the Apex Class!

Go to your developer console:
– 1. Click on debug
-2. Open execute Anonymous Window
-3. Run the following: System.schedule(‘Send Renewal Emails’, ‘0 0 8 1 * ?’, new SendRenewalOpportunityOverviewBatch());

With this run, you will schedule the apex class at 8am in the morning every first of the month! So for example if you schedule the flow at 7am every first of the month, 1 hour later it will send out the email to the opportunity owners.

 

HC Partners B.V.Solarplaza International B.V.