QR code decoder

KAkanksha15
Tera Contributor

I have a requirement that when an inbound email is received, if a QR code is associated with the received email , it should be decoded and the extracted data should be stored in a custom ServiceNow table.

1 REPLY 1

Naveen20
ServiceNow Employee

 

Solution - There are three main pieces to wire together:

1. Custom Table to store decoded QR data 2. Inbound Email Action to intercept emails with image attachments 3. QR Decode Logic — this is the tricky part, since ServiceNow has no native QR decoder

For the decoding, you have two practical paths:

Option A — Outbound REST to an external decoder API (most reliable, works in scoped apps) Option B — Java Packages.* with ZXing (global scope only, and ZXing isn't bundled by default, so you'd need a MID Server or custom JAR — generally not recommended)

I'd recommend Option A. Here's the full implementation:


Step 1 — Custom Table

Create a table, e.g. u_qr_code_data, with fields like:

Field Type Purpose
u_source_email String Sender email address
u_email_subject String Subject of the inbound email
u_decoded_data String (4000) The extracted QR content
u_attachment_name String Original image filename
u_processed_on Date/Time When the QR was decoded
u_sys_email Reference → sys_email Link back to the email record

Step 2 — Outbound REST Message

Create a REST Message (e.g., QR_Decode_API) pointing to your chosen external decoder. A common free option is something like api.qrserver.com or a custom Azure Function / AWS Lambda you control.

Example using api.qrserver.com:

Or if you have your own endpoint, configure accordingly. Set up an HTTP Method called decode on the REST Message.


Step 3 — Script Include (QR Decoder Helper)

var QRCodeDecoder = Class.create();
QRCodeDecoder.prototype = {
    initialize: function() {},

    /**
     * Decodes a QR code from an attachment sys_id
     * @param {string} attachmentSysId - sys_id of the image attachment
     * @returns {string|null} decoded text or null on failure
     */
    decode: function(attachmentSysId) {
        var gsa = new GlideSysAttachment();
        var bytes = gsa.getBytes(attachmentSysId, '');

        // Base64-encode the image bytes for the API call
        var base64Image = GlideStringUtil.base64Encode(bytes);

        // Use REST Message or direct GlideHTTPRequest
        var request = new sn_ws.RESTMessageV2('QR_Decode_API', 'decode');
        // If your API accepts base64 in the body:
        request.setRequestHeader('Content-Type', 'application/json');
        request.setRequestBody(JSON.stringify({
            image: base64Image
        }));

        var response = request.execute();
        var httpStatus = response.getStatusCode();
        var body = response.getBody();

        if (httpStatus == 200) {
            var result = JSON.parse(body);
            // Structure depends on your API — adapt accordingly
            // For api.qrserver.com, it returns: [{"symbol":[{"data":"...","error":null}]}]
            if (result && result[0] && result[0].symbol && result[0].symbol[0].data) {
                return result[0].symbol[0].data;
            }
        }

        gs.error('QRCodeDecoder: Failed to decode. HTTP ' + httpStatus + ' | Body: ' + body);
        return null;
    },

    type: 'QRCodeDecoder'
};

Note: If your API accepts multipart/form-data instead of base64 JSON, you'll need to adjust the request construction. Some APIs also accept a direct image URL — in that case you could generate a temporary URL to the attachment instead.


Step 4 — Inbound Email Action

Create an Inbound Email Action:

  • Name: QR Code Processor
  • Target table: Can be sys_email or any relevant table
  • Condition / When: Set a filter if needed (e.g., specific sender, subject pattern), or leave open to process all

Script:

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

    var emailSysId = current.sys_id + '';
    var sender     = email.from + '';
    var subject    = email.subject + '';

    // Query attachments on this email record (images only)
    var attGR = new GlideRecord('sys_attachment');
    attGR.addQuery('table_name', 'sys_email');
    attGR.addQuery('table_sys_id', emailSysId);
    attGR.addQuery('content_type', 'STARTSWITH', 'image/');
    attGR.query();

    var decoder = new QRCodeDecoder();

    while (attGR.next()) {
        var attachmentSysId = attGR.sys_id + '';
        var fileName = attGR.file_name + '';

        gs.info('QR Processor: Attempting decode on attachment: ' + fileName);

        var decodedData = decoder.decode(attachmentSysId);

        if (decodedData) {
            // Insert into custom table
            var qrRecord = new GlideRecord('u_qr_code_data');
            qrRecord.initialize();
            qrRecord.u_source_email    = sender;
            qrRecord.u_email_subject   = subject;
            qrRecord.u_decoded_data    = decodedData;
            qrRecord.u_attachment_name = fileName;
            qrRecord.u_processed_on    = new GlideDateTime();
            qrRecord.u_sys_email       = emailSysId;
            qrRecord.insert();

            gs.info('QR Processor: Decoded and stored. Data: ' + decodedData);
        } else {
            gs.warn('QR Processor: No QR data found in attachment: ' + fileName);
        }
    }

})(current, event, email, logger);

Key Considerations

Security: If the decoded QR data could contain URLs or scripts, sanitize it before storing. Consider adding an u_data_type field (URL, text, vCard, etc.) and validating accordingly.

Performance: Outbound REST calls from an Inbound Email Action are synchronous. If you expect high volume, consider having the email action just flag the record and use a Scheduled Job or Flow Designer async flow to process the queue — this avoids blocking the email processing pipeline.

Scoped App: If this is in a scoped app, make sure the REST Message is in the same scope and that you have the sn_ws.RESTMessageV2 API available. The GlideSysAttachment.getBytes() approach works in scoped apps.

Error Handling: You may also want a u_status field (Pending / Success / Failed) and an u_error_message field on your custom table for traceability.