The CreatorCon Call for Content is officially open! Get started here.

Andrew Bettcher
Kilo Sage

Most of our incidents are created from inbound emails sent by devices out on customer sites. Unfortunately, some of these devices keep sending the same error emails over and over again until someone fixes the root cause which results in us having a lot of duplicate tickets in our instance that need to be cancelled.

We worked on this problem for a few months, tweaking the inbound rules until we were satisfied that we would not exclude required emails. I thought that the process we went through might come in useful for other users and so, here it is, my first article.

The first thing we did was to have our internal IT team temporarily divert all emails that go to PROD into our DEV instance so that we could replicate the duplicates.

The first idea was to allow users to add records to a table based on the emails received. The inbound rule would check this table for a record that match the new email. If it found a match then it looked for active tickets that also matched. If it found one it would ignore the email but update the existing ticket and stop processing any further inbound rules. If it didn't find one it would do nothing which meant the next inbound action would kick in.  

This is the script from the inbound action which was set to run before "Create Incident" but with the same triggers (i.e. email is "New").  The IF statement at the top is specific to us and is the result of testing. We don't want to ignore any of the emails that match the criteria list here. 

(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

if (email.subject == 'ECA - AlienVault Alarm [Low Priority]' || email.subject == 'M365 Service Health Notification' || email.subject == 'We found suspicious content in a message' || email.subject == 'MTI - AlienVault Alarm [Low Priority]'){
	
	logger.logInfo('Skipped Ignore due to exception rule');
	
	return;
	
}
	
var sender = email.origemail.toString().toLowerCase();
var subject = email.subject.toString().toLowerCase();
var body = email.body_text.toString().toLowerCase();
var body2 = email.body_text.toString().toLowerCase();

var ignored = new GlideRecord('u_duplicate_emails');	
ignored.addQuery('u_email_lower_case',sender.toLowerCase());
ignored.addQuery('u_active',true);
ignored.query();
					
			while(ignored.next()){
					
				var ignoresender = ignored.u_email_lower_case;
				var ignoresubject = ignored.u_subject_lower_case;	
				var ignoreshortdesc = ignored.u_short_description_lower_case;
				var ignorebody = ignored.u_body_lower_case;
				var ignorebody2 = ignored.u_and_also_lower_case;	
				
				if (ignorebody == '' && ignoresubject == ''){
				
				event.state="stop_processing";
					
//if the ignore fields for both subject and body are blank, further rules are not processed
					
				}
				
				else{ //otherwise, compare the ignore fields against the actual content of the subject and body of the email.
					
					var ignoresendermatch = false;
					var ignorebodymatch = false;
					var ignorebody2match = false;
					var ignoresubjectmatch = false;
								
				if (ignoresender == '' || sender.indexOf(ignoresender) != -1){
					ignoresendermatch = true;
				}				

					if (ignoresubject == '' || subject.indexOf(ignoresubject) != -1){
						ignoresubjectmatch = true;
					}
					
					if (ignorebody == '' || body.indexOf(ignorebody) != -1){
					ignorebodymatch = true;
					}
					
					if (ignorebody2 == '' || body2.indexOf(ignorebody2) != -1){
					ignorebody2match = true;
					}
					
											
					if (ignoresendermatch){
						if (ignoresubjectmatch){
							if (ignorebodymatch){
								if (ignorebody2match){

//if a Duplicate Email record is found that matches the email details the next section tries to find an active incident that matches the duplicate record								
									
var origInc = new GlideRecord('incident');
origInc.addQuery('active', true);
origInc.addQuery('state','!=', 6);
//origInc.addQuery('caller_id.email', ignored.u_email_address);
//origInc.addQuery('short_description', ignored.u_subject);
origInc.addQuery('short_description', 'CONTAINS', ignored.u_short_description);
									
if (ignored.u_short_description_and_also != '') {
	
origInc.addQuery('short_description','CONTAINS', ignored.u_short_description_and_also);
}									
									
origInc.query();
																									
	while (origInc.next()){
		logger.logInfo('Matched Incident: ' + origInc.number);
		var notes = origInc.comments.getJournalEntry(-1);
		var notesLower = notes.toLowerCase();
		var incMatchNotes =  false;

		if (ignorebody.indexOf(notesLower) || ignorebody2.indexOf(notesLower)){
			incMatchNotes = true;}
		
			if (incMatchNotes){
				origInc.u_duplicate_messages = 'Duplicate email received and ignored: ' + subject;
				origInc.u_duplicate_alert_count += 1;
				origInc.update();
				logger.logInfo('Duplicate email received and ignored');
				event.state="stop_processing";
				

//if an active incident is found stop processing, otherwise, do nothing (i.e. the next inbound action runs)
			}
else
	
	logger.logInfo('No matches found. Move on to next inbound rule');
					
	}
								}
							}
						}
					}				
				}
				
			} 
			
			
			//if the code gets to this stage, it means a match was not found.
			
			
})(current, event, email, logger, classifier);

