@ross/openclaw-channel-agentmail (1.0.0-alpha.13)

Published 2026-05-27 23:27:40 +00:00 by ross

Installation

@ross:registry=
npm install @ross/openclaw-channel-agentmail@1.0.0-alpha.13
"@ross/openclaw-channel-agentmail": "1.0.0-alpha.13"

About this package

AgentMail Channel

🦊 OpenClaw channel plugin for AgentMail — turn email inboxes into OpenClaw chat conversations.

This plugin lets your OpenClaw agent send and receive emails through AgentMail's API, treating each email thread as a conversation channel. Built on OpenClaw's channel plugin SDK.

Features

  • Inbox as channel — Each AgentMail inbox becomes a messaging channel
  • Threaded conversations — AgentMail threads map to OpenClaw session threads
  • Two-way email — Send and receive emails directly from OpenClaw
  • DM security — Configurable sender allowlists and pairing flows
  • Webhook-ready — Supports AgentMail webhooks for real-time inbound email
  • 📧 Smart classification — Rule-based classifier sorts emails into marketing / notification / personal / unknown
  • 📨 Marketing digest — Batches marketing emails into scheduled digests instead of spamming the agent
  • 🔔 Notification handling — Allowlisted notifications get context injection so the agent knows when to act vs. stay silent
  • 🤖 AI fallback — Optional lightweight LLM classification for ambiguous emails (configurable, default off)
  • 🔇 Silent mode — Suppress non-essential log noise
  • 📎 Auto-download attachments — Optionally download email attachments automatically with security controls (size limits, extension allowlist/blocklist)

Installation

openclaw plugins install @ross/openclaw-channel-agentmail

Or for development:

git clone https://git.reslate.solutions/ross/agentmail-channel.git
cd agentmail-channel
npm install
npm run build

Configuration

  1. Get an API key from the AgentMail Console
  2. Set up the channel in your OpenClaw config:
{
  "channels": {
    "agentmail": {
      "accounts": {
        "default": {
          "apiKey": "your-api-key",
          "inboxId": "your-inbox-id",
          "ownerEmail": "you@example.com",
          "forwardUnrecognized": true,
          "ignoreFrom": ["*@spamdomain.com", "noreply-*@badsender.com"],
          "allowFrom": ["trusted@partner.com"],
          "classifyInbound": true,
          "aiClassificationFallback": false,
          "marketingDigest": {
            "enabled": true,
            "schedule": "0 9 * * *",
            "maxQueueSize": 100
          },
          "notificationAction": {
            "enabled": true,
            "autoAction": false
          },
          "silentMode": false,
          "autoDownloadAttachments": {
            "enabled": true,
            "maxSizeMb": 25,
            "maxTotalSizeMb": 50,
            "allowedExtensions": ["pdf", "jpg", "png", "docx", "xlsx"],
            "blockedExtensions": ["exe", "js", "sh", "bat"]
          }
        }
      }
    }
  }
}

Or via environment variable:

export AGENTMAIL_API_KEY="your-api-key"

Classification & Routing

When classifyInbound: true, every inbound email is classified before routing:

