imap-agent

n8n community node for IMAP mailbox access with first-class AI Agent (usableAsTool) support. Read, search, move, flag and download emails from any IMAP server via imapflow + mailparser.

Documentation

n8n-nodes-imap-agent

An n8n community node for IMAP mailbox access with first-class AI Agent support (usableAsTool: true). Read, search, move, flag and download emails from any IMAP server — either as a regular workflow node or as a tool driven by the n8n AI Agent node.

Built on top of imapflow (modern, Promise-based IMAP) and mailparser for RFC822 MIME parsing.


Why another IMAP node?

The existing community node n8n-nodes-imap works well for classic workflows, but was not designed for the n8n AI Agent tool protocol. This package is an independent implementation with three priorities:

  1. AI-ergonomic descriptions. Every resource, operation and parameter has a description written for an LLM: what the tool does, when to pick it, what each filter means.
  2. Flat, JSON-shaped search input. IMAP search criteria (SINCE, UNSEEN, FROM, etc.) are exposed as a collection with one field per criterion — the LLM fills a JSON object, the node translates it to an IMAP query.
  3. Binary-clean downloads. Attachments and .eml files come through as n8n binary data, ready to hand off to Paperless-ngx, S3, a file node, or back to the agent.

Installation

In the n8n UI (self-hosted)

Settings → Community Nodes → Install → package name n8n-nodes-imap-agent → Install.

As a custom node (link or manual install)

# in your n8n instance
cd ~/.n8n/custom
npm install n8n-nodes-imap-agent
# restart n8n

Docker

Mount the custom directory and install inside the container, or extend the image:

FROM n8nio/n8n:latest
USER root
RUN cd /home/node/.n8n/custom && npm install n8n-nodes-imap-agent
USER node

Credentials

Create a credential of type IMAP API.

Field Description Example
Authentication Password or OAuth2 (XOAUTH2) Password
Host IMAP server hostname imap.gmail.com
Port 993 (implicit TLS) or 143 993
Use TLS enable for port 993 true
User mailbox user / email you@example.com
Password account or app password xxxx-xxxx-xxxx-xxxx
Access Token OAuth2 token (only in OAuth2 mode) —
TLS Options → Allow Self-Signed set false for self-hosted Dovecot with a private CA true
TLS Options → Min Version lowest TLS version to negotiate TLSv1.2
Advanced → Connection Timeout socket timeout in ms 30000
Advanced → Disable Compression turn off IMAP COMPRESS=DEFLATE false

Gmail / Google Workspace

  • Enable 2FA and use an App Password — Google refuses plain passwords over IMAP since 2022.
  • Alternatively use OAuth2 (XOAUTH2) and hand over a fresh access token. ⚠️ This node does not refresh tokens on its own — access tokens typically expire after 1 hour. Pair with an upstream HTTP Request / OAuth2 node that refreshes, then pass the token in via expression like ={{ $json.access_token }}. A stale token surfaces as "authentication failed".

Microsoft 365 / Outlook.com

  • Basic auth is deprecated. Use OAuth2 (XOAUTH2) with a Graph-registered app (IMAP.AccessAsUser.All scope).

Self-hosted (Dovecot, Stalwart, Mailcow)

  • Plain password + TLS 1.2 on port 993.
  • For internal CAs: set Allow Self-Signed Certificates to false only after you've installed your CA root in the n8n container — otherwise use true.

Operations

Resource: Email (10)

Operation What it does Typical AI use
Search IMAP SEARCH with FROM/SUBJECT/SINCE/UNSEEN/FLAGGED/TEXT/BODY filters "Find ungelesene Mails mit 'Rechnung' von letzter Woche"
Get Fetch one message by UID, parsed subject/body/headers, optional attachments "Lies Mail 4217 und fasse zusammen"
Move Move UIDs → folder (batch-fähig) "Verschiebe alle 12 Rechnungen nach /Archive/2026/Rechnungen"
Copy Copy UIDs → folder (batch) —
Delete \Deleted + best-effort EXPUNGE (batch, returns {deleted, expunged, uids, requestedCount} — expunged: false means the server rejected UID EXPUNGE, flag is set, server expunges on next folder close) —
Mark Read / Mark Unread \Seen flag toggle (batch) —
Flag / Unflag \Flagged (star) or custom keyword (batch) "Markiere als Important"
Append Upload raw RFC822 to a folder (from text or binary input) "Save draft"

