Restricting Outbound Email Recipients in Non‐Production Instances - ben-vargas/servicenow-wiki GitHub Wiki

This article describes how to implement a solution to restrict outbound emails in ServiceNow non-production instances (e.g., development, test) to a specific group of users. This is crucial for preventing accidental emails from being sent to real users during testing and development.

The Challenge

In non-production ServiceNow instances, it's crucial to prevent emails generated by the platform from reaching external users or production users. Sending test emails to a limited set of internal users or a specific group ensures proper testing of email functionality without disrupting real users. Without proper controls, these emails could cause confusion, expose sensitive data, or unintentionally trigger workflows in production environments.

The Solution

This solution uses a system property and a Business Rule to intercept outgoing emails and reroute them to a designated test group. This will ensure only those in that group will receive any emails. This approach provides a centralized and flexible way to manage email recipients in your non-production environments.

Implementation Components

  1. System Property:

    • tp13.glide.email.test.group: This system property stores the sys_id or name of the user group whose members are allowed to receive emails. This property allows for an easy way to turn on or off this functionality, or to swap the group used.
  2. Business Rule:

    • TP13 Filter SubProd / NonProd Emails: This Business Rule runs before sending emails (on the sys_email table). It intercepts outgoing emails based on a condition that checks for the above property, validates the group, and changes the recipients of the email.

Creating the System Property

To create the system property manually:

  1. Navigate to System Definition > Properties
  2. Click the New button.
  3. Fill out the following fields:
    • Description: Send all mail only to the members of this group (by sys_id) (non-production testing) -- The above property overrides this setting.
    • Name: tp13.glide.email.test.group
    • Table: sys_properties
    • Type: string
    • Value: Set this to the sys_id of the group that should receive the test emails, or the name of the group.

Creating the Business Rule

To create the Business Rule manually:

  1. Navigate to System Definition > Business Rules
  2. Click the New button.
  3. Fill out the following fields:
    • Name: TP13 Filter SubProd / NonProd Emails

    • Table: sys_email

    • When: before

    • Advanced: true (Checked)

    • Condition: gs.getProperty('tp13.glide.email.test.group','') != ''

    • Filter Condition: Set the condition builder with the following values: [type] [is] [send-ready] AND [body] [does not contain] [glide.email.test].

    • Script:

/**
* SubProd Email Filter
* This code handles checking all outgoing emails for who they're supposed to
* send to, and restricts it to only send to a specific group of people.
*
* This code is compatible with text notifications, and with calendar invites.
* This code is overridden by the glide.email.test.user property.
*
* Example Usage:
* On a Dev instance, to have only developers get emails.
*
* Specifically:
* This looks for the property "tp13.glide.email.test.group" and if it's populated
* it looks at all the recipients of the outbound emails, and checks to see
* if those email addresses or phone numbers belong to the members of the
* group referenced by sys_id in the property.
*
* If the user that created the record the email is about happens to be in the
* group, they will be added to the recipients list (to simplify testing).
*
* Note that in the code below:
* allowedEmails = people the email is slated to go to
* grpMembers = email addresses / phone numbers allowed to receive email.
*/

if (current.type != 'received') {
    subprodEmailFilter();
}

