Maximo Open Forum

 View Only
  • 1.  questions on using Email Listener

    Posted 11-30-2021 09:30
    I'm in the midst of creating training documents & videos for my company, and I've come to Email Listener -- which we've never used, and based on this article from IBM it seems like getting it to work would at least be challenging for us since we use MS 365.  Just a couple of simple questions for anyone out there who has successfully set this up, because between the System Help files and IBM Training Academy videos, I seem to be getting conflicting information:

    1) On an incoming unformatted email, for Maximo to properly identify it as an update to an existing ticket (SR) instead of creating a new ticket, does the Object Key Identifier have to surround the key value, or just be in front of it?  System Help says surround (example given is ##1009##), IBM Academy training video says just in front (example given is ##:ticketid).
    2) On that same email, does the Object Key Identifier and key​ value HAVE TO be in the Subject line, or can it be in the body?​
    #Administration

    ------------------------------
    Travis Herron
    Pensacola Christian College
    ------------------------------


  • 2.  RE: questions on using Email Listener

    Maximo Certified
    Posted 12-01-2021 08:44
    2) On that same email, does the Object Key Identifier and key​ value HAVE TO be in the Subject line, or can it be in the body?​

    It should be in the subject in order to check and take decision via IBEP workflow to create new ticket or add the email as log.

    ------------------------------
    Fouad Jaara
    BTAM
    ------------------------------



  • 3.  RE: questions on using Email Listener

    Posted 12-01-2021 09:11
    The object key delimiters must SURROUND the primary key, thus ##keyvaluehere## ... out-of-the-box, the key value must exist in the SUBJECT of the mail item.

    We have found ourselves significantly disappointed with the limitations of the listener; it is VERY picky about what it will accept and process, and we have identified at least three business cases that the listener cannot address:
    1) Adding communications to closed tickets--one would think that this would be acceptable and useful, but if a ticket is closed, the listener rejects it
    2) Processing e-mails from "external parties"--we are a cooperative and so our service desk occasionally handles requests from staff at our 28 member owners...we simply CANNOT be the HR department for 28 independent companies, but unless EVERY incoming e-mail has a corresponding person record in Maximo, the listener will reject
    3) Adding communications to WORK ORDERS ... I cannot fathom why IBM doesn't enable this, but the out-of-the-box functionality is such that either the object key matches to a ticket (happy path) or it doesn't and the mail item is rejected (sad path)

    Because of this, we ​have supplemented the listener with several automation scripts to address the business cases above.  Obviously these scripts encapsulate our specific requirements, but the overall idea may be useful.

    INBOUNDCOMM.SAVE

    from psdi.server import MXServer;
    from psdi.mbo import MboConstants;
    from java.util import HashMap;

    ## get context
    mailid = mbo.getString("MAILID");
    obj_key = mbo.getString("OBJECTKEY");
    object_name = mbo.getString("OBJECTNAME");
    status = mbo.getString("STATUS");
    subject = mbo.getString("SUBJECT");
    message = mbo.getString("MSGBODY");
    sender = mbo.getString("CHANGEBY");
    vError = mbo.getString("ERROR");

    ## flags
    found = False;
    record_exists = False;
    record_closed = False;
    create_comm = False;
    create_record = False;
    proceed = True if mailid == "" else False;

    ## correct situations where the OBJECTKEY wasn't populated due to poor coding
    if (obj_key == ""):
        s = subject.find("##");
        e = subject.find("##", s + 2);
    if (s != -1 and e != -1): ## double delimiters exist...
        obj_key = subject[s+2:e];
        mbo.setValue("OBJECTKEY", obj_key, 11L);

    if (not mbo.isNull("OBJECTKEY")):
        tkt_set = mbo.getMboSet("$TKT", "TICKET", "ticketid = '" + obj_key + "'");
        tkt = tkt_set.moveFirst();
        wo_set = mbo.getMboSet("$WO", "WORKORDER", "wonum = '" + obj_key + "'");
        wo = wo_set.moveFirst();

    if (tkt != None): ## Check ticket table
        object_name = tkt.getString("CLASS");
        mbo.setValue("OBJECTNAME", object_name);
        record_exists = True;
        record_closed = True if tkt.getBoolean("HISTORYFLAG") else False;
        UID = tkt.getLong("TICKETUID");
        varRecord = tkt;
    elif (wo != None):
        object_name = wo.getString("WOCLASS");
        mbo.setValue("OBJECTNAME", object_name);
        record_exists = True;
        record_closed = True if wo.getBoolean("HISTORYFLAG") else False;
        UID = wo.getLong("WORKORDERID");
        varRecord = wo;
    else:
        record_exists = False;

    ## start doing record clean-up
    if (subject == ""):
        subject = "<empty subject>";
        mbo.setValue("SUBJECT", subject, 11L);

    if (sender == "" or sender == "MAXADMIN" or vError.find("Person does not exist") != -1):
        sender == "EXTERNAL";
        mbo.setValue("CHANGEBY", "EXTERNAL", 11L);
        mbo.setValue("PERSONID", "EXTERNAL", 11L);

    if (mailid == "" and status == "NEW" and subject[:13] == "Undeliverable"):
        mbo.setValue("STATUS", "COMP_ERR", 11L);
        mbo.setValue("ERROR", "Processed by GRE script", 11L);
        mbo.setValue("MAILID", mailid + "undeliverable_", 11L);

    ## Truncate SEND TO field
    s = mbo.getString("SENDTO");
    if (len(s) > 255):
        mbo.setValue("SENDTO", s[:250] + "...", 11L);

    ## start taking action against the record
    if (mailid != "" and status == "WORKFLOW" and vError != ""):
        mbo.setValue("STATUS", "COMP_CL", 11L);
        mbo.setValue("ERROR", "Processed by GRE script", 11L);
        proceed = False;
        mbo.setValue("MAILID", mailid + "workflow_", 11L);
       
    if (not record_exists and obj_key != "" and mailid == ""): ## in this case, a bogus object key was fed to the listener
        mbo.setValue("STATUS", "ERROR_GRE", 11L);
        mbo.setValue("ERROR", "Object key " + obj_key + " does not exist in either the TICKET or WORKORDER table. No action.");
        proceed = False;
        mbo.setValue("MAILID", mailid + "bad_key_", 11L);

    if (proceed and record_exists) and (
    (status == "INVALID") or
    (status in ("ERROR", "NEW") and mbo.getString("PERSONID") == "EXTERNAL") or
    (record_closed) or
    (object_name not in ("SR", "PROBLEM"))
    ):
    ## in this case, the record exists in Maximo and we DO want a COMM LOG entry
        create_comm = True;

    if (proceed and not record_exists and ((status == "INVALID") or (status in ("ERROR", "NEW") and mbo.getString("PERSONID") == "EXTERNAL")) and mailid == ""):
        create_record = True;
        create_comm = True;

    #### ACTIONS - CREATE TICKET ####

    if (create_record): ## Create ticket ##
        ctx = HashMap();
        ctx.put("mbo", mbo);
        service.invokeScript("LSTNCRSR", ctx);

    obj_key = mbo.getString("OBJECTKEY");
    varRecord = mbo.getMboSet("GRE_SR").moveFirst();

    #### ACTIONS - COMM LOG ####
    if (create_comm): ## Create communication log entry ##
        if (not record_closed):
            c = varRecord.getMboSet("COMMLOG").add(11L);
        else:
            c = mbo.getMboSet("OPEN_TICKETS").moveFirst().getMboSet("COMMLOG").add(11L);

        src_fields = ["CC", "BCC", "SUBJECT", "CHANGEBY", "CREATEDATE", "SENDFROM", "SENDTO", "REPLYTO", "MSGBODY"];
        dst_fields = ["CC", "BCC", "SUBJECT", "CREATEBY", "CREATEDATE", "SENDFROM", "SENDTO", "REPLYTO", "MESSAGE"];

        c.copyValue(mbo, src_fields, dst_fields, 11L);
        c.setValue("INBOUND", 1, 11L);

        ## Add attachments to newly created comm log entry
        aset = mbo.getMboSet("DOCLINKS");
        xset = c.getMboSet("DOCLINKS");

        a = aset.moveFirst();
        while (a != None):
            x = xset.add();
            fields = ["DOCUMENT", "DOCTYPE", "DOCINFOID"];
            x.copyValue(a, fields, fields, 11L);
            a = aset.moveNext();
        if (vError == ""):
            vError = "COMM LOG updated.";
        else:
            vError = vError + " " + "COMM LOG updated.";

        if (record_closed):
            c.setValue("OWNERID", UID, 11L);
            c.setValue("OWNERTABLE", object_name, 11L);

    #### FINAL CLEAN-UP ####

    if (create_record == 1 or create_comm == 1):
        mbo.setValue("MAILID", mailid + "processed_", 11L);
        mbo.setValue("ERROR", vError, 11L);
        if (create_record == 1):
            mbo.setValue("STATUS", "COMP_NEW", 11L);
        else:
            mbo.setValue("STATUS", "COMP_CL", 11L);

    LSTNCRSR

    from psdi.server import MXServer;
    from psdi.mbo import MboConstants;
    from psdi.mbo import Mbo;
    from psdi.util import HTML
    from java.util import HashMap;

    try:
        set = MXServer.getMXServer().getMboSet("SR", mbo.getUserInfo());
        SR = set.add();

        ## Ticket basic identifiers
        SR.setValue("SITEID", "TRANS", 11L);
        SR.setValue("EXTERNALSYSTEM", "EMAIL", 11L);
        SR.setValue("SOURCE", "EMAIL", 11L);

        ## E-mail fields
        SR.setValue("DESCRIPTION", mbo.getString("SUBJECT"));

        ## Process e-mail body to remove excess line breaks
        TEXT = mbo.getString("MSGBODY");
        SR.setValue("DESCRIPTION_LONGDESCRIPTION", TEXT);

        ## Process e-mail body to remove excess line breaks
        ctx_sr = HashMap();
        ctx_sr.put("mbo", SR);
        service.invokeScript("CLEANTXT", ctx_sr);

        ## Update other e-mail sourced fields and ticket fields
        SR.setValue("REPORTDATE", mbo.getDate("RECEIVEDATE"));
        SR.setValue("REPORTEDBY", mbo.getString("PERSONID"));
        SR.setValue("DEPT_GRE", "IT");
        SR.setValue("COMMSEND_GRE", True);
        SR.setValueNull("OWNER");

        priority = 2 if mbo.getString("PRIORITY")[:1] == 1 else 4;
        SR.setValue("URGENCY", priority);

        ## Flag the inbound comm row
        mbo.setValue("OBJECTKEY", SR.getString("TICKETID"), 11L);
        mbo.setValue("OBJECTNAME", "SR", 11L);
        mbo.setValue("ACTION", "CREATE", 11L);
        mbo.setValue("STATUS", "COMPLETE", 11L);

        ## Determine what person group should be assigned ##
        vEmail=mbo.getString("emailaddress");
        if (vEmail in ("max2252", "qmaxit")):
            SR.setValue("PERSONGROUP_GRE","2252");
        if (vEmail=="max1179"):
            SR.setValue("PERSONGROUP_GRE","1179");
            SR.setValue("URGENCY", 4, 11L);
            SR.setValue("COMMODITYGROUP", "GRE", 7L);
            SR.setValue("ASSETNUM", "THREAT", 7L);
            SR.setValue("TKTTYPE_GRE", "INC", 11L);
        if (vEmail=="maxnoc"):
            SR.setValue("PERSONGROUP_GRE","5662");

        ## Process self-assignments #ME tag ##
        vD = mbo.getString("SUBJECT");
        vDu = vD.upper();
        l = len(vD);
        x = vDu.find("#ME");
        if (x != -1):
            ## Clear the PERSONGROUP_GRE field
            SR.setValue("PERSONGROUP_GRE","");
            ## Determine whether the REPORTEDBY individual belongs to a QUEUE group
            try:
                clause="persongroup in (select persongroup from persongroupteam where '" + mbo.getString("PERSONID") + "' = respparty) and type='QUEUE' "
                persongset = MXServer.getMXServer().getMboSet("PERSONGROUP", mbo.getUserInfo());
                persongset.setWhere(clause);
                g = persongset.moveFirst()
                if (g != None): ## As long as the person belongs to AT LEAST 1 QUEUE group, we can proceed with ownership assignment
                    SR.setValue("OWNER", mbo.getString("PERSONID"));
                if (persongset.count()==1): ## If the person belongs to EXACTLY 1 QUEUE group, we can also assign their group for them
                    SR.setValue("PERSONGROUP_GRE", g.getString("PERSONGROUP"));
                if (g == None): ## If the person belongs to NO QUEUE groups (probably due to drag-and-drop), assign to '2252'
                    SR.setValue("PERSONGROUP_GRE", "2252");
            finally:
                persongset.close();

            ## Remove tag from the DESCRIPTION field
            SR.setValue("DESCRIPTION", SR.getString("DESCRIPTION").replace("#ME", ""));
            set.save();
    finally:
        set.cleanup();
        set.close();

    We have actually done additional things like processing approvals via the inbound communication process--overall, we have been happy with the results of these scripts.

    Jade

    ------------------------------
    Jade Warren
    Great River Energy
    ------------------------------



  • 4.  RE: questions on using Email Listener

    Posted 12-01-2021 10:49
    That is fantastic-looking stuff there!

    Re: adding comments to Closed tickets -- that's pretty standard Maximo, can't edit something that's closed; I'm not surprised that that's not allowed.  One specific question for you Jade:  In this portion of the Script:
    #### ACTIONS - COMM LOG ####
    if (create_comm): ## Create communication log entry ##
        if (not record_closed):
            c = varRecord.getMboSet("COMMLOG").add(11L);
        else:
            c = mbo.getMboSet("OPEN_TICKETS").moveFirst().getMboSet("COMMLOG").add(11L);​

    Am I reading this right, that if the record is in Closed status, it's going to add the CommLog entry to the first open Ticket it can find?



    Re: email from external parties -- at least it's documented!  For my situation, I've currently got thousands of college students, more may come or go at any semester change, and I don't have Maximo integrated to any directory system that'll keep this updated for me.  Not sure that I'd want to take on managing it manually or semi-manually (e.g. data dump 2 or 3 times per year).  But it looks like you've got a way around that in your script. . .interesting!

    Re: can't add Communications to Work Orders -- now that's an interesting issue.  I've got to assume it's mostly about licensing.  So help me play this out (everyone):  If I had a technician in the field with a Work Order, and he needs to communicate/collaborate with the "customer" and I provide a Comm Template for this.  That Comm Template is going to need the Object Key Identifier and TicketID, so when the customer responds, the Ticket (SR) would be updated with their reply.  I assume getting the TicketID in the Comm Template can be done via relationship dotted notation, and that the correspondence from both technician and customer would be visible on both the Work Order and SR.  However, what if there are many SR's mapped (related) to one Work Order. . .like two roommates turn in a request for the same issue in their room, or two teachers turn in the same request for a classroom?  Which "customer" is going to be collaborated with?  It seems to me that at that point, the communications need to happen on the Work Order -- but the customer likely isn't authorized/licensed to make updates to Work Orders.  How does this work in real life?

    ------------------------------
    Travis Herron
    Pensacola Christian College
    ------------------------------