Resource: Mailbox (7)

List, Create, Delete, Rename, Status, Quota, Test Connection.

Agents should run Mailbox: List once before addressing specific folders, so they know the separator (/, ., /[Gmail]/) and which folders exist.

Batch UIDs

All write operations (Move, Copy, Delete, Mark Read, Mark Unread, Flag, Unflag) accept the batch UIDs field — a single number, a comma list, or an IMAP range:

Input Meaning
42 single message
1,5,9 three messages
100:200 101 messages (range, inclusive)
1:* all messages in the folder
1,5,9:12,20 mixed

One call → one IMAP round-trip. A classification workflow that moves 50 mails no longer needs 50 execute() iterations.

Error Handling

When the node is set to Continue On Fail, error items now include full context so a downstream IF / Switch node can branch cleanly:

{
  "error": "IMAP flag add failed: no messages matched \"4217\" in \"INBOX\"",
  "resource": "email",
  "operation": "flag",
  "mailbox": "INBOX",
  "uids": "4217"
}

Trigger: IMAP Trigger

Separate node (group trigger) that starts a workflow when the watched mailbox changes. Uses IDLE when the server supports it, polling otherwise.

Property Default Notes
Mailbox INBOX Folder path (server delimiter)
Events newMessage Multi-select: newMessage (EXISTS), expunged (EXPUNGE), flagsChanged
Mode auto auto picks IDLE if advertised, else poll. idle forces (errors if unsupported). poll forces polling.
Poll Interval 60 s Only used in poll mode
Filters — Optional client-side from / to / subject substring filter applied AFTER fetch
Fetch Options — includeBody, includeHeaders, includeAttachments, maxAttachmentSizeMB (default 25)
Manual Trigger Sample Size 5 When you click "Execute Node" in the editor, emit N recent messages for testing

Behaviour:

  • No backlog replay. On first activation (or after UIDVALIDITY change), the trigger sets its watermark to the current UIDNEXT and only emits subsequent messages.
  • State persistence. lastUidNext + uidValidity survive workflow restarts via getWorkflowStaticData. UIDVALIDITY change (mailbox re-indexed) resets the watermark without replaying.
  • Reconnect. Exponential backoff (250 ms → 30 s cap) on connection loss. client.on('close' | 'error') triggers a re-connect; the catch-up fetch after reconnect emits anything that arrived while offline.
  • Manual test. Clicking "Execute Node" in the editor emits the N most recent messages without touching production state — safe to test an activated workflow.

Resource: Download (2)

  • Download Attachments — all attachments of a UID into binary slots, with optional filters:
    • Filename Contains — case-insensitive substring match on the attachment filename.
    • MIME Type — exact (application/pdf), type-wildcard (image/*), or full-wildcard (* / */*). A bare type like image does not match — always include a subtype or wildcard.
    • Max Attachment Size (MB) — default 25. Guards n8n workers against OOM when a mail carries a 500 MB file. Set 0 to disable.
  • Download EML — full raw RFC822 message as a single .eml binary.

Example AI Agent workflow

Paste the JSON below into Workflows → Import from clipboard. It's a minimal "triage my inbox" agent: the AI Agent is wired to an OpenAI chat model and is given the IMAP node as a tool. The system prompt is kept short on purpose — the tool descriptions in this package do the heavy lifting.

Replace imap-ai / openai-cred with your actual credential IDs after import.

