Auto-reminder and auto-close after 10 business days for pending approvals and On Hold tickets
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
Hi All,
I have a requirement to implement a scheduled job in ServiceNow for both Incidents and Request Items (RITMs) with the following logic:
Requirement:
- For records in:
- Pending Approval
- On Hold
- The system should:
- Send reminders during the 10 business days
- If no action is taken within 10 business days, then:
- Auto-close the ticket
Business Rules:
- Business days = Sunday to Thursday
- Exclude weekends (Friday & Saturday)
- Exclude holidays (defined in schedule)
- Applies to:
- Incident
- sc_req_item (RITM)
Current Approach:
- I initially implemented a loop-based logic to calculate business days by excluding weekends
- However, this does not handle holidays
- I am now considering using GlideSchedule to:
- Calculate business duration
- Handle holidays properly
Challenges:
- How to accurately calculate 10 business days including holidays
- How to design reminder logic within those 10 days
- Ensuring performance efficiency in a scheduled job (avoiding heavy loops per record)
- Applying the same logic consistently across Incident and RITM
Scheduled job:// ================= DAY CHECK =================var gdtToday = new GlideDateTime();gdtToday.setStartOfDay();var day = parseInt(gdtToday.getDayOfWeekLocalTime(), 10);// Run only Sunday–Thursdayif (day != 5 && day != 6) {// ================= CALCULATE 10 BUSINESS DAYS =================var count = 0;var pastDate = new GlideDateTime(gdtToday);while (count < 10) {pastDate.addDaysLocalTime(-1);var d = parseInt(pastDate.getDayOfWeekLocalTime(), 10);// Skip Friday (5) and Saturday (6)if (d != 5 && d != 6) {count++;}}// VERY IMPORTANT → include full daypastDate.setDisplayValue(pastDate.getLocalDate() + " 23:59:59");gs.info("Auto-close running. Cutoff date: " + pastDate.getDisplayValue());// ================= INCIDENT =================var appr = new GlideRecord('incident');appr.addQuery('active', true);appr.addQuery('state', 3);appr.addQuery('sys_updated_on', '<=', pastDate);appr.addQuery('hold_reason', '6');appr.query();var incidentCount = 0;while (appr.next()) {gs.eventQueue('autocancel.incident', appr, "", "");appr.state = '8';appr.sys_updated_by = 'system';appr.comments = "This ticket has been automatically closed due to no activity over the past 10 business days (Sun–Thu). On Hold Reason: " + appr.getDisplayValue('hold_reason');appr.update();incidentCount++;}// ================= RITM =================var gr_SRonhold = new GlideRecord('sc_req_item');gr_SRonhold.addQuery('active', true);gr_SRonhold.addQuery('state', 17);gr_SRonhold.addQuery('sys_updated_on', '<=', pastDate);gr_SRonhold.addQuery('u_on_hold_reason', '2');gr_SRonhold.query();var ritmCount = 0;while (gr_SRonhold.next()) {var ritmSysId = gr_SRonhold.sys_id;// Reject approvalsvar notapproved = new GlideRecord('sysapproval_approver');notapproved.addQuery('source_table', 'sc_req_item');notapproved.addQuery('sysapproval', ritmSysId);notapproved.addQuery('state', 'requested');notapproved.query();while (notapproved.next()) {notapproved.state = 'rejected';notapproved.comments = "Auto-closed after 10 business days. On Hold Reason: " + gr_SRonhold.getDisplayValue('u_on_hold_reason');notapproved.update();}// Close RITMgr_SRonhold.state = '4';gr_SRonhold.comments = "This ticket has been automatically closed due to no activity over the past 10 business days. On Hold Reason: " + gr_SRonhold.getDisplayValue('u_on_hold_reason');gr_SRonhold.update();// Update Requestvar grReq = new GlideRecord('sc_request');grReq.addQuery('sys_id', gr_SRonhold.request);grReq.query();while (grReq.next()) {grReq.state = '4';grReq.update();}// Update Catalog Tasksvar grtask = new GlideRecord('sc_task');grtask.addQuery('request_item', ritmSysId);grtask.query();while (grtask.next()) {grtask.state = '4';grtask.update();}gs.eventQueue('autocancel.requestItem', gr_SRonhold, "", "");ritmCount++;}// ================= LOG =================gs.info("Auto-close completed. Incidents: " + incidentCount + ", RITMs: " + ritmCount);} else {gs.info("Auto-close job skipped (Weekend)");}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
an hour ago
Hi there @Deepika Gangra1
Right now your logic only excludes Fri/Sat, so holidays will still break the calculation.
I’d recommend:
- Create a business schedule (Sun–Thu + holidays)
- Use
DurationCalculatorto check if 10 business days elapsed - Run reminders separately (ex: day 3, 7, 9)
- Batch process records to avoid huge loops in scheduled jobs
Also, small optimization:
Instead of querying Requests and Tasks inside every RITM loop, consider using bulk updates or encoded queries to reduce DB hits.
Kind Regards,
Azar
Serivenow Rising Star ⭐
Developer @ KPMG.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
an hour ago
Hi @Its_Azar ,
Thanks for your suggestion, it’s really helpful.
In my case, the requirement is slightly different — I need to send reminders continuously for all 10 business days (Sun–Thu, excluding weekends and holidays) for records in Pending Approval and On Hold state.
If there is still no action after the 10th business day, the ticket should be auto-closed.
So instead of sending reminders only on specific days (like Day 3, 7, 9), the expectation is to trigger reminders daily during the 10 business day window.
Could you please help with a sample script or best approach using DurationCalculator or GlideSchedule to handle this efficiently (especially avoiding loops and handling holidays correctly)?
Also, any recommendations on avoiding duplicate reminders (since this will run as a scheduled job) would be really helpful.
Thanks in advance!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
32m ago
Hey @Deepika Gangra1
The better approach is:
- Create a schedule for Sunday to Thursday.
- Add holidays to that schedule.
- Use GlideSchedule to calculate the business duration.
- Store reminder tracking fields on the record to avoid duplicate reminders.
Script
(function executeAutoCloseReminderJob() {
var scheduleSysId = 'PUT_SCHEDULE_SYS_ID_HERE';
var schedule = new GlideSchedule(scheduleSysId);
var tenBusinessDaysMs = 10 * 24 * 60 * 60 * 1000;
var now = new GlideDateTime();
if (!schedule.isInSchedule(now)) {
gs.info('Auto-close reminder job skipped because current time is outside business schedule.');
return;
}
processIncidents(schedule, now, tenBusinessDaysMs);
processRITMs(schedule, now, tenBusinessDaysMs);
function processIncidents(schedule, now, tenBusinessDaysMs) {
var inc = new GlideRecord('incident');
inc.addQuery('active', true);
inc.addQuery('state', '3'); // On Hold
inc.addQuery('hold_reason', '6');
inc.addQuery('u_auto_close_processed', '!=', true);
inc.query();
while (inc.next()) {
if (!inc.u_pending_hold_start) {
inc.u_pending_hold_start = inc.sys_updated_on;
inc.update();
continue;
}
var start = new GlideDateTime(inc.u_pending_hold_start);
var duration = schedule.duration(start, now);
var elapsedMs = duration.getNumericValue();
if (elapsedMs >= tenBusinessDaysMs) {
inc.state = '8'; // Closed / Cancelled as per your instance
inc.comments = 'Ticket auto-closed because no action was taken within 10 business days.';
inc.u_auto_close_processed = true;
inc.update();
gs.eventQueue('autocancel.incident', inc, '', '');
continue;
}
if (isReminderAlreadySentToday(inc, now)) {
continue;
}
gs.eventQueue('autocancel.incident.reminder', inc, '', '');
inc.u_last_reminder_sent = now;
inc.u_reminder_count = parseInt(inc.u_reminder_count || 0, 10) + 1;
inc.update();
}
}
function processRITMs(schedule, now, tenBusinessDaysMs) {
var ritm = new GlideRecord('sc_req_item');
ritm.addQuery('active', true);
ritm.addQuery('state', '17'); // Pending Approval / On Hold as per your instance
ritm.addQuery('u_on_hold_reason', '2');
ritm.addQuery('u_auto_close_processed', '!=', true);
ritm.query();
while (ritm.next()) {
if (!ritm.u_pending_hold_start) {
ritm.u_pending_hold_start = ritm.sys_updated_on;
ritm.update();
continue;
}
var start = new GlideDateTime(ritm.u_pending_hold_start);
var duration = schedule.duration(start, now);
var elapsedMs = duration.getNumericValue();
if (elapsedMs >= tenBusinessDaysMs) {
rejectPendingApprovals(ritm);
ritm.state = '4'; // Closed Incomplete / Cancelled as per your instance
ritm.comments = 'RITM auto-closed because no action was taken within 10 business days.';
ritm.u_auto_close_processed = true;
ritm.update();
closeCatalogTasks(ritm.sys_id);
closeParentRequest(ritm.request);
gs.eventQueue('autocancel.requestItem', ritm, '', '');
continue;
}
if (isReminderAlreadySentToday(ritm, now)) {
continue;
}
gs.eventQueue('autocancel.ritm.reminder', ritm, '', '');
ritm.u_last_reminder_sent = now;
ritm.u_reminder_count = parseInt(ritm.u_reminder_count || 0, 10) + 1;
ritm.update();
}
}
function isReminderAlreadySentToday(gr, now) {
if (!gr.u_last_reminder_sent) {
return false;
}
var lastReminder = new GlideDateTime(gr.u_last_reminder_sent);
return lastReminder.getLocalDate().toString() == now.getLocalDate().toString();
}
function rejectPendingApprovals(ritm) {
var appr = new GlideRecord('sysapproval_approver');
appr.addQuery('sysapproval', ritm.sys_id);
appr.addQuery('state', 'requested');
appr.query();
while (appr.next()) {
appr.state = 'rejected';
appr.comments = 'Approval rejected because the RITM was auto-closed after 10 business days.';
appr.update();
}
}
function closeCatalogTasks(ritmSysId) {
var task = new GlideRecord('sc_task');
task.addQuery('request_item', ritmSysId);
task.addQuery('active', true);
task.query();
while (task.next()) {
task.state = '4';
task.update();
}
}
function closeParentRequest(requestSysId) {
if (!requestSysId) {
return;
}
var req = new GlideRecord('sc_request');
if (req.get(requestSysId)) {
req.state = '4';
req.update();
}
}
})();*************************************************************************************************************************************
If this response helps, please mark it as Accept as Solution and Helpful.
Doing so helps others in the community and encourages me to keep contributing.
Regards
Vaishali Singh
Servicenow Developer
Linkedin - https://www.linkedin.com/in/vaishali-singh-2273361bb