Skip to content
Home » Blog » Sandbox User Emails

Sandbox User Emails

TL; DR;

Simple approach to fixing .invalid email addresses of selected users in newly created Sandboxes using SandboxPostCopy triggered Apex and a Permission Set.

The Problem

Scratch orgs are still a distant dream for many teams. Cloning a single Developer sandbox that’s been around for years and contains sort-of usable test data is often the best option available. No-one dares to refresh that sandbox and so you may run into the problem with .invalid email addresses for users created in the Template Sandbox directly rather than in Production (they can’t get in).

You don’t want to rely on a few dedicated people who have been created in Production before your template Sandbox was cut to create Sandboxes for you all the time. We faced just this issue recently and I decided to get around it using Apex and Permission Sets.

The Solution

When creating new Sandboxes you have the option to name a class to be run post-activation. The class just needs to implement the SandboxPostCopy interface. We can run code that will update the User’s email addresses for us.

We can’t just change users’ email addresses though. Salesforce will understandably always require the new email address to be verified. I read about a workaround where the current user was deactivated and a new one with the right email address was created. This is fine, but it is preferable to keep the same user. You don’t have to worry about usernames being unique and the user can keep their password. 

Since new User would mean an email with the activation link anyway, why not just strip the “.invalid” from the end of the email of the existing User. Sandbox email deliverability is “System Email Only” by default. Which covers the email change confirmation fine. It will go out.

You don’t want to reset all the users though. Not everyone needs to get an email and not everyone needs to access the Sandbox. For that I’ve created an ActiveSandboxUser Permission Set and assigned it to the core development and testing team.

The Apex code executed then queries the PermissionSetAssignment SObject to find out which Users to update. Job done.

global without sharing class SandboxSetup implements SandboxPostCopy {
   global void runApexClass(SandboxContext context) {
       updateSandboxUserEmailAddresses();
   }
 
   public static void updateSandboxUserEmailAddresses() {
       Set<Id> userIds = ListUtil.getUniqueIds(
           [SELECT AssigneeId FROM PermissionSetAssignment WHERE PermissionSet.Name = 'ActiveSandboxUser' AND IsActive = TRUE],
           PermissionSetAssignment.AssigneeId
       );
       List<User> activeSandboxUsers = [
           SELECT Id, Alias, Name, UserName, Email
           FROM User
           WHERE Id IN :userIds AND IsActive = TRUE AND Email LIKE '%.invalid'
           LIMIT 200
       ];
       for (User activeSandboxUser : activeSandboxUsers) {
           activeSandboxUser.Email = activeSandboxUser.Email.removeEnd('.invalid');
       }
       update activeSandboxUsers;
   }
}

Pros and Cons

This is a fairly simple approach, so that’s good. Depending on the Org and if there is already some other logic executed in the post activation context it may need to be adjusted a bit of course.

This will always send emails for all new sandboxes to the users with the Permission Set assigned. Most of the time needlessly. That’s why it’s important not to include too many users. Especially business users. Simple email client rule can make sure this not too annoying and it will save those who cannot create Sandboxes for themselves (until you can refresh your template Sandbox).

See it on GitHub

Where to next

Test Execution Dashboard

Test Execution Dashboard

Nov 30, 20225 min read

TL; DR; Tool to help with monitoring Test Execution time as you go through development or refactoring intended to encourage…

picture of person with 2 different shoes

Queueable Running User in Unit Test

Mar 15, 20234 min read

I noticed that the Running User context of a Queueable Job in a unit test depends on who calls the Test.stopTest()…

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x