This relies on users (the Service Desk) identifying duplicates and taking action to prevent further duplicate tickets and so the next stage was to see if we could do this automatically. This required a lot of tweaking over the course of weeks. We looked at the results daily focussing on incidents that did have duplicate entries to ensure that they had been correctly identified and on tickets that hadn't been identified as duplicates to ensure that the script had correctly allowed them to be logged as normal new tickets.

(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {

	
//initial exception rule. Hardcoded subjects agreed with Jason Nicolas.
	
if (email.subject.contains("AlienVault") || email.subject.contains("Cloud App") || email.subject == 'M365 Service Health Notification' || email.subject == 'We found suspicious content in a message'){
	
	logger.logInfo('Skipped Ignore due to intial exception rule');
	event.state="stop_processing";	
}
	
var sender = email.origemail.toString();
var subject = email.subject.toString();
var body = email.body_text.toString();
var body2 = email.body_text.toString();
var senderLower = email.origemail.toLowerCase();
		
//builds the incident query based on what the create incident inbound rule would have used to add short description to the incident if the email is a duplicate.	
	
var origInc = new GlideRecord('incident');
origInc.addQuery('active', true);
origInc.addQuery('state', '!=', 6);
	
if (email.body.alert_text != undefined)
    origInc.addQuery('short_description', email.body.alert_text);

else if (email.body.description != undefined && email.body.alert == undefined)
    origInc.addQuery('short_description', email.body.description);

else if (email.body.alert != undefined && email.body.description == undefined)
    origInc.addQuery('short_description', email.body.alert);

else if (email.body.alert != undefined && email.body.description != undefined)
    origInc.addQuery('short_description', email.body.alert);

else origInc.addQuery('short_description', email.subject);

origInc.query();
		
	//iterate through any active incidents that match and compare the email body to the entire notes from the incidents AND look for the sender in the notes too
	
      while (origInc.next()){
		logger.logInfo('Matched Incident: ' + origInc.number);
		var notes = origInc.comments.getJournalEntry(-1);
		var notesLower = notes.toLowerCase();
		
		if (notesLower.indexOf(senderLower) ==-1){
		logger.logInfo('Sender (' + senderLower + ') could not be found in the notes of the matched tickets: ' + notesLower);
	
	}

		var incMatchNotes =  false;

		if (notesLower.indexOf(body) || notesLower.indexOf(body2)){
			incMatchNotes = true;}
  
			if (incMatchNotes){
				origInc.u_duplicate_messages = 'Duplicate email received and ignored: ' + subject;
				origInc.u_duplicate_alert_count += 1;
				origInc.update();
				logger.logInfo('(Auto)Duplicate email received and ignored');
				event.state="stop_processing";
				

//if an active incident is found stop processing, otherwise, do nothing (i.e. the next inbound action runs)
			}
else
	
	logger.logInfo('(Auto) No matches found. Move on to next inbound rule');
					
	}
	
})(current, event, email, logger, classifier);

You'll note a number of comparisons done near the end to confirm that the email is in fact a duplicate of the email that created the original incident as well as ways to match the subject line with the part of the email that was original used to add the short description to the ticket. In some cases we would use something from the body (e.g. "Alert: xxxxx) as the short description.

We also added a new field that would count the number of duplicates received against a specific ticket as well as seperate journal to list all of the duplicates and avoid clogging up the notes journal.

Good luck if you're in the same position and attempting something similar. Happy to help or discuss further as required.

Version history
Last update:
‎06-02-2021 06:00 AM
Updated by: