4

When you create/refresh a Sandbox, all (but the refreshing) users email addresses get crippled with a nice "example.com". Using security codes via email, this basically breaks the possibility for other users to login at the fresh sandbox, unless their email addresses get changed...

We have written some Apex fixing the email-addresses back for some or all users, but the extra loop in confirming the email-address change often is ignored or forgotten by the affected users, so that if these users should login usually some time has passed and the email-change-confirmation is expired after 72h. So eventually most of them still can't login and again they are wasting time of others. This really sucks.

For sure this email-mangling is done by Salesforce with a purpose in mind and for sure this is designed as an very powerful security feature to prevent normal users form getting spammed by Sandbox-Emails. Only that it bothers us a lot and has killed already so much of our time, that I would happily turn it OFF once and for all.

Is that possible? If so, how?

Bonus question:

Also the email deliverability is usually reduced on fresh Sandboxes. Also here we would love to have the full-thing without the need of extra clicks. At Setup > Email-Administration > Deliverability is there a way to get the Access Level on "All email" instead something less by default after a refresh?

2
  • 1
    Just a theoretical question how about running a batch job in sandbox post creation to replace example with your email domain? Commented Nov 1, 2018 at 10:34
  • 1
    @codeyinthecloud still ALL those users will need to click on the confirmation-link in a email which Salesforce sends to ALL users, if their email get changed. The new email-address is only updated AFTER each user has clicked on that link. This step is usually forgotten by my users. Even training does not help. They forget. They are humans. So it is bothering over and over and over again, killing time repetitively. I want to get rid of this. Totally.
    – Uwe Heim
    Commented Nov 1, 2018 at 11:37

4 Answers 4

2

Only that it bothers us a lot and has killed already so much of our time, that I would happily turn it OFF once and for all.

Is that possible? If so, how?

It is not possible to turn off this feature of removing @example.com from the email addresses after a sandbox refresh. And it's not recommended to go that route. As you have rightly figured out that Salesforce does it for purpose. And one of the main reasons to use example.com is because:

This domain is established to be used for illustrative examples in documents. You may use this domain in examples without prior coordination or asking for permission.

So, you don't have a way to accidentally trigger emails from sandbox to end users. (I have seen this causing issues based on my previous experience)

Another perspective to this is that with every sandbox refresh, you don't really need all Users to be back on that sandbox. E.g., if you are refreshing a developer sandbox, you only need the developers out there and say not the QA or Business Users.

If you have a CI/CD process in place, then you will not really require "frequent" sandbox refreshes. However still considering you want to refresh your sandboxes and that you need all Users to be able to login, below is one of the approaches you can try out.

  1. Deactivate all Users after refresh
  2. Load Users again with their valid email addresses. The only caveat here is that you will need to also have unique username for all those users
  3. Set a generic password for all Users using setPassword() SOAP API

With this approach, you communicate the username and password to targeted audience and that whenever they login, they will be forced to reset their password.

1

Here's the best way I found to do it:

Create a custom metadata type called Internal_User with fields User_Id__c and Email__c. Add your internal users (devs, admins, etc) as records on the custom metadata type, filling in the Salesforce user record id and email on each record.

Create apex class that implements SandboxPostCopy:

global class SandboxRefreshHandler implements SandboxPostCopy {
global void runApexClass(SandboxContext context) {
    run();
}

@Future
global static void run() {
    updateInternalUserEmails();
    disableScheduledJobs();
}

private static void updateInternalUserEmails() {
    // pull User SF Ids from Internal Users custom metadata type:
    Map<Id, Internal_User__mdt> userIdToInternalUser = new Map<Id, Internal_User__mdt>();
    for (Internal_User__mdt internalUser : [SELECT User_Id__c, Email__c FROM Internal_User__mdt]) {
        userIdToInternalUser.put(internalUser.User_Id__c, internalUser);
    }

    // query User object and cleanse emails (only for users defined as an internal user in the custom metadata type):
    List<User> internalUsersToUpdate = new List<User>();
    for (User user : [SELECT Id, Email FROM User WHERE Id IN :userIdToInternalUser.keyset()]) {
        if (userIdToInternalUser.containskey(user.Id)) {
            user.Email = userIdToInternalUser.get(user.Id).Email__c;
            user.isActive = true;
            internalUsersToUpdate.add(user);
        }
    }

    // Update users:
    if (!internalUsersToUpdate.isEmpty()) {
        update internalUsersToUpdate;
    }
}
}

Test class:

@isTest
class SandboxRefreshHandlerTest {
@isTest
static void testUpdateToCurrentEnvironment() {
    List<String> userIds = new List<String>();
    for (Internal_User__mdt internalUser : [SELECT User_Id__c FROM Internal_User__mdt]) {
        userIds.add(internalUser.User_Id__c);
    }

    List<User> internalUsers = [SELECT Id, Email FROM User WHERE Id IN :userIds];
    for (User user : internalUsers) {
        // append an inproper suffix to the user email, just like a sandbox refresh does:
        user.Email += '.invalid';
    }

    update internalUsers;

    Id orgId = UserInfo.getOrganizationId();
    Id sandboxId = UserInfo.getOrganizationId();

    Test.startTest();
    Test.testSandboxPostCopyScript(new SandboxRefreshHandler(), orgId, sandboxId, 'sandbox');
    Test.stopTest();

    // requery to see if the email no longer contains the inproper suffixes:
    for (User internalUser : [SELECT Email FROM User WHERE Id IN :userIds]) {
        System.assert(!internalUser.Email.contains('@example.com'));
        System.assert(!internalUser.Email.contains('.invalid'));
    }
}
}

When going to refresh the sandbox, populate the Apex Class field with SandboxRefreshHandler before kicking off the refresh.

0

I did something similar in the past. Create an apex class, to go over the users (I did it for admins - so all our admins can always log to any sandbox). and modify their email address by removing the '@example.com' Add that class to fire when the sandbox refresh is complete.

0

If you want a solution without apex class using sfdx, you can now do that with a few clicks with a VsCode Extension :

  • all users with .invalid email
  • users attached to selected profiles
  • manually selected users

enter image description here

The command under the hood is sfdx hardis:org:user:activateinvalid

Quick tuto in this article

Disclaimer: my company authors the related open-source plugin

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .