Find your people. Pick a challenge. Ship something real. The CreatorCon Hackathon is coming to the Community Pavilion for one epic night. Every skill level, every role welcome. Join us on May 5th and learn more here.

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.