Bug: Memory leak in long-running email-mcp process
Summary
email-mcp's JavaScript heap grows steadily over days of continuous operation until it exhausts available memory and crashes with OOM. This affects any long-running deployment (HTTP server, Docker container, systemd service), not a specific transport mode.
Environment
- Transport: HTTP (Streamable HTTP), but same
runServer() code path as stdio
- Node.js: 24 (
node:24-slim)
- Accounts: 2 IMAP accounts
- Container memory limit: 512 Mi
- Watcher: disabled (
[settings.watcher] enabled = false)
Symptoms
- Process starts at ~187 Mi
- Heap grows steadily to ~254 MB over 6 days (~5.7 days uptime,
494741809 ms)
- GC cannot keep up:
Mark-Compact (reduce) 253.7 → 253.5 MB — almost nothing freed
- Crash:
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
- Exit code 139 (SIGSEGV after OOM handler)
Logs (anonymized — no account data)
email-mcp HTTP server listening on :8080
Endpoint : http://0.0.0.0:8080/mcp
Health : http://0.0.0.0:8080/health
<--- Last few GCs --->
[1:0x88ca000] 494741295 ms: Mark-Compact (reduce) 253.7 (257.0) -> 253.5 (256.8) MB, pooled: 0 MB, 482.95 / 0.00 ms (average mu = 0.999, current mu = ...)
[1:0x88ca000] 494741809 ms: Mark-Compact 254.6 (256.8) -> 254.0 (258.2) MB, pooled: 0 MB, 511.13 / 0.00 ms allocation failure; scavenge might not succeed
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----
1: node::OOMErrorHandler(...)
2-14: [internal node frames]
Analysis
The watcher is disabled, so the leak is not from IMAP IDLE. Likely sources:
-
Scheduler — setInterval(checkAndSend, 60_000) runs ~8,640 times over 6 days. If IMAP connections/results aren't fully released after each check, they accumulate.
-
ConnectionManager — persistent ImapFlow clients (Map<string, ImapFlow>) may accumulate internal buffers (mailparser, fetch results) that aren't GC'd properly over time.
-
ImapService caches — mailboxListCache, labelStrategies, labelStrategyPending Maps grow and stale entries may not be evicted. The code itself notes: "buffer as a JS string — that would defeat the parse cap and pin memory" (imap.service.ts:451).
-
HTTP sessions — StreamableHTTPServerTransport session state may not be fully cleaned up after client disconnects.
Why this isn't seen in typical stdio usage
In stdio mode, email-mcp is typically spawned per-session (Claude Desktop, Cursor) and lives for minutes to hours — not enough time for the slow leak to manifest. Long-running deployments (HTTP server, Docker, systemd) expose it.
Reproduction
- Start email-mcp in HTTP mode:
node dist/main.js http 8080
- Configure 2 IMAP accounts
- Leave running for 5-7 days with periodic tool calls
- Observe heap growth via
--trace-gc or process memory monitoring
- Process crashes with OOM (exit 139)
Workaround
- Increase memory limit to 1 Gi
- Daily process restart (CronJob in k8s, systemd timer, or equivalent)
Bug: Memory leak in long-running email-mcp process
Summary
email-mcp's JavaScript heap grows steadily over days of continuous operation until it exhausts available memory and crashes with OOM. This affects any long-running deployment (HTTP server, Docker container, systemd service), not a specific transport mode.
Environment
runServer()code path as stdionode:24-slim)[settings.watcher] enabled = false)Symptoms
494741809 ms)Mark-Compact (reduce) 253.7 → 253.5 MB— almost nothing freedFATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryLogs (anonymized — no account data)
Analysis
The watcher is disabled, so the leak is not from IMAP IDLE. Likely sources:
Scheduler —
setInterval(checkAndSend, 60_000)runs ~8,640 times over 6 days. If IMAP connections/results aren't fully released after each check, they accumulate.ConnectionManager — persistent ImapFlow clients (
Map<string, ImapFlow>) may accumulate internal buffers (mailparser, fetch results) that aren't GC'd properly over time.ImapService caches —
mailboxListCache,labelStrategies,labelStrategyPendingMaps grow and stale entries may not be evicted. The code itself notes: "buffer as a JS string — that would defeat the parse cap and pin memory" (imap.service.ts:451).HTTP sessions —
StreamableHTTPServerTransportsession state may not be fully cleaned up after client disconnects.Why this isn't seen in typical stdio usage
In stdio mode, email-mcp is typically spawned per-session (Claude Desktop, Cursor) and lives for minutes to hours — not enough time for the slow leak to manifest. Long-running deployments (HTTP server, Docker, systemd) expose it.
Reproduction
node dist/main.js http 8080--trace-gcor process memory monitoringWorkaround