@compeso/node-red-contrib-imap-email 1.0.0

Node-RED IMAP email nodes with externally triggered bounded cursor-window fetch, at-least-once ACK actions, diagnostics, timings, and comprehensive help documentation.

npm install @compeso/node-red-contrib-imap-email

@compeso/node-red-contrib-imap-email

Node-RED nodes for externally triggered IMAP email processing with bounded cursor-window fetch and at-least-once ACK handling.

Nodes

The package registers these Node-RED types:

Flow type           Palette label        Purpose
imap-email account  imap email account   shared IMAP account configuration
imap-email in       imap email in        externally triggered bounded cursor-window fetch
imap-email ack      imap email ack       batched acknowledgement and UID actions

Requirements

  • Node.js >=22.0.0
  • Node-RED >=4.0.0

Node-RED 5.x can require a stricter Node.js patch level than this package declares. Check the Node-RED runtime requirement when upgrading Node-RED itself.

Installation

From the npm registry:

cd ~/.node-red
npm install @compeso/node-red-contrib-imap-email

From GitHub during development:

cd ~/.node-red
npm install github:Harpau/node-red-contrib-imap-email

From a local checkout:

cd /path/to/node-red-contrib-imap-email
npm install
npm link

cd ~/.node-red
npm link @compeso/node-red-contrib-imap-email

Restart Node-RED after installation.

Example Flow

Import examples/basic-at-least-once-flow.json in Node-RED, open the imap email account config node, and enter your IMAP username and password. The example tab is disabled by default, the Inject node does not run automatically, and the ACK path only marks messages as seen. The visible palette labels use spaces; the stored Flow-JSON types use the imap-email ... prefix.

Minimal flow:

Inject / scheduler / HTTP trigger
  -> imap-email in
      -> your successful processing
          -> imap-email ack

Only wire messages to imap-email ack after all processing that must succeed has actually succeeded.

imap-email ack can be configured multiple times in one flow. Typical modes:

delete       delete the mail by UID and complete it; requires IMAP UIDPLUS
move         optionally update flags, move the mail and complete it; requires native IMAP MOVE
copy         copy the mail to a target folder, then optionally update source flags
flag         set or clear flags, keep the mail and complete it
set by msg.imap.ackAction
             read action, target folder and flags from the message

The ack node can set or clear \Seen, \Answered, \Flagged, other IMAP system flags such as \Draft, and custom keywords such as $Processed in flag, move and copy mode. With move, flags are updated on the source message before it is moved. With copy, the message is copied first and the configured ACK flag changes are then applied only to the source message. They are not applied to the target copy, though the IMAP server may copy flags that already existed on the source message. Use delete to delete mail; setting \Deleted as a raw flag is an advanced flag operation and does not replace the delete action.

Large Mailboxes

imap-email in is designed for mailboxes that may contain many messages. It does not run an unbounded mailbox-wide search. Instead, each trigger reads one or more bounded windows and emits at most the configured batch size. Each window is streamed and discarded before another window is read; only selected candidate UIDs up to the remaining batch/inflight capacity are kept in memory.

Important settings:

Batch size       maximum messages emitted per trigger
Front window     maximum messages inspected per bounded window
Max inflight     maximum emitted but not-yet-ACKed messages tracked in memory
Retry after ms   time after which an un-ACKed message may be emitted again
Scan time ms     initial cursor-window soft time budget; 0 means exactly one cursor window
UIDs/command     maximum UID count per IMAP command chunk
Max bytes        maximum RFC822 bytes per message, 0 means unlimited
Chunk bytes      streamed IMAP download chunk size

Selection settings:

Deleted   Any | Only with flag | Only without flag
Seen      Any | Only with flag | Only without flag
Answered  Any | Only with flag | Only without flag
Flagged   Any | Only with flag | Only without flag

The defaults are Deleted = Only without flag and all other flags set to Any. These filters are applied only inside bounded windows. A selective filter may emit fewer messages than Batch size; it never causes a full-mailbox scan to fill the batch. When the cursor reaches the end of the mailbox, it wraps back to the first sequence number. The cursor is volatile and resets when Node-RED restarts or when IMAP UIDVALIDITY changes.

The node always uses an adaptive scan strategy. After restart or UIDVALIDITY reset it starts in the cursor-window phase with full Front window sized sequence windows. Empty windows are discarded and the node updates its status after every read window. The phase stops when the batch/capacity is filled, the mailbox end is reached, or Scan time ms expires; 0 means only one cursor window. If a window contains more selectable messages than the remaining batch/capacity can hold, the scan cursor is kept on that window so a later trigger can continue draining it instead of leaving messages behind. This is especially useful when the ACK node deletes or moves processed mails: after those mails leave the mailbox, remaining messages shift into the held sequence window and can be drained without being skipped. An empty mailbox with a valid UIDNEXT is treated as already at the mailbox end.

