Interested in a ServiceNow event built for developers? Registration for now[dev]26 is officially open!

RITM is not getting approved even the inbound email action got processed

Hemagiri B
Tera Expert

When I try to approve the RITM through email, the Inbound Email Action is being processed successfully, but the RITM approval state is not changing to “Approved” and remains in “Requested” state.

We are using the OOB Inbound Email Action “Update Approval Request.” As shown in the logs below, after the OOB code executes current.update();, we are getting a null result:

[Approval Email Debug] Update result: null

Could someone please check and help us identify the root cause of why the approval record is not being updated to “Approved”?

 

current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
var controller = new GlideController();
controller.putGlobal("approvalSource", "email");
current.update();
 
Logs as follow
 
HemagiriB_0-1788838415671.png

 

5 REPLIES 5

musislam
Kilo Sage

Hi Hemagiri,

 

That null from current.update() is actually a useful clue - it usually means the update got aborted rather than the script not running. A few things I'd check before digging deeper:

 

1. In your snippet I only see the comments line and current.update(). The OOB "Update Approval Request" action normally has this above it:

if (email.body.approve == "yes") current.state = "approved";

if (email.body.reject == "yes") current.state = "rejected";

Is that still there? And does the reply email actually contain the word "approve" on its own line? If the reply just says "approved" in a sentence or has a signature above it, email.body.approve won't be set and the state never changes.

 

2. Does the sender (email.from) map to the same sys_user who is the Approver on that sysapproval_approver record? If someone else is replying (delegate, shared mailbox, forwarded mail), the OOB approval business rules will quietly abort the state change, which lines up with the null you're seeing.

 

3. What state is the approval record in right before the email lands - still Requested, or has it already gone to Approved / No longer required?

 

4. Any custom before-update business rules on sysapproval_approver that could be calling current.setAbortAction(true)?

 

If you can share those I'm happy to reproduce it on a PDI and post back the exact fix.

Macki | Deloitte AU | Engineer Lead

1) This is the complete OOB code 

/*global current, email, gs, GlideController, GlideRecord*/
/*eslint-disable eqeqeq*/
processApprovalEmail();

function processApprovalEmail() {
"use strict";
var errorMsg = "";
var msgArray = [];


if (current.getTableName() != "sysapproval_approver")
return;

var displayValue = getApprovalDisplayValue(current);

if (!validUser()) {
gs.log(getFailurePreamble() + "Sender email does not match approval assignee.");
msgArray.push(displayValue);
msgArray.push(current.approver.getDisplayValue());
msgArray.push(current.approver.email);
errorMsg = gs.getMessage("approvalInvalidUser", msgArray);
createEmailEvent(errorMsg);
return;
}

if (current.state == 'cancelled') {
gs.log(getFailurePreamble() + "The approval has been canceled.");
msgArray.push(displayValue);
errorMsg = gs.getMessage("approvalCancelled", msgArray);
createEmailEvent(errorMsg);
return;
}
if (email.body.state != undefined)
current.state = email.body.state;

if (email.subject.indexOf("approve") >= 0)
current.state = "approved";

if (email.subject.indexOf("reject") >= 0)
current.state = "rejected";

if (current.state != "approved" && current.state != "rejected") {
gs.log(getFailurePreamble() + "The subject is malformed. The approver probably did not click the approve or reject button on the email.");
msgArray.push(displayValue);
errorMsg = gs.getMessage("approvalFailed", msgArray);
createEmailEvent(errorMsg);
return;
}

current.comments = "reply from: " + email.from + "\n\n" + email.body_text;
var controller = new GlideController();
controller.putGlobal("approvalSource", "email");
current.update();
controller.removeGlobal("approvalSource");

function validUser() {

/*
Defect Fix - User approval emails were evaluting the email against customer contact records.
*/
var appUserID;
var appUser = new GlideRecord('sys_user');
appUser.addEncodedQuery('sys_class_name=sys_user^user_name=' + email.from);
appUser.setLimit(1);
appUser.query();

while (appUser.next())
appUserID = appUser.sys_id;

/* || current.approver == appUserID was added to if statement - checks the actual user_name for approval - contact management job issue ..
*/

gs.info('Update Approval Request :: inbound approver ID: ' + appUserID + ' against approver' + current.approver);

if (current.approver == email.from_sys_id || current.approver == appUserID)
return true;




// check if the email is from a delegate of the approver
var g = new GlideRecord("sys_user_delegate");
g.addQuery("user", current.approver.toString());
g.addQuery("delegate", email.from_sys_id);
g.addQuery("approvals", "true");
g.addQuery("starts", "<=", gs.daysAgo(0));
g.addQuery("ends", ">=", gs.daysAgo(0));
g.query();
return g.hasNext();
}

function createEmailEvent(msg) {
gs.eventQueue("approval.email.errorMsg", current, email.from, msg);
}

function getFailurePreamble() {
return 'Approval email from ' + email.from + ' for task "' + displayValue + '" assigned to "' + current.approver.getDisplayValue() +
'" failed because: ';
}

function getApprovalDisplayValue(approval) {
if (!gs.nil(approval.sysapproval))
return approval.getDisplayValue();
else {
var target = new GlideRecord(approval.source_table);
if (target.get(approval.document_id))
return target.getDisplayValue();
}
gs.warn("Target for sysapproval_approver:" + approval.getUniqueValue() + " not found. Target=" + approval.source_table + ":" + approval.document_id);
return "Unknown";
}

}
 
