Brian Dailey1
Kilo Sage

You will have users who insist on emailing you from Gmail, Yahoo, Outlook, anything and everything EXCEPT for the email address they have registered with you in their user profile.   Their issue is either going to be rejected or logged as Guest.

This is a topic near and dear to my heart (well, the Service Management one at least), and it's something I believe is missed out-of-the-box by many ITSM solutions (this one included).   For some reason, properly handling more than one email address for each user just doesn't seem to be a serious consideration.   Perhaps they were all designed around more corporate-centric requirements, where all users are given a single address they are expected to use for official business.

But in other less structured organizations, users can be free to contact you however they wish.   This is especially true for users in Education, simply due to the nature of the environment.   You can always deny service, reject the request and force them to email you from a recognized account, but that just comes off as rigid and unhelpful.   As any modern service organization should, we strive to be accommodating wherever reasonably possible.   And logging all those under a Guest account just leads to more work for us.

Luckily for us, the ServiceNow platform comes ready-made with a method for managing multiple email addresses per user known as Notification Devices.   The challenge here is that it still only checks the single email address field on user profiles when processing Inbound messages.   Any message received from a sender's address that is not found in the email field of an active user can be either rejected or processed as Guest.

Challenge accepted...

Overall layout of the solution:

alt_email_handling_1.png

alt_email_handling_2.png

Here's a brief explanation of the approach, before we dive in:

  1. The first part is the meat of it... this is what allows you to use multiple email addresses per user and have the system recognize them for Inbound Email processing by performing a lookup on the Notification Device table before the record for the email message gets inserted.
  2. The second part is just a low-maintenance method to manage the notification devices.   Let the user profiles do the work for you...   devices will get populated as user email addresses are updated.   That way, you don't need to create any special UI or train your users/analysts to handle the additional email addresses as Notification Devices.   With a few business rules setup, you can simply allow all email addresses to be entered via the Email field on a user's profile, and the devices are managed automatically in the background. And since this all happens server-side, it applies to data imports as well.   (you could still script population of notification devices for your users, if you have the data handy and a desire to do so)

Note: as a part of the Lookup process, we store the original sender's address in the [sys_email].reply_to field before overwriting [sys_email].user, so you can still use it later during Inbound Email Processing to add to a Watch List (or some other field) to maintain the original point of contact.

Now, the scripts you'll need for each process item 1 & 2, as listed above:

1. Lookup User by Alternate EmailScript

Business Rule on [sys_email]

Match User on Alternate Address

When

before/on Insert

Condition

User ID is EMPTY

Type is Received

  OR Type is Received Ignored

Desc:

If the user is unrecognized by email address, check the From address (current.user) to see if there is a match in [cmn_notif_device].

Note:

Copying current.user to current.reply_to allows you to keep the original sender address, since current.user is about to be overwritten.

matchUserByAltEmail();

function matchUserByAltEmail(){

try{

// If a sender is unrecognized, the sender's email address is stored in current.user and user_id is empty.

                                current.reply_to = current.user.toString();

// This returns a user object if found, otherwise returns null (defined by BR function)

var existingUser = getUserByAltEmail(current.user);

if (!(JSUtil.nil(existingUser))){

// If a match was found, change the email UserID to existingUser

current.user_id = existingUser.sys_id.toString();

current.user = existingUser.sys_id.toString();

}

else{

gs.log("No user found for address: " + current.user, "BR-Match User on Alternate Address");

//Automatic User Creation functionality can also be inserted here, if desired.

}

}

catch(err){

gs.log("An error occurred: (" + err.lineNumber + ") " + err, "BR-Match User on Alternate Address");

}

}

Script Include

getUserByAltEmail

function getUserByAltEmail(altEmail){

// Do not attempt lookup with a null address

if(JSUtil.nil(altEmail)){

return null;

}

// Find record from existing Alternate Email Addresses

try{

var gr = new GlideRecord('cmn_notif_device');

gr.query('email_address',altEmail);

if(!gr.next()){

// Alternate Email Address was not found

return null;

}

else{

// Alternate Email Address has matched, return the user object

return gr.user.getRefRecord();

}

}

catch(err){

gs.log(" An error occurred: (" + err.lineNumber + ") " + err, "BR-getUserByAltEmail");

return null;

}

}

2. Manage Devices by Entering as User Email AddressesScript

Business Rule [sys_user]

Create primary email device

When

After/on Insert

Condition

current.email != ''

createPrimaryEmailDevice(current);

Business Rule [sys_user]

Update primary email device

When

Before/on Update

Condition

current.email.changes()

updatePrimaryEmail();

function updatePrimaryEmail(){

try{

//User email address was cleared (deleted)

if(current.email == ''){

var userDevices = new GlideRecord('cmn_notif_device');

userDevices.addQuery('type=Email^user=' + current.sys_id + '^email_address!=' + previous.email);

userDevices.query();

//Another email device exists for this user, set it as primary

if(userDevices.next()){

                  userDevices.primary_email = true;

                  current.email = userDevices.email_address;

                  userDevices.update();

}

//No other email device exists for this user, abort update

else{

                  current.email = previous.email;

                  current.setAbortAction(true);

                  gs.setRedirectURL(current);

                  gs.addInfoMessage("Cannot delete a user's only email address.");

}

}

//A new email address was entered

else{

verifyNewPrimaryAddress(current.email);

}

}

catch(e){

                  gs.log("Error (" + e.lineNumber + "): " + e, "updatePrimaryEmail()");

}

}

//Checks for an existing cmn_notif_device and makes it primary if it belongs to the user, denies the

//update if it belongs to another user, or creates it as the user's primary if not already in existence.

function verifyNewPrimaryAddress(email){

try{

var existing = new GlideRecord('cmn_notif_device');

existing.addQuery('email_address', email);

existing.query();

//An email device already exists for this address

if(existing.next()){

//Existing email device belongs to another user, abort update

if(existing.user.sys_id != current.sys_id){

                  current.setAbortAction(true);

}

//Existing email device belongs to the current user, update Primary to true

else{

        existing.primary_email = true;

        existing.update();

}

}

//No email device exists for this address, create one as Primary

else{

createPrimaryEmailDevice(current);

}

}

catch(e){

        gs.log("Error (" + e.lineNumber + "): " + e, "verifyNewPrimaryAddress()");

}

}

Script Include

createPrimaryEmailDevice

function createPrimaryEmailDevice(userRecord){

try{

var thisSysID = userRecord.sys_id;

var device = new GlideRecord('cmn_notif_device');

device.user = thisSysID;

device.email_address = userRecord.email;

device.primary_email = true;

device.active = true;

device.type = 'Email';

device.name = 'Primary email';

if(device.insert()){

                  gs.addInfoMessage(gs.getMessage('Primary email device created for') + ' ' + GlideStringUtil.escapeHTML(userRecord.name) + ": " + userRecord.email);

return true;

}

else{

                  gs.log("Failed to insert Primary email device created for " + GlideStringUtil.escapeHTML(userRecord.name) + ": " + userRecord.email, "createPrimaryEmailDevice()");

}

}

catch(e){

                  gs.log("Failed to insert Primary email device created for " + GlideStringUtil.escapeHTML(userRecord.name) + ": " + userRecord.email, "createPrimaryEmailDevice()");

}

}

Business Rule [cmn_notif_device]

Restrict to Only One Primary Address

When

After/on Insert or Update

Condition

current.primary_email == true && !current.user.nil()

restrictToSinglePrimaryAddress();

function restrictToSinglePrimaryAddress(){

try{

// Set the user's default email address ([sys_user].email) equal to this Primary address.

var grUser = current.user.getRefRecord();

grUser.setWorkflow(false);

if(grUser.email != current.email_address){

          grUser.email = current.email_address;

          grUser.update();

}

// If there are other primary addresses for this user, set Primary=False for each

var gr = new GlideRecord('cmn_notif_device');

gr.addQuery('user',current.user);

gr.addQuery('sys_id','!=',current.sys_id);

gr.addQuery('primary_email',true);

gr.addQuery('type','Email');

gr.query();

while(gr.next()){

          gr.setWorkflow(false);

          gr.primary_email = false;

          gr.update();

}

}

catch(err){

        gs.addErrorMessage("(BR) Restrict to One Primary Email Address: An error occurred saving the record.   " + err);

}

}

And that's all there is to it...

Yes, I actually did say those words.   I know it looks like a lot of code, but really the functional part of the Lookup can be done with a single business rule, even though I packaged it with a separate Script Include.   The rest is just fluff to make the functional change invisible in terms of maintenance, so that you won't have to worry about managing Notification Devices from here on out.   Attention to the details is just a bunch of little steps that eventually get us over the hill.

Enjoy, and please post any suggestions or questions you may have.

-Brian

34 Comments