With the Spring ’16 release Salesforce introduces a new SandboxPostCopy interface. Implementing the SandboxPostCopy interface will allow a class to run after you create or refresh a sandbox. This feature comes in very handy when trying to make your sandbox environment development ready. Often times this process involves removing sensitive data from fields on some objects, invalidating customer emails and other data manipulations. This enforces data standard policies and prevents email automations from reaching the customers during the development phases.
In the past, you had to manually take care of the processes outlined above using data loaders or partially automated with batch jobs. In this post, we explore how to use the SandboxPostCopy interface to automate invalidating all email fields in the system. This prevents email automation from accidentally reaching your customers.
Implementation
We will begin by defining an InvalidateEmails class that implements SandboxPostCopy and go on to define the provided runApexClass function.
https://gist.github.com/krystiancharubin/b7fe226f2bbbfd5b319d
In the snippet above we can see the InvalidteEmails.runApexClass definition. The function takes a SandboxContext as a parameter dynamically injected during execution. The SandboxContext class provides three pieces of information utilized during the execution. These include the Organization Id, Sandbox Id, and Sandbox Name. In our case, we are not going to utilize any of these, so let’s focus on the run function definition.
https://gist.github.com/krystiancharubin/4ce74267f569209f518f
The run function includes findEmailFields, which find all emails fields across all SObjects found in the system and processEmailFields which then queries for the found email fields and invalidates them. This prevents email delivery.
https://gist.github.com/krystiancharubin/450ac31b78ee702cf11a
In the snippet above, we can see that findEmailFields iterates over all SObjects found in a global describe. It then skips over any objects that cannot be queried or updated. Finally, it creates a list of all email fields found on a given SObject. In this case, we are only allowing filterable email fields. This means that we can include these fields in a WHERE SOQL clause. As you will see later, this is because we are only going to query for email fields that are not blank, and to do so we need to use them in the WHERE clause.
https://gist.github.com/krystiancharubin/e737c573d0fcb1093a18
Now that we’ve found all the email fields, we need to process each SObject by querying the given records and updating them to invalidate the email fields. In the snippet above we can see that the processEmailFields function iterates over the SObject to Email Fields map build previously. (We will skip the conditionals part for the moment. I promise I will get back to it). Next, we get the list of email fields from the map and use the getSOQL helper function to build out a SOQL query.
https://gist.github.com/krystiancharubin/597771a8e1c2de61584f
The getSOQL function helps us to easily build out a complex query by utilizing String.replace and String.join. They dynamically build a list of fields, conditionals and merge them into a SOQL query template.
Getting back to the processEmailFields function, we can see that the generated SOQL is used to query for a data set from a given SObject. That data set is then iterated. Each email field is invalidated by replacing any nonalphanumeric characters with underscores and concatenating a @disabled.disabled suffix. (Instead of invalidating the emails you can also set it to some fixed test email address that was created specifically for testing purposes).
Now as promised, we will return to the conditionals part omitted earlier. If we run the code omitting the conditionals portion, we would run into an issue if we had any Converted Leads in the system. This is because we are not allowed to update Converted Leads. To get around that problem, we can create a function that will check if a given SObject record matches a set of field-value conditions. In this case, the condition will be on the Lead object, and it will be isConverted equals false.
https://gist.github.com/krystiancharubin/91bfb0daea6b76c03993
In the snippet above, we have a Map definition that specifies the condition we described previously. This can also define numerous conditions on other SObjects.
So when iterating over the SObjects that have Email Fields, we check if there are any extra conditionals specified for the given SObject. If there are, then we query for those fields and then use the checkConditions function to determine if a given record meets the criteria. If there are no criteria or the criteria are met, we continue to process the record; otherwise it is skipped.
https://gist.github.com/krystiancharubin/9793dd3eb9bb515c3275
The checkConditons function takes in an SObject and field value pair Map of conditionals. Each conditional is then checked against the record value. If any record value does not match the conditional value, then false is returned. This signifies the record did not pass the conditional test and is therefore ignored. If all the conditionals are met the true is returned, and the record processes as described previously.
Conclusion
We now have a class that we can set to run after creating or refreshing a sandbox by defining it in a Sandbox Template. Generally, classes implementing the SandboxPostCopy can be specified to run using a Sandbox Template for Full or Partial sandbox or directly for Developer and Developer Pro sandboxes. The InvalidateEmails class we have created will only be useful in a Full or Partial sandbox since it tries to modify existing records which would not exist in the Developer type sandboxes since those only contain metadata. One possible use of the SandboxPostCopy interface in Developer sandboxes would be to create test data dynamically.
Full Code
https://gist.github.com/krystiancharubin/9a8376fe664af0117562
Learn More
Read about Enhanced Email for Salesforce and our series of posts about the Spring ’16 release:
- What is Lightning?
- Lightning Experience Features
- Lightning Under the Hood
- Making the Move to Lightning
- Spring ’16 Release Updates
You can also check out our other posts on customizing your Salesforce solution.
Nice Post. Do we need to worry about Governor limits while running post refresh logic. For eg I may need to update all contacts emails, so while querying contact records will it throw error?
Hi Sagar,
The documentation for SandboxPostCopy interface does not mention any exceptions regarding governor limits.
It most likely is subjected to the same governor limits as any other synchronous apex.
In order to process large amount of records you should be able to submit a batch job from the SandboxPostCopy class.
Thank you for this flexible framework, Krystian. How would you implement it with a batch job? I got a “Too many DML rows: 10001” error in my partial copy sandbox.
Hi,
We found your code very usefully, but just want to ask that will it help in updating more than two millions records ?
Please reply ASAP
Thanks,
Jayni
Hey Jayni,
You should be able to process two million records by submitting a batch job from the SandboxPostCopy class.
Regards,
K
Hi,
Do you have any experience in creating a CollaborationGroup using SandboxPostCopy class ? My requirement is to create a Public Group post sandbox refresh.
I have tried following ways but no success:
Owner = Automated Process User (autoproc)
CollaborationType = Public
Error = System.DmlException: Insert failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, This user can’t be added to the group.: []
Owner =
CollaborationType = Public
Error = System.DmlException: Insert failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, This user doesn’t have permission to own groups. : [OwnerId]
assigning Method run() with @Future
OwnerId =
CollaborationType = Public
Error = method didn’t execute, hence no Collaboration Group created.
Owner =
CollaborationType = Private
Error = System.DmlException: Insert failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, This user doesn’t have permission to own groups. : [OwnerId]
Hi,
I am having trouble deploying ‘SandboxPostCopy’ class to production. Do we need test class to deploy the class to production?
Can we run classes implementing the SandboxPostCopy manually in salesforce sandbox and do we need to mention the class name before kick starting the refresh or will the class automatically run after the refresh is completed