Varying the frequency of IMAP connections (email node)

I'm using the email node to check for the presence of an email in a specified IMAP folder.

Ideally I'd like to be able to vary the frequency of connecting to IMAP. Am I right in saying it's not possible with the default email node?

Is there another node that might be suitable?

The reason is that I want to check every 5 seconds when my Node-RED alarm system is armed (via a dashboard button) but when it's not, check every 5 minutes.

right now no - but there is this open issue to create a version that does it...
if only someone had the time to create the Pul Request.

IMAP push would be a nice enhancement as well! Then you wouldn't need to poll if you had a suitable IMAP server.

Be my guest !

1 Like

Have pushed a new version of email node to npm that can optionally allow manual fetch of messages (and fixes a bug of duplicate messages when connection is lost).

Haha, been a long time since I looked at doing an IMAP client - did one in PHP a decade or so back when I was contracting for NHS NPfIT and we needed to write an emergency national registration system because BT had taken a year to develop one that didn't work! Wasn't my job but it was the quickest way to get something working. For some reason the standard PHP client library didn't work properly.

I'll add it to the backlog!

I tried my best to implement IDLE (snippet from 61-email.js), see Node-red-node-email ignoring IMAP IDLE · Issue #1108 · node-red/node-red-nodes · GitHub.

        //
        // checkIMAP
        //
        // Check the email sever using the IMAP protocol for new messages.
        var s = false; // Connection established
        var ss = false; // Server announced ready
        function checkIMAP(msg,send,done) {
            let doneCalled = false;
            function safeDone(err) {
                if (done && !doneCalled) {
                    doneCalled = true;
                    done(err);
                }
            }
            var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
            var saslxoauth2 = "";
            node.lastMsg = msg;
            if (!s) {
                if (node.authtype === "XOAUTH2") {
                    var value = RED.util.getMessageProperty(msg,node.token);
                    if (value !== undefined) {
                        if (node.saslformat) {
                            //Make base64 string for access - compatible with outlook365 and gmail
                            saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
                        } else {
                            saslxoauth2 = value;
                        }
                }
                imap = new Imap({
                    xoauth2: saslxoauth2,
                    host: node.inserver,
                    port: node.inport,
                    tls: node.useSSL,
                    autotls: node.autotls,
                    tlsOptions: { rejectUnauthorized: false },
                    connTimeout: tout,
                    authTimeout: tout
                });
            } else {
                imap = new Imap({
                    user: node.userid,
                    password: node.password,
                    host: node.inserver,
                    port: node.inport,
                    tls: node.useSSL,
                    autotls: node.autotls,
                    tlsOptions: { rejectUnauthorized: false },
                    connTimeout: tout,
                    authTimeout: tout
                });
            }
            // Server notified about new mails during IDLE
            imap.on('mail', function(numNew) {
                node.status({fill:"yellow", shape:"dot", text:"Server notified " + numNew + " new mail(s)..."});
                fetchIMAP(); // new mail -> catch immediatly
            });
            // Server notified about new attributes on (e.g. -> UNSEEN) during IDLE 
            imap.on('update', function() {
                node.status({fill:"yellow", shape:"dot", text:"Server notified updated attributes..."});
                fetchIMAP();
            });
            imap.on('error', function(err) {
                if (err.errno !== "ECONNRESET") {
                    s = false;
                    node.error(err.message,err);
                    node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
                }
	            safeDone();
                setInputRepeatTimeout();
            });
            //console.log("Checking IMAP for new messages");
            // We get back a 'ready' event once we have connected to imap
            s = true;
            imap.once("ready", function() {
                if (ss === true) { return; }
                ss = true;
                node.status({fill:"blue", shape:"dot", text:"email.status.fetching"});
                //console.log("> ready");
                // Open the folder
                imap.openBox(node.box, // Mailbox name
                    false, // Open readonly?
                    function(err, box) {
                    //console.log("> Inbox err : %j", err);
                    //console.log("> Inbox open: %j", box);
                        if (err) {
                            var boxs = [];
                            imap.getBoxes(function(err,boxes) {
                                if (err) { return; }
                                for (var prop in boxes) {
                                    if (boxes.hasOwnProperty(prop)) {
                                        if (boxes[prop].children) {
                                            boxs.push(prop+"/{"+Object.keys(boxes[prop].children)+'}');
                                        }
                                        else { boxs.push(prop); }
                                    }
                                }
                                node.error(RED._("email.errors.fetchfail", {folder:node.box+".  Folders - "+boxs.join(', ')}),err);
                            });
                            node.status({fill:"red", shape:"ring", text:"email.status.foldererror"});
                            imap.end();
                            s = ss = false;
                            setInputRepeatTimeout();
                            safeDone(err);
                            return;
                        }
                        else {
                            fetchIMAP();
                        }
                    }); // End of imap->openInbox
                }); // End of imap->ready
            }
            function fetchIMAP() {
                var msg = RED.util.cloneMessage(node.lastMsg);
                var criteria = ((node.criteria === '_msg_')?
                    (msg.criteria || ["UNSEEN"]):
                    ([node.criteria]));
                if (Array.isArray(criteria)) {
                    try {
                        imap.search(criteria, function(err, results) {
                            if (err) {
                                node.status({fill:"red", shape:"ring", text:"email.status.foldererror"});
                                node.error(RED._("email.errors.fetchfail", {folder:node.box}),err);
                                imap.end();
                                s = false;
                                setInputRepeatTimeout();
                                safeDone(err);
                                return;
                            }
                            else {
                            //console.log("> search - err=%j, results=%j", err, results);
                                if (results.length === 0) {
                                //console.log(" [X] - Nothing to fetch");
                                    node.status({results:0});
                                    if (imap && imap.serverSupports && imap.serverSupports('IDLE')) {
                                        node.status({ fill:"grey", shape:"ring", text:"Received 0. -> IDLE" });
                                    } else {
                                        imap.end();
                                        s = false;
                                        setInputRepeatTimeout();
                                    }
                                    msg.payload = 0;
                                    safeDone();
                                    return;
                                }

                                var marks = false;
                                if (node.disposition === "Read") { marks = true; }
                                // We have the search results that contain the list of unseen messages and can now fetch those messages.
                                var fetch = imap.fetch(results, {
                                    bodies: '',
                                    struct: true,
                                    markSeen: marks
                                });

                                // For each fetched message returned ...
                                fetch.on('message', function(imapMessage, seqno) {
                                //node.log(RED._("email.status.message",{number:seqno}));
                                //console.log("> Fetch message - msg=%j, seqno=%d", imapMessage, seqno);
                                    imapMessage.on('body', function(stream, info) {
                                           //console.log("> message - body - stream=?, info=%j", info);
                                        simpleParser(stream, {checksumAlgo: 'sha256'}, function(err, parsed) {
                                            if (err) {
                                                node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
                                                node.error(RED._("email.errors.parsefail", {folder:node.box}),err);
                                            }
                                            else {
                                                processNewMessage(msg, parsed);
                                            }
                                        });
                                    }); // End of msg->body
                                }); // End of fetch->message

                                // When we have fetched all the messages
                                fetch.on('end', function() {
                                    node.status({results:results.length});
                                    var cleanup = function() {
                                    if (imap && imap.serverSupports && imap.serverSupports('IDLE')) {
                                            node.status({ fill:"grey", shape:"ring", text:"Received " + results.length + ". -> IDLE" });
                                        } else {
                                            imap.end();
                                            s = false;
                                            setInputRepeatTimeout();
                                        }
                                        msg.payload = results.length;
                                        safeDone();
                                    };
                                    if (node.disposition === "Delete") {
                                        imap.addFlags(results, '\\Deleted', imap.expunge(cleanup) );
                                    } else if (node.disposition === "Read") {
                                        imap.addFlags(results, '\\Seen', cleanup);
                                    } else {
                                        cleanup();
                                    }
                                });

                                fetch.once('error', function(err) {
                                    console.log('Fetch error: ' + err);
                                    imap.end();
                                    s = false;
                                    setInputRepeatTimeout();
                                    safeDone();
                                });
                            }
                        }); // End of imap->search
                    }
                    catch(e) {
                        node.status({fill:"red", shape:"ring", text:"email.status.bad_criteria"});
                        node.error(e.toString(),e);
                        s = ss = false;
                        imap.end();
                        safeDone(e);
                        return;
                    }
                }
                else {
                    node.status({fill:"red", shape:"ring", text:"email.status.bad_criteria"});
                    node.error(RED._("email.errors.bad_criteria"),msg);
                    s = ss = false;
                    imap.end();
                    safeDone(new Error("bad criteria"));
                    return;
                }

            }
            node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"});
            imap.connect();
        } // End of checkIMAP

The underlying imap library just expects the connection to stay open.