Category Rule triggers What happens
Marketing Subject keywords (sale, newsletter, promo…), sender patterns (marketing@, mailchimp…), body signals (unsubscribe, opt-out…) Queued for digest — never forwarded or dispatched to AI
Notification Subject keywords (issue #, pull request, alert…), sender patterns (notifications@, noreply@github.com…), structural cues (no salutation, action buttons…) Allowlisted senders → inject context, agent decides action. Unknown sender → forwarded to owner if forwardUnrecognized is enabled
Personal / Business Low scores on both marketing and notification sides Dispatched to AI normally
Unknown Ambiguous signals, or empty content If aiClassificationFallback: true, a lightweight LLM prompt classifies it. Otherwise treated as personal

Marketing Digest

Marketing emails are batched and sent as a single digest email to ownerEmail on a schedule (default daily at 9 AM). Configure with:

  • marketingDigest.enabled — turn digest on/off
  • marketingDigest.schedule — cron-like string or shorthand (daily, hourly, 2h, 30m)
  • marketingDigest.maxQueueSize — cap the queue to prevent unbounded growth

Forward Unrecognized

When an unknown sender (not in allowFrom or ignoreFrom) sends an email:

  1. If forwardUnrecognized: true and ownerEmail is set → forward the email to owner with instructions
  2. Owner replies to the forward → agent sees the reply with context about the original sender
  3. Owner can say "approve", "ignore", or "reply to sender: …"

Silent Mode

Set silentMode: true to suppress non-essential classification and routing log messages.

Auto-Download Attachments

By default, email attachments are listed in the SYSTEM NOTE but not downloaded. To automatically download attachments from trusted (allowlisted) senders, enable autoDownloadAttachments:

{
  "channels": {
    "agentmail": {
      "accounts": {
        "default": {
          "autoDownloadAttachments": {
            "enabled": true,
            "maxSizeMb": 25,
            "maxTotalSizeMb": 50,
            "allowedExtensions": ["pdf", "jpg", "png", "docx", "xlsx"],
            "blockedExtensions": ["exe", "js", "sh", "bat"],
            "skipFrom": ["*@newsletter.example.com"]
          }
        }
      }
    }
  }
}
Option Description Default
enabled Turn auto-download on/off true
maxSizeMb Maximum size per attachment in MB 25
maxTotalSizeMb Maximum total size for all attachments in one email 50
allowedExtensions Only download these file types (empty = allow all) undefined
blockedExtensions Never download these file types []
skipFrom Sender patterns to skip auto-download for (same wildcard syntax as ignoreFrom) []

Security notes:

  • Downloads only happen after the sender passes the allowlist gate
  • Attachments from unrecognized senders are listed in SYSTEM NOTE but never auto-downloaded
  • Blocked extensions are checked before download; unknown/executable types are rejected
  • Files are saved to ~/.openclaw/workspace/downloads/ with sanitized filenames

Set autoDownloadAttachments: true for simple enablement with default limits, or false to disable entirely (attachments listed but not downloaded).

Development

npm install        # Install dependencies
npm run build      # Compile TypeScript
npm run dev        # Watch mode
npm test           # Run tests
npm run lint       # Lint code
npm run format     # Format code

Commit Convention

This project uses Conventional Commits with commitlint enforcement.

feat: add outbound email sending
fix: resolve inboxId parsing for multi-tenant pods
docs: update README with setup instructions

Release Workflow

Automated via semantic-release on push to main:

  • Analyzes commits for version bump (fix → patch, feat → minor, BREAKING → major)
  • Generates CHANGELOG
  • Publishes to git.reslate.solutions npm registry

Project Structure

src/
├── index.ts              # Plugin entry point
├── channel-plugin.ts     # Channel plugin definition
├── config.ts             # Config resolution
├── outbound.ts           # Outbound messaging (send email)
├── websocket.ts          # WebSocket client for real-time inbound
├── gateway.ts            # Gateway adapter (start/stop accounts, inbound routing)
├── classification.ts   # Rule-based email classifier + AI fallback hook
├── digest.ts             # Marketing digest builder + scheduled sender
├── security.ts           # Allowlist / ignore-list logic
├── session-grammar.ts    # Conversation/thread mapping
└── types.ts              # Public type exports
tests/
├── unit/                 # Unit tests
└── integration/          # Integration tests

Deployment Notes

Copy built files to the correct location

The plugin loader reads from ~/.openclaw/extensions/agentmail/dist/. Do not copy .js/.d.ts files to the root of the extension directory — they must be inside dist/.

cd agentmail-channel
npm run build
cp dist/* ~/.openclaw/extensions/agentmail/dist/

Gateway lifecycle: startAccount must block

The OpenClaw framework expects startAccount to return a promise that does not resolve until the account is actually stopped. If startAccount resolves immediately after setup, the framework sees "channel exited without an error" and enters an auto-restart loop.

What this means for the code:

  • Store a deferred runningPromise in AgentMailAccountState
  • startAccount must await runningPromise before returning
  • stopAccount resolves the promise after cleanup
  • Duplicate-start guard returns existing.runningPromise — do not return a fresh resolved promise

What this means for tests:

  • await adapter.startAccount() in tests will hang forever unless stopAccount is called
  • Use the withStartedAccount helper (or fire startAccount without awaiting and call stopAccount in cleanup)

Containerized testing

A Podman/ Docker container is available for isolated E2E testing:

podman build -t agentmail-test -f tests/e2e/Containerfile .
podman run -d -p 18790:18790 agentmail-test

See tests/e2e/Containerfile and tests/e2e/test-config.json for details.

License

MIT — see LICENSE

Dependencies

Development dependencies

ID Version
@commitlint/cli ^19.0.0
@commitlint/config-conventional ^19.0.0
@ross/semantic-release-forgejo ^1.0.0
@semantic-release/changelog ^6.0.3
@semantic-release/commit-analyzer ^13.0.0
@semantic-release/exec ^7.1.0
@semantic-release/git ^10.0.1
@semantic-release/npm ^12.0.0
@semantic-release/release-notes-generator ^14.0.0
@types/node ^22.0.0
conventional-changelog-conventionalcommits ^8.0.0
eslint ^9.0.0
eslint-config-prettier ^10.1.8
husky ^9.0.0
prettier ^3.0.0
semantic-release ^25.0.0
typescript ^5.5.0
typescript-eslint ^8.59.2
vitest ^3.0.0

Peer dependencies

ID Version
openclaw *

Keywords

openclaw channel-plugin agentmail email agent
Details
npm
2026-05-27 23:27:40 +00:00
2
Harrison Deng
UNLICENSED
75 KiB
Assets (1)
Versions (17) View all
1.0.0-alpha.18 2026-06-07
1.0.0-alpha.17 2026-06-02
1.0.0-alpha.16 2026-05-30
1.0.0-alpha.15 2026-05-29
1.0.0-alpha.14 2026-05-28