Email Trigger (IMAP) crashes n8n process when a fetched message has no attributes
Bug Description
getNewEmails() in the Email Trigger (IMAP) node (nodes/EmailReadImap/v2/utils.ts) assumes every message returned by imapConnection.search(...) has a populated attributes object. When a message in the result set has attributes === undefined, the first read of message.attributes.uid throws a TypeError inside the trigger's poll cycle. This is an uncaught exception, so it doesn't just fail one execution - it kills the trigger ("failed at runtime and was deactivated"), and in our case brought down the whole n8n process.
We also found that a naive fix (just skipping messages without attributes) exposes a second bug: the do/while retry loop in the same function only checks results.length >= EMAIL_BATCH_SIZE to decide whether to loop again - it never checks whether the loop made forward progress (maxUid advancing). If an entire batch lacks usable attributes, maxUid stays at 0 forever and the loop re-fetches the same batch indefinitely, ballooning memory until the OS OOM-kills the process.
Full write-up with stack trace, code, and a suggested fix is in the "Debug Info" section below.
To Reproduce
- Set up the Email Trigger (IMAP) node against an IONOS mailbox (imap.ionos.de) with trackLastMessageId enabled on a live production mailbox with hundreds/thousands of messages.
- Restart the n8n instance (Docker container) so the trigger re-establishes its IMAP connection and polls for new messages.
- On our instance this reliably produced at least one message in the search result whose
attributesobject was undefined - including polls that found zero genuinely new messages. - The trigger throws "Cannot read properties of undefined (reading 'uid')", logs "The trigger node ... failed at runtime and was deactivated", and in our environment crashed the whole n8n Node process (Docker's restart policy then brought up a fresh container, which hit the same crash again on its own first poll).
Expected behavior
The trigger should skip a malformed/attribute-less message gracefully (log a warning and continue) instead of throwing an uncaught exception that kills the trigger and can crash the whole n8n process.
Debug Info
Note: this is not the literal "Copy debug information" output (our instance was mid-incident and heavily patched at the time), but here is the full technical detail:
Stack trace:
TypeError: Cannot read properties of undefined (reading 'uid')
at TriggerContext.getNewEmails (.../EmailReadImap/v2/utils.ts:111:59)
at processTicksAndRejections (node:internal/process/task_queues:104:5)
at fetchNewEmails (.../EmailReadImap/v2/EmailReadImapV2.node.ts:307:5)Followed by:
Email Read Imap node encountered an error fetching new emails
The trigger node "E-Mails lesen (IONOS)" failed at runtime and was deactivatedRoot cause, compiled dist/nodes/EmailReadImap/v2/utils.js (~lines 64-86):
async function getNewEmails({ onEmailBatch, imapConnection, postProcessAction, searchCriteria, }) {
let criteria = searchCriteria;
let results = [];
let maxUid = 0;
do {
if (maxUid) {
criteria = criteria.filter(...);
criteria.push(['UID', `${maxUid}:*`]);
}
results = await imapConnection.search(criteria, FETCH_OPTIONS[format] ?? {}, limit);
const newEmails = [];
const processedUids = [];
for (const message of results) {
const lastMessageUid = staticData.lastMessageUid;
if (lastMessageUid !== undefined && message.attributes.uid <= lastMessageUid) // throws if message.attributes is undefined
continue;
if (message.attributes.uid > maxUid)
maxUid = message.attributes.uid;
...
}
...
if (maxUid > (staticData.lastMessageUid ?? 0)) {
staticData.lastMessageUid = maxUid;
}
} while (results.length >= EMAIL_BATCH_SIZE); // no progress check
}Suggested fix:
for (const message of results) {
if (!message.attributes) { continue; } // skip malformed entries instead of crashing
const lastMessageUid = staticData.lastMessageUid;
if (lastMessageUid !== undefined && message.attributes.uid <= lastMessageUid)
continue;
...
}let maxUid = 0;
do {
const previousMaxUid = maxUid; // track progress
...
} while (results.length >= EMAIL_BATCH_SIZE && maxUid > previousMaxUid); // require forward progressWe patched exactly this directly in a running container for our own recovery, and both the instant-crash-on-poll and the runaway-memory loop stopped. We can't rule out a third, slower memory-growth issue under normal operation that we didn't have time to isolate separately, but these two are clearly real and independent of that.
Additional environment notes: 1 vCPU / 4GB RAM VPS, Docker image n8nio/n8n:latest pulled ~2026-09-26. Tested NODE_OPTIONS=--max-old-space-size at 1536/2048/3072 - the crash-on-poll happened identically regardless, confirming it is not a heap-size issue.
Operating System
Ubuntu 25.10
n8n Version
2.40.7
Node.js Version
26.7.0
Database
SQLite (default)
Execution mode
main (default)
Hosting
self hosted
Source: n8n-io/n8n