Server-side
// Dead Letter Queue Script Include
var IntegrationDLQ = Class.create();
IntegrationDLQ.prototype = {
initialize: function() {
this.MAX_RETRIES = 5;
this.INITIAL_DELAY = 300; // 5 minutes in seconds
},
// Store failed message in DLQ
queueFailedMessage: function(integrationName, payload, errorMessage, originalHeaders) {
var dlqRecord = new GlideRecord('u_integration_dlq');
dlqRecord.initialize();
dlqRecord.setValue('u_integration_name', integrationName);
dlqRecord.setValue('u_payload', JSON.stringify(payload));
dlqRecord.setValue('u_error_message', errorMessage);
dlqRecord.setValue('u_headers', JSON.stringify(originalHeaders || {}));
dlqRecord.setValue('u_retry_count', 0);
dlqRecord.setValue('u_next_retry', this._calculateNextRetry(0));
dlqRecord.setValue('u_status', 'queued');
var sysId = dlqRecord.insert();
gs.info('Message queued in DLQ: ' + sysId + ' for integration: ' + integrationName);
return sysId;
},
// Process messages ready for retry
processRetryQueue: function() {
var gr = new GlideRecord('u_integration_dlq');
gr.addQuery('u_status', 'queued');
gr.addQuery('u_retry_count', '<', this.MAX_RETRIES);
gr.addQuery('u_next_retry', '<=', gs.nowDateTime());
gr.query();
var processed = 0;
while (gr.next() && processed < 50) { // Limit batch size
this._retryMessage(gr);
processed++;
}
this._alertOnMaxRetries();
return processed;
},
// Retry individual message
_retryMessage: function(dlqRecord) {
try {
var integrationName = dlqRecord.getValue('u_integration_name');
var payload = JSON.parse(dlqRecord.getValue('u_payload'));
var headers = JSON.parse(dlqRecord.getValue('u_headers') || '{}');
var success = this._sendMessage(integrationName, payload, headers);
if (success) {
dlqRecord.setValue('u_status', 'processed');
dlqRecord.setValue('u_processed_on', gs.nowDateTime());
gs.info('DLQ message processed successfully: ' + dlqRecord.getUniqueValue());
} else {
this._incrementRetry(dlqRecord);
}
dlqRecord.update();
} catch (e) {
gs.error('Error processing DLQ message: ' + e.message);
this._incrementRetry(dlqRecord);
dlqRecord.update();
}
},
// Send message to target system
_sendMessage: function(integrationName, payload, headers) {
var rm = new RESTMessageV2();
rm.setHttpMethod('POST');
// Configure endpoint based on integration name
var endpoint = this._getEndpointForIntegration(integrationName);
rm.setEndpoint(endpoint);
// Set headers
for (var header in headers) {
rm.setRequestHeader(header, headers[header]);
}
rm.setRequestBody(JSON.stringify(payload));
var response = rm.execute();
var statusCode = response.getStatusCode();
if (statusCode >= 200 && statusCode < 300) {
return true;
} else {
gs.warn('Integration retry failed with status: ' + statusCode + ', body: ' + response.getBody());
return false;
}
},
// Increment retry count with exponential backoff
_incrementRetry: function(dlqRecord) {
var retryCount = parseInt(dlqRecord.getValue('u_retry_count')) + 1;
dlqRecord.setValue('u_retry_count', retryCount);
if (retryCount >= this.MAX_RETRIES) {
dlqRecord.setValue('u_status', 'failed');
dlqRecord.setValue('u_failed_on', gs.nowDateTime());
} else {
dlqRecord.setValue('u_next_retry', this._calculateNextRetry(retryCount));
}
},
// Calculate next retry time with exponential backoff
_calculateNextRetry: function(retryCount) {
var delaySeconds = this.INITIAL_DELAY * Math.pow(2, retryCount);
var nextRetry = new GlideDateTime();
nextRetry.addSeconds(delaySeconds);
return nextRetry;
},
// Get endpoint configuration
_getEndpointForIntegration: function(integrationName) {
// This would typically look up configuration from a system property or table
return gs.getProperty('integration.' + integrationName + '.endpoint', 'https://default.endpoint.com/api');
},
// Alert on messages that have exceeded max retries
_alertOnMaxRetries: function() {
var gr = new GlideRecord('u_integration_dlq');
gr.addQuery('u_status', 'failed');
gr.addQuery('u_alerted', false);
gr.query();
if (gr.getRowCount() > 0) {
var event = new GlideRecord('sysevent');
event.initialize();
event.setValue('name', 'integration.dlq.max_retries_exceeded');
event.setValue('parm1', gr.getRowCount());
event.insert();
// Mark as alerted
while (gr.next()) {
gr.setValue('u_alerted', true);
gr.update();
}
}
},
// Manual reprocess method for UI actions
manualReprocess: function(dlqSysId) {
var dlqRecord = new GlideRecord('u_integration_dlq');
if (dlqRecord.get(dlqSysId)) {
dlqRecord.setValue('u_retry_count', 0);
dlqRecord.setValue('u_status', 'queued');
dlqRecord.setValue('u_next_retry', gs.nowDateTime());
dlqRecord.setValue('u_alerted', false);
dlqRecord.update();
return true;
}
return false;
},
type: 'IntegrationDLQ'
};
Client-side
// UI Action client script for manual reprocessing
function reprocessDLQMessage() {
if (!confirm('Are you sure you want to reprocess this failed integration message?')) {
return;
}
var ga = new GlideAjax('IntegrationDLQ');
ga.addParam('sysparm_name', 'manualReprocess');
ga.addParam('sysparm_dlq_sys_id', g_form.getUniqueValue());
ga.getXML(function(response) {
var answer = response.responseXML.documentElement.getAttribute('answer');
if (answer === 'true') {
g_form.addInfoMessage('Message queued for reprocessing');
g_form.reload();
} else {
g_form.addErrorMessage('Failed to queue message for reprocessing');
}
});
}
The code implements a complete DLQ system with a custom table to store failed messages, exponential backoff retry logic, and batch processing limits to prevent system overload. Key features include status tracking, automatic alerting when messages exceed maximum retries, and manual reprocessing capabilities through UI actions.