Description
Environment
- @codefuturist/email-mcp: 0.2.3
- imapflow (transitive dep): ^1.2.9
- Node.js: v24.18.0
- Deployed behind sparfenyuk/mcp-proxy (streamable HTTP,
--no-stateless)
What happened
Under a burst of ~20+ concurrent tool calls (mark_email/move_email) against
a single IMAP account, one underlying IMAP connection was left idle after a
STATUS command. After ImapFlow's default 5-minute socketTimeout elapsed, the
server closed it and ImapFlow emitted an 'error' event on the client. Since
no 'error' listener appears to be attached, Node's default behavior for an
unhandled 'error' event on an EventEmitter took over: it threw and crashed
the entire process.
Server-side (Dovecot) log confirming the ~300s idle window before close:
imap(user@example.com)<sessionid>: Disconnected: Connection closed
(STATUS finished 300.114 secs ago) ...
Crash trace from our systemd journal:
node:events:487
throw er; // Unhandled 'error' event
^
Error: Socket timeout
at TLSSocket.<anonymous> (.../imapflow/lib/imap-flow.js:966:29)
at TLSSocket.emit (node:events:509:28)
at Socket._onTimeout (node:net:611:8)
...
Emitted 'error' event on ImapFlow instance at:
at ImapFlow.emitError (.../imapflow/lib/imap-flow.js:468:14)
...
{
code: 'ETIMEOUT',
_connId: '...'
}
Impact
Because we run email-mcp as a single long-lived backend behind mcp-proxy
(--no-stateless, one shared process per session), this single unhandled
error brought down the entire MCP tool surface for every client using that
session — not just the one in-flight call — until the parent process
noticed and respawned the child.
Suspected root cause
Somewhere in the codebase an ImapFlow client instance is created without
a .on('error', ...) handler attached. ImapFlow's own docs recommend
attaching an error listener on long-lived clients specifically because
transient conditions (like a socket timeout on an idle connection) surface
as an 'error' event rather than a rejected promise — and Node treats an
unlistened 'error' event as fatal.
This is the same bug class reported in another ImapFlow consumer:
twentyhq/twenty#20509
Suggested fix
- Attach an
.on('error', ...) handler (log + close gracefully) to every
ImapFlow client the server creates, rather than letting it propagate as
an unhandled EventEmitter error.
- Consider whether connections opened for a single command (e.g. STATUS)
are being closed/logged-out explicitly after use, or left dangling until
ImapFlow's own idle timeout fires — reusing/pooling connections would
also reduce how many concurrent IMAP sessions get opened under bursts of
parallel tool calls.
Happy to provide more logs or test a patch if useful.
Steps to reproduce
see details above
Expected behavior
see details above
Actual behavior
see details above
Version
0.2.3
Email provider
Other / Self-hosted
MCP client
Claude Desktop
Node.js version
24.18.0
Operating system
debian 13
Description
Environment
--no-stateless)What happened
Under a burst of ~20+ concurrent tool calls (mark_email/move_email) against
a single IMAP account, one underlying IMAP connection was left idle after a
STATUS command. After ImapFlow's default 5-minute socketTimeout elapsed, the
server closed it and ImapFlow emitted an 'error' event on the client. Since
no 'error' listener appears to be attached, Node's default behavior for an
unhandled 'error' event on an EventEmitter took over: it threw and crashed
the entire process.
Server-side (Dovecot) log confirming the ~300s idle window before close:
Crash trace from our systemd journal:
Impact
Because we run email-mcp as a single long-lived backend behind mcp-proxy
(
--no-stateless, one shared process per session), this single unhandlederror brought down the entire MCP tool surface for every client using that
session — not just the one in-flight call — until the parent process
noticed and respawned the child.
Suspected root cause
Somewhere in the codebase an
ImapFlowclient instance is created withouta
.on('error', ...)handler attached. ImapFlow's own docs recommendattaching an error listener on long-lived clients specifically because
transient conditions (like a socket timeout on an idle connection) surface
as an 'error' event rather than a rejected promise — and Node treats an
unlistened 'error' event as fatal.
This is the same bug class reported in another ImapFlow consumer:
twentyhq/twenty#20509
Suggested fix
.on('error', ...)handler (log + close gracefully) to everyImapFlow client the server creates, rather than letting it propagate as
an unhandled EventEmitter error.
are being closed/logged-out explicitly after use, or left dangling until
ImapFlow's own idle timeout fires — reusing/pooling connections would
also reduce how many concurrent IMAP sessions get opened under bursts of
parallel tool calls.
Happy to provide more logs or test a patch if useful.
Steps to reproduce
see details above
Expected behavior
see details above
Actual behavior
see details above
Version
0.2.3
Email provider
Other / Self-hosted
MCP client
Claude Desktop
Node.js version
24.18.0
Operating system
debian 13