Once the mailbox end has been reached without candidate overflow, the node records the current UIDNEXT. Later triggers enter the new-uid-priority phase: they first read newly arrived UIDs up to a per-trigger UIDNEXT snapshot, then read one cyclic backlog window if capacity remains. In this phase the new-UID and backlog windows each use about half of Front window, with the larger half assigned to new UIDs. The logical new-UID window is still bounded by Front window, and its UID fetches are additionally split into commands of at most UIDs/command UIDs. New UIDs are emitted before backlog messages and in ascending UID order, but the node does not guarantee globally oldest unread delivery across the whole mailbox. Backlog windows are also held on candidate overflow. If the new-UID window already covers all messages that currently exist in the mailbox, the redundant backlog window is skipped. New-UID windows advance to the first UID that did not fit into the current batch, or to the next UID after the last actually read command chunk.

Stats report phase as the current operating phase (cursor-window or new-uid-priority). windowPhasesRead contains the ordered unique window types read during the trigger (cursor, new-uid, backlog); the last entry is the last window type read.

Output messages include the server flags as an array:

msg.imap.flags // for example ["\\Seen", "\\Flagged"]
msg.imap.flagState // { deleted: false, seen: true, answered: false, flagged: true }

Parsed mail headers are emitted in msg.email.header as a JSON-serializable object without an Object prototype. Use Object.hasOwn(msg.email.header, key) instead of msg.email.header.hasOwnProperty(key) when checking header presence. Headers with prototype-sensitive names such as __proto__, constructor or prototype are preserved under neutralized names.

Message bodies are downloaded as streams after the bounded front window has selected candidate UIDs. Attachments are drained without buffering unless Attachments is enabled. Raw source intentionally buffers the full RFC822 message in msg.raw; keep it disabled for very large messages. Set Max bytes to a positive value to reject oversized messages on output 2 with msg.imap.ackToken instead of parsing them.

Delivery Semantics

The package provides at-least-once delivery.

ACK action succeeded = successfully processed and ACKed
ACK action failed    = not successfully ACKed
Duplicate delivery   = possible
Exactly once         = not guaranteed

The inflight registry is volatile process memory. If Node-RED restarts after a message was emitted but before it was ACKed, the message remains in the mailbox and may be emitted again.

Completion guards for successfully ACKed inflight generations are also kept only in process memory and are bounded by a per-queue TTL and hard cap. After that bounded best-effort window, an extremely old fetch generation may be eligible for re-marking again. This keeps memory use bounded and preserves the package's at-least-once model; it is not an exactly-once guarantee.

No message is reported as successfully completed if the configured IMAP action fails. In that case output 2 receives the original message with msg.imapAck.ok = false, and the inflight entry remains available for a later retry.

To avoid unsafe IMAP fallback behavior, delete requires server support for UIDPLUS and move requires native MOVE. Without those capabilities the ACK action fails closed on output 2. copy keeps the source message, copies it to the target mailbox first, and then applies any configured flag changes to the source message only.

ACK tokens are opaque signed bearer capabilities scoped to the configured IMAP account and to the current in-memory inflight generation. Pass msg.imap.ackToken from imap-email in to imap-email ack unchanged. Do not build, edit, log or expose tokens externally. If a token is missing required fields, is unsigned, has been modified, has already completed, or no longer matches the current inflight generation, the ACK node rejects it on output 2 without creating an IMAP client. The internal queueKey value is not a stable public API.

For dynamic decisions, configure imap-email ack to set by msg.imap.ackAction and set msg.imap.ackAction:

msg.imap.ackAction = {
  action: "move",               // delete, move, copy, flag
  targetMailbox: "Archive/Processed",
  flags: {
    seen: "set",                // ignore, set, clear
    answered: "ignore",
    flagged: "clear",
    add: ["$Processed"],
    remove: ["\\Draft"]
  }
};

Successful completions add msg.imapAck with fields such as action, disposition, mailbox, targetMailbox, uid, uidValidity, flags, range and completed. Failed completions may include msg.imapAck.partial = true when a state-changing IMAP step already succeeded before a later step failed. For copy, a successful copy followed by a failed source flag update is partial; because Inflight is kept for retry, a retry may create another copy in the target mailbox.

Current Limits

  • OAuth2 token acquisition and refresh are not implemented. The account node supports username/password and an optional static access token field.

Development Checks

npm test
npm run pack:check

Do not publish this package to npm or flows.nodered.org without explicit human approval.

Node Info

Version: 1.0.0
Updated 21 hours ago
License: MIT
Rating: not yet rated

Categories

Actions

Rate:

Downloads

0 in the last week

Nodes

  • imap-email account
  • imap-email in
  • imap-email ack

Keywords

  • node-red
  • imap
  • email
  • mail
  • imap-email
  • at-least-once
  • ack

Maintainers