function subprodEmailFilter() {
    var table = current.target_table;
    var record = current.instance;
    var recips = current.recipients;
    var bcc = current.blind_copied;
    var cc = current.copied;
    var body = current.body;
    var body_text = current.body_text;
    var grpMembers = [];
    var allowedEmails = [];
    var allowedBCC = [];
    var allowedCC = [];
    var email = '';
    var phone = '';
    var lastUpdate = '';
    var groupName = '';

    // Grab the sys_id of the email group, and verify it's a valid group.
    var testGroup = gs.getProperty('tp13.glide.email.test.group', '');

    // If the email is not turned on, then this should not run.
    if (testGroup == '') {
        return false;
    }

    // Validate that the group exists.
    var grp = new GlideRecord('sys_user_group');
    if (grp.get('sys_id', testGroup)) {
        groupName = grp.name.toString();
    } else if (grp.get('name', testGroup)) {
        testGroup = grp.sys_id; // Sets the group sys_id
        groupName = grp.name.toString();
    } else {
        // SubProd Email Group not found, don't send any emails.
        current.type = 'send-ignored';
        gs.log('Email Test Group "' + groupName + '" provided but does not match a valid group, so no email has been sent. Check property tp13.glide.email.test.group.');
        return false;
    }

    // Look through all members of the test group, checking their email and phone.
    var grpMember = new GlideRecord('sys_user_grmember');
    grpMember.addQuery('group', testGroup);
    grpMember.query();
    var ndv;
    while (grpMember.next()) {
        // For each user, check their email
        email = grpMember.user.email.toString();
        if (recips.indexOf(email) >= 0) {
            if (allowedEmails[0] == '') {
                allowedEmails[0] = grpMember.user.email.toString();
            } else {
                allowedEmails.push(grpMember.user.email.toString());
            }
        }
        if (bcc.indexOf(email) >= 0) {
            if (allowedBCC[0] == '') {
                allowedBCC[0] = grpMember.user.email.toString();
            } else {
                allowedBCC.push(grpMember.user.email.toString());
            }
        }
        if (cc.indexOf(email) >= 0) {
            if (allowedCC[0] == '') {
                allowedCC[0] = grpMember.user.email.toString();
            } else {
                allowedCC.push(grpMember.user.email.toString());
            }
        }
        grpMembers.push(grpMember.user.email.toString());

        // Lookup that member's notification devices
        ndv = new GlideRecord("cmn_notif_device");
        ndv.addQuery("user", grpMember.user);
        ndv.addQuery("email_address", "!=", email);
        ndv.query();
        while (ndv.next()) {
            if (ndv.type == "SMS")
                phone = String(ndv.phone_number) + "@" + String(ndv.service_provider.email_suffix);
            else
                phone = String(ndv.email_address);

            if (recips.indexOf(phone) >= 0) {
                allowedEmails.push(phone);
            }
            if (bcc.indexOf(phone) >= 0) {
                allowedBCC.push(phone);
            }
            if (cc.indexOf(phone) >= 0) {
                allowedCC.push(phone);
            }
            grpMembers.push(phone);
        }
    }

    // Look to see who last updated the referenced record.
    if (table) {
        var gr = new GlideRecord(table);
        if (gr.get(record)) {
            lastUpdate = gr.sys_updated_by;
        }
    }

    // Check if the last updated user is in the allowedEmails array and add them if not
    // Only do this if there are no valid recipients.
    if (lastUpdate && !allowedEmails) {
        var usr = new GlideRecord('sys_user');
        usr.addQuery('user_name', lastUpdate);
        usr.addQuery('notification', 2); // Email notifications are set to 2
        usr.query();
        if (usr.next()) {
            if (grpMembers.toString().indexOf(usr.email) >= 0) {
                if (allowedEmails.toString().indexOf(usr.email) == -1) {
                    allowedEmails.push(usr.email.toString());
                }
            }
        }
    }

    // If the current recipients field is blank then set the text to NONE.
    if (recips == '')
        recips = 'NONE';

    // DO NOT APPEND anything if this is a calendar invite.
    if (body.indexOf("END:VCALENDAR") < 0 && body != '' && body != null) {
        current.body = body + '<div><br/><br/><hr/></div><div>Testing mode on via system property "tp13.glide.email.test.group". Intended recipients: ' + recips + '</div><br/>';
    } else if (body.indexOf("END:VCALENDAR") < 0 && body_text != '' && body_text != null) {
        current.body_text = body_text + '\n\nTesting mode on via system property "tp13.glide.email.test.group". Intended recipients: ' + recips;
    }

    // Set the recipients.
    current.recipients = allowedEmails.toString();
    current.blind_copied = allowedBCC.toString();
    current.copied = allowedCC.toString();

    return true;
}

Explanation:

  • The Business Rule runs before emails are sent and only when a property value is present.
  • The subprodEmailFilter function then:
    • Validates the test group, and if not valid, logs an error, ignores the email, and returns.
    • Queries all members of the test group, including their notification devices (SMS email addresses).
    • Checks if the original recipients (to, cc, bcc) exist in the test group's member list and if not, removes them from the original recipient list.
    • If the last updated user is a member of the test group (to allow testing by those users), they will be added to the recipients list.
    • It adds a line to the email body to identify that the email is from a test instance.
    • It updates the email with only valid recipients, and sets the copied and blind copied fields as needed.

How to Implement

  1. Create the System Property:
    • Follow the instructions above to manually create the system property.
  2. Create the Business Rule:
    • Follow the instructions above to manually create the Business Rule.
    • Copy the provided code into the Script field.
  3. Ensure the Business Rule is Active:
    • Navigate to sys_script.list and search for TP13 Filter SubProd / NonProd Emails.
    • Ensure the Active checkbox is selected.
  4. Testing:
    • Trigger emails from the instance, and confirm that only members of the configured group receive the messages. Verify that the last updated user for the record is also added if that user is a member of the test group.

Best Practices

  • Configuration: Ensure the tp13.glide.email.test.group property is configured correctly with the sys_id or name of a valid group.
  • Active Property: Always double-check that the business rule is active.
  • Group Management: Make sure the users in your test group are users who are appropriate for receiving email from the instance.
  • Error Handling: If errors occur during the process, make sure to look at the logs for detailed information about how the Business Rule ran and if any issues were reported.
  • Testing: Thoroughly test the solution to confirm that the correct users are getting emails, and all other users are not.
  • Avoid Production: This logic should never be enabled on a production instance.
  • Notification Devices: This script will get both email addresses, and phone numbers when looking for matching users. Ensure you have the proper mobile setup for this.
  • Direct User Override: Consider adding a property where individual users can opt out of the test email functionality, as currently, it is controlled by the group property only.
  • Script Includes: Consider moving some of the logic into a Script Include for better maintainability and reusability.

Security Considerations

  • System Properties: Restrict access to modify system properties to authorized users only.
  • Business Rules: Limit write access to business rules, to ensure that no one can disable the logic.

Conclusion

This solution provides a robust mechanism for restricting outbound emails in non-production ServiceNow instances. By implementing a system property and Business Rule, you can ensure that emails are only sent to authorized personnel during testing and development. This helps to improve security, prevent user disruption, and maintain the integrity of your non-production environments. Always remember to test all code in a non-production environment before deploying to production.

⚠️ **GitHub.com Fallback** ⚠️