{
  "name": "IMAP AI Triage (demo)",
  "nodes": [
    {
      "parameters": {},
      "id": "1a",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "options": {
          "systemMessage": "Du bist ein E-Mail-Assistent. Nutze das Tool 'IMAP (AI)' um Mails zu suchen, zu lesen oder zu verschieben. Antworte knapp auf Deutsch."
        }
      },
      "id": "1b",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [520, 300]
    },
    {
      "parameters": {
        "model": "gpt-4o-mini",
        "options": {}
      },
      "id": "1c",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1,
      "position": [520, 500],
      "credentials": { "openAiApi": { "id": "openai-cred", "name": "OpenAI" } }
    },
    {
      "parameters": {
        "resource": "email",
        "operation": "search",
        "mailbox": "INBOX",
        "searchFilters": { "unseen": true },
        "options": { "limit": 20, "newestFirst": true }
      },
      "id": "1d",
      "name": "IMAP (AI)",
      "type": "n8n-nodes-imap-agent.imap",
      "typeVersion": 1,
      "position": [800, 500],
      "credentials": { "imapApi": { "id": "imap-ai", "name": "My IMAP" } }
    }
  ],
  "connections": {
    "When chat message received": {
      "main": [[{ "node": "AI Agent", "type": "main", "index": 0 }]]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]
    },
    "IMAP (AI)": {
      "ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]]
    }
  }
}

Example chat turns once the workflow is running:

  • "Habe ich neue Rechnungen seit Montag?" → Agent calls Email / Search with { subject: "Rechnung", since: "2026-04-20", unseen: true }.
  • "Zeig mir Mail 1423" → Email / Get with uid: 1423.
  • "Verschiebe sie nach Archive/2026/Rechnungen" → Email / Move with that UID and the target folder.

Local development

git clone https://github.com/Daisytwo/n8n-nodes-imap-ai.git
cd n8n-nodes-imap-ai
npm install
npm run build          # tsc + gulp build:icons
npm run lint           # ESLint with n8n-nodes-base rules
npm test               # Jest unit tests for helpers
npm run dev            # tsc --watch

CI runs lint, build and test on every push and PR against main via GitHub Actions (.github/workflows/ci.yml, Node 20 & 22).

Then link into a local n8n instance:

# inside this repo
npm link

# in your n8n custom-nodes dir (create if missing)
mkdir -p ~/.n8n/custom && cd ~/.n8n/custom
npm init -y 2>/dev/null || true
npm link n8n-nodes-imap-agent

# start n8n — it auto-discovers linked packages
n8n start

Open http://localhost:5678, create an IMAP API credential, add an IMAP (AI) node, or wire it as a tool under an AI Agent node.


Implemented / omitted / known limits

Implemented (19 operations + trigger)

  • Mailbox (7): List, Create, Delete, Rename, Status, Quota, Test Connection
  • Email (10): Search, Get, Move, Copy, Delete, Mark Read, Mark Unread, Flag, Unflag, Append
  • Download (2): Download Attachments (with filename/MIME/size filter), Download EML
  • Trigger: IMAP Trigger with IDLE (push) + polling fallback, watermark-based dedupe via UIDNEXT/UIDVALIDITY

Deliberately not included

  • Gmail-only thread ops (X-GM-THRID). Non-portable across servers.
  • OAuth2 refresh flow. Credentials take a pre-issued access token — pair with an upstream OAuth2 credential node for refresh.

Known limits

  • hasAttachment filter is a size heuristic (> 50 KB), not an exact BODYSTRUCTURE scan.
  • No connection pool across execute() runs — a fresh IMAP connection per node execution, one socket shared across all items inside that execution.
  • Unit tests cover the pure helpers (buildSearchQuery, parseRawEmail, matchesMime). Integration testing against Gmail / Dovecot / Stalwart is manual — there is no mock IMAP server in the suite.
  • Delete issues \Deleted + best-effort EXPUNGE (expunged: false in the return when the server refuses UID EXPUNGE — the flag is still set). Gmail behaviour differs (moves to Trash).
  • Attachment size cap defaults to 25 MB — larger attachments are silently skipped during Download Attachments. Override via Max Attachment Size (MB) = 0 to disable.

License

MIT ©

Discussion