2) I am only the approver for that RITM no deligate 
 
3) Still state is showing "Requested" 
 
4) There is no custom before-update business rule

Thanks Hemagiri, that narrows it down a lot.

 

Two things stand out from what you've shared:

 

1. The log line "Processed 'Update Approval Request', updated sysapproval_approver: Requested Item: RITM0112754" is written by the inbound email engine, not by your script. It only tells you the action ran against the right record - it does not prove the write went through. The null from current.update() is the real evidence: the script reached the update (so validUser() passed and state was set to approved/rejected in memory), and then something on the server rejected the write.

 

2. The script isn't fully OOB - the validUser() function has a "Defect Fix" customisation that matches user_name against email.from. That's fine and it passed here, so it isn't the cause, just worth knowing.

 

Also, since the log shows the email was classified as a reply and matched the approval record, the watermark / record matching part is working - the failure is downstream of that.

 

When update() returns null with no script error, it's almost always one of these:

 

- A before-update business rule calling current.setAbortAction(true) - including OOB ones, or an OOB one someone has modified. Filter Business Rules by Table = sysapproval_approver, When = before, Active = true, and also check Global rules; look at Updated by rather than just "custom".

- A Data Policy on sysapproval_approver (e.g. comments mandatory on approve/reject). Data policies apply to script updates too and fail them silently unless you look in the logs.

- Something that behaves differently for the sending user. Inbound email actions run in the context of the sender, so a rule that checks roles or gs.getUserID() can pass when you test as admin and block when the approver's email comes in.

 

Fastest way to find out which - add one line right after the update in a test copy of the action:

 

gs.info("Approval email update: " + result + " | " + current.getLastErrorMessage());

 

(where result is the return of current.update()). getLastErrorMessage() gives you the actual abort / data policy reason. Then check System Logs > All for the same minute - a Data Policy Exception or an aborted business rule will show up there.

 

As a control test, run this in Scripts - Background:

 

var gr = new GlideRecord('sysapproval_approver');

if (gr.get('<sys_id of the Requested approval>')) {

    gr.state = 'approved';

    var r = gr.update();

    gs.info(r + ' | ' + gr.getLastErrorMessage());

}

 

If that works as admin but the email path still returns null, the difference is the running user, and the business rule / ACL side is where to look. If it also returns null as admin, it's a rule or data policy on the table and getLastErrorMessage() will name it.

 

Post back what getLastErrorMessage() says and I'll point you at the exact fix.

Macki | Deloitte AU | Engineer Lead

Thanks for your update @musislam ,

please find the below output for the mentioned background script

Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Slow business rule 'Reduce request price - approval change' on sysapproval_approver:<span class = "session-log-bold-text"> Requested Item: RITM0112754</span>, time was: 0:00:00.314
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}-
-
-
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
*** Script: ApiUserRestUtils.getData statusCode:200
errorMessage:
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}-
-
-
nable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
*** Script: {"result":[]}
Slack approval message BR: Slack approval message at update RITM BR
ERROR:
RITM0112754
NIL channel: no thrown error
Slow business rule 'Slack approval message at update RITM' on sysapproval_approver:<span class = "session-log-bold-text"> Requested Item: RITM0112754</span>, time was: 0:00:00.869
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}-
-
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
*** Script: ApiUserRestUtils.getData statusCode:200
errorMessage:
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}

Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
*** Script: ApiUserRestUtils.getData statusCode:200
errorMessage:
Unable to find vtable operation for operation id {}-
-
-
Unable to find vtable operation for operation id {}
Unable to find vtable operation for operation id {}
*** Script: {"result":[]}
Slack approval message BR: Slack approval message at update RITM BR
ERROR:
RITM0112754
B Hemagiri
NIL channel: no thrown error
Slow business rule 'Slack approval message at update RITM' on sysapproval_approver:<span class = "session-log-bold-text"> Requested Item: RITM0112754</span>, time was: 0:00:00.273
Unable to find vtable operation for operation id {}