SandboxPostCopy – Run Scripts After Sandbox Creation and Refresh

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:

You can also check out our other posts on customizing your Salesforce solution.

8 thoughts on “SandboxPostCopy – Run Scripts After Sandbox Creation and Refresh”

  1. 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?

    1. Krystian Charubin

      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.

  2. 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

    1. Krystian Charubin

      Hey Jayni,

      You should be able to process two million records by submitting a batch job from the SandboxPostCopy class.

      Regards,
      K

  3. 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]

  4. Hi,

    I am having trouble deploying ‘SandboxPostCopy’ class to production. Do we need test class to deploy the class to production?

  5. 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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top