feat(skills): rewrite AgentMail optional skill CLI-first

Replaces the stale MCP-first AgentMail skill with CLI-first guidance:
self-signup + OTP verification, inbox/message/thread/label/attachment
flows, webhook and WebSocket delivery references, and MCP as an
alternative path. Declares AGENTMAIL_API_KEY (optional) so a stored key
reaches the sandboxed terminal while self-signup stays viable without
one.

Salvaged from PR #60811 — kept in optional-skills/ per the March 2026
decision that third-party-API-key skills are not bundled.
This commit is contained in:
Haakam Aujla
2026-07-15 10:01:49 -07:00
committed by Teknium
parent cced6fa360
commit 14320d18df
7 changed files with 392 additions and 99 deletions

View File

@@ -1,128 +1,95 @@
---
name: agentmail
description: "Give the agent its own inbox: send and receive email."
description: Use when an agent needs AgentMail CLI email inboxes.
version: 1.0.0
author: teyrebaz33, Hermes Agent
author: Haakam Aujla (Haakam21), AgentMail
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [email, communication, agentmail, mcp]
category: email
tags: [Email, CLI, AgentMail, Communication]
homepage: https://agentmail.to
prerequisites:
commands: [agentmail]
required_environment_variables:
- name: AGENTMAIL_API_KEY
prompt: AgentMail API key (starts with am_)
help: "Create one at https://console.agentmail.to — or run the CLI self-signup flow in references/signup.md to obtain a key without one."
required_for: "authenticating the agentmail CLI; not needed before self-signup"
optional: true
---
# AgentMail — Agent-Owned Email Inboxes
# AgentMail Skill
## Requirements
AgentMail gives an agent its own email inbox for sending mail, receiving
replies, completing email OTP flows, and running inbound email loops. Use it for
agent-owned inboxes, not a user's existing IMAP/SMTP mailbox.
- **AgentMail API key** (required) — sign up at https://console.agentmail.to (free tier: 3 inboxes, 3,000 emails/month; paid plans from $20/mo)
- Node.js 18+ (for the MCP server)
Use the `agentmail` CLI first. Use MCP only when the harness expects MCP tools;
use REST only when the CLI is missing a required operation.
## When to Use
Use this skill when you need to:
- Give the agent its own dedicated email address
- Send emails autonomously on behalf of the agent
- Receive and read incoming emails
- Manage email threads and conversations
- Sign up for services or authenticate via email
- Communicate with other agents or humans via email
This is NOT for reading the user's personal email (use himalaya or Gmail for that).
AgentMail gives the agent its own identity and inbox.
- The agent needs an email address it owns.
- The task involves email OTP flows, replies, threads, labels, or attachments.
- The agent needs webhook or WebSocket delivery for inbound mail.
## Setup
## Prerequisites
### 1. Get an API Key
- Go to https://console.agentmail.to
- Create an account and generate an API key (starts with `am_`)
- Run commands through the `terminal` tool.
- Install the CLI:
### 2. Configure MCP Server
Add to `~/.hermes/config.yaml` (paste your actual key — MCP env vars are not expanded from .env):
```yaml
mcp_servers:
agentmail:
command: "npx"
args: ["-y", "agentmail-mcp"]
env:
AGENTMAIL_API_KEY: "am_your_key_here"
```
### 3. Restart Hermes
```bash
hermes
npm install -g agentmail-cli@latest
```
All 11 AgentMail tools are now available automatically.
## Available Tools (via MCP)
- Export an API key:
| Tool | Description |
|------|-------------|
| `list_inboxes` | List all agent inboxes |
| `get_inbox` | Get details of a specific inbox |
| `create_inbox` | Create a new inbox (gets a real email address) |
| `delete_inbox` | Delete an inbox |
| `list_threads` | List email threads in an inbox |
| `get_thread` | Get a specific email thread |
| `send_message` | Send a new email |
| `reply_to_message` | Reply to an existing email |
| `forward_message` | Forward an email |
| `update_message` | Update message labels/status |
| `get_attachment` | Download an email attachment |
```bash
export AGENTMAIL_API_KEY="am_..."
```
No API key yet? Use [signup.md](references/signup.md).
## How to Run
Use `--format json` whenever another command or script needs IDs.
```bash
agentmail inboxes list --format json
```
## Quick Reference
- [AgentMail agent reference](https://agentmail.md): hosted copy.
- [AgentMail](https://agentmail.to): product landing page.
- [Console](https://console.agentmail.to): API keys and account management.
- [Docs](https://docs.agentmail.to): full product documentation.
- [signup.md](references/signup.md): self-signup and OTP verification.
- [core.md](references/core.md): inboxes, messages, threads, labels, attachments.
- [webhooks.md](references/webhooks.md): events to a public HTTPS server.
- [websockets.md](references/websockets.md): events to a local agent process.
- [mcp.md](references/mcp.md): MCP integration.
## Procedure
### Create an inbox and send an email
1. Create a dedicated inbox:
- Use `create_inbox` with a username (e.g. `hermes-agent`)
- The agent gets address: `hermes-agent@agentmail.to`
2. Send an email:
- Use `send_message` with `inbox_id`, `to`, `subject`, `text`
3. Check for replies:
- Use `list_threads` to see incoming conversations
- Use `get_thread` to read a specific thread
### Check incoming email
1. Use `list_inboxes` to find your inbox ID
2. Use `list_threads` with the inbox ID to see conversations
3. Use `get_thread` to read a thread and its messages
### Reply to an email
1. Get the thread with `get_thread`
2. Use `reply_to_message` with the message ID and your reply text
## Example Workflows
**Sign up for a service:**
```
1. create_inbox (username: "signup-bot")
2. Use the inbox address to register on the service
3. list_threads to check for verification email
4. get_thread to read the verification code
```
**Agent-to-human outreach:**
```
1. create_inbox (username: "hermes-outreach")
2. send_message (to: user@example.com, subject: "Hello", text: "...")
3. list_threads to check for replies
```
1. Install `agentmail-cli@latest` and verify `agentmail inboxes list --format json`.
2. If no API key is available, complete [signup.md](references/signup.md).
3. Use [core.md](references/core.md) for inbox, send, read, reply, forward,
label, thread, and attachment flows.
4. Add [webhooks.md](references/webhooks.md) or
[websockets.md](references/websockets.md) only when polling is not enough.
## Pitfalls
- Free tier limited to 3 inboxes and 3,000 emails/month
- Emails come from `@agentmail.to` domain on free tier (custom domains on paid plans)
- Node.js (18+) is required for the MCP server (`npx -y agentmail-mcp`)
- The `mcp` Python package must be installed: `pip install mcp`
- Real-time inbound email (webhooks) requires a public server — use `list_threads` polling via cronjob instead for personal use
- Prefer `AGENTMAIL_API_KEY` over `--api-key`.
- Never expose `AGENTMAIL_API_KEY` in prompts, logs, URLs, or committed files.
- Use stable `client_id` values for retried create operations.
- Prefer `extracted_text` or `extracted_html` for LLM input when present.
- React to `message.received`, not messages the agent sent.
## Verification
After setup, test with:
```
hermes --toolsets mcp -q "Create an AgentMail inbox called test-agent and tell me its email address"
```
You should see the new inbox address returned.
## References
- AgentMail docs: https://docs.agentmail.to/
- AgentMail console: https://console.agentmail.to
- AgentMail MCP repo: https://github.com/agentmail-to/agentmail-mcp
- Pricing: https://www.agentmail.to/pricing
```bash
agentmail inboxes list --format json
```

View File

@@ -0,0 +1,116 @@
# AgentMail Core
Common CLI path: create inboxes, send mail, read incoming mail, reply in
threads, label work, and fetch attachments. Need a key first? Use
[signup.md](signup.md). Need realtime delivery? Use [webhooks.md](webhooks.md)
or [websockets.md](websockets.md).
## Setup
```bash
npm install -g agentmail-cli@latest
export AGENTMAIL_API_KEY="am_..."
agentmail inboxes list --format json
```
## Inboxes
```bash
agentmail inboxes create \
--username support \
--display-name "Support Agent" \
--client-id support-agent-primary \
--format json
agentmail inboxes get --inbox-id support@agentmail.to --format json
```
Omit `domain` to use `@agentmail.to`. Use stable `client_id` values for
retried create commands.
## Send
```bash
agentmail inboxes:messages send \
--inbox-id support@agentmail.to \
--to customer@example.com \
--subject "Hello" \
--text "Plain-text body." \
--html "<p>Plain-text body.</p>" \
--label outreach \
--format json
```
Send both `text` and `html` when possible. Recipient limit is 50 total across
`to`, `cc`, and `bcc`.
## Read and Reply
```bash
agentmail inboxes:messages list --inbox-id support@agentmail.to --label unread --format json
agentmail inboxes:messages get --inbox-id support@agentmail.to --message-id <message_id> --format json
agentmail inboxes:threads get --inbox-id support@agentmail.to --thread-id <thread_id> --format json
```
For LLM input, prefer `extracted_text` or `extracted_html`. Some email has
`html` but no `text`.
```bash
agentmail inboxes:messages reply \
--inbox-id support@agentmail.to \
--message-id <message_id> \
--text "Thanks, I will take a look." \
--format json
agentmail inboxes:messages reply-all \
--inbox-id support@agentmail.to \
--message-id <message_id> \
--text "Thanks, everyone." \
--format json
agentmail inboxes:messages forward \
--inbox-id support@agentmail.to \
--message-id <message_id> \
--to teammate@example.com \
--format json
```
## Labels
Use labels as lightweight state: `unread`, `handled`, `needs-review`.
```bash
agentmail inboxes:messages update \
--inbox-id support@agentmail.to \
--message-id <message_id> \
--add-labels handled \
--remove-labels unread \
--format json
```
## Attachments
```bash
agentmail inboxes:messages get-attachment \
--inbox-id support@agentmail.to \
--message-id <message_id> \
--attachment-id <attachment_id> \
--format json
```
Fetch the returned download URL before it expires. Check
`agentmail inboxes:messages send --help` for attachment-send flags.
## REST Notes
Use REST only when the CLI is unavailable or missing a required operation.
```bash
curl https://api.agentmail.to/v0/inboxes \
-H "Authorization: Bearer $AGENTMAIL_API_KEY"
```
Base URLs: `https://api.agentmail.to/v0`, `https://api.agentmail.eu/v0`.
Error bodies include `name` and `message`; validation errors include `errors`.
For `429`, honor `Retry-After` and back off.

View File

@@ -0,0 +1,16 @@
# AgentMail MCP
Use MCP only when the user or harness already wants MCP tools. The CLI remains
the default path for AgentMail workflows.
Hosted server:
```text
https://mcp.agentmail.to/mcp
```
Authentication:
- OAuth in compatible MCP clients.
- API key in `?apiKey=...`.
- API key in an `x-api-key` header.

View File

@@ -0,0 +1,50 @@
# AgentMail Self-Signup
Use this when the agent does not have an AgentMail API key. A human must receive
and provide the OTP.
## Sign Up
```bash
npm install -g agentmail-cli@latest
agentmail agent sign-up \
--human-email you@example.com \
--username my-agent \
--source agentmail-cli \
--referrer hermes-agent \
--format json
```
Export the returned `api_key`:
```bash
export AGENTMAIL_API_KEY="am_..."
```
Verify with the OTP:
```bash
agentmail agent verify --otp-code 123456
```
## Notes
- Use a real human email address for `--human-email`.
- `human_email` is the signup idempotency key, but signing up again with the
same email rotates the API key.
- Before verification, the account has one inbox, 10 sends/day, and can only
send to the signup human email.
## First Check
```bash
agentmail inboxes list --format json
agentmail inboxes:messages send \
--inbox-id my-agent@agentmail.to \
--to you@example.com \
--subject "AgentMail verified" \
--text "My AgentMail inbox is verified." \
--format json
```
Continue with [core.md](core.md).

View File

@@ -0,0 +1,30 @@
# AgentMail Webhooks
Use webhooks when a public HTTPS server should receive AgentMail events. Use
[websockets.md](websockets.md) for local processes without a public URL.
## Create
```bash
agentmail webhooks create \
--url https://your-app.example.com/webhooks/agentmail \
--event-type message.received \
--inbox-id support@agentmail.to \
--client-id support-agentmail-webhook \
--format json
```
Store the returned `secret` immediately.
## Handle
Headers: `svix-id`, `svix-timestamp`, `svix-signature`.
1. Verify the raw request body with the webhook secret.
2. Dedupe by `svix-id` or `event_id`.
3. Return `200` quickly.
4. Process asynchronously.
5. For `message.received`, load the thread with the CLI and reply if needed.
Act on `message.received` only. Treating `message.sent` or delivery events as
inbound work creates loops.

View File

@@ -0,0 +1,55 @@
# AgentMail WebSockets
Use WebSockets when a local agent process needs realtime inbound mail. After an
event arrives, use the CLI for inbox, send, reply, label, and attachment work.
## Python
The Python example needs the separate AgentMail Python SDK, not the CLI:
```bash
pip install agentmail
```
`AgentMail()` reads `AGENTMAIL_API_KEY` from the environment. If the SDK is
unavailable, use the `Raw` WebSocket below instead.
```python
from agentmail import AgentMail, MessageReceivedEvent, Subscribe
client = AgentMail()
with client.websockets.connect() as socket:
socket.send_subscribe(Subscribe(
inbox_ids=["agent@agentmail.to"],
event_types=["message.received"],
))
for event in socket:
if isinstance(event, MessageReceivedEvent):
print(event.message.subject, event.message.from_)
```
## Raw
```text
wss://ws.agentmail.to/v0?api_key=$AGENTMAIL_API_KEY
```
EU region:
```text
wss://ws.agentmail.eu/v0?api_key=$AGENTMAIL_API_KEY
```
Subscribe frame:
```json
{ "type": "subscribe", "event_types": ["message.received"], "inbox_ids": ["agent@agentmail.to"] }
```
Omit `inbox_ids` and `pod_ids` for the API key scope.
## Loop Rules
- Dedupe every event by `event_id`.
- Reconnect with backoff and resubscribe after reconnecting.

View File

@@ -1,6 +1,8 @@
"""Test that skill_view registers required env vars in the passthrough registry."""
import json
import shutil
from pathlib import Path
from unittest.mock import patch
import pytest
@@ -8,6 +10,12 @@ import pytest
import tools.env_passthrough as _ep_mod
from tools.env_passthrough import clear_env_passthrough, is_env_passthrough
# Real optional AgentMail skill — its CLI-first workflow runs `agentmail` through
# the sandboxed terminal, which strips secrets unless the skill declares them.
_AGENTMAIL_SKILL_SRC = (
Path(__file__).resolve().parents[2] / "optional-skills" / "email" / "agentmail"
)
@pytest.fixture(autouse=True)
def _clean_passthrough():
@@ -78,3 +86,54 @@ class TestSkillViewRegistersPassthrough:
assert result["success"] is True
from tools.env_passthrough import get_all_passthrough
assert len(get_all_passthrough()) == 0
class TestAgentMailKeyPassthrough:
"""The real AgentMail skill must declare AGENTMAIL_API_KEY so a profile-stored
key reaches the sandboxed terminal, while staying usable (self-signup) without
one."""
def _install_real_skill(self, tmp_path, monkeypatch):
dest = tmp_path / "email" / "agentmail"
shutil.copytree(_AGENTMAIL_SKILL_SRC, dest)
monkeypatch.setattr("tools.skills_tool.SKILLS_DIR", tmp_path)
return dest
def test_agentmail_api_key_registered_for_sandbox_when_set(self, tmp_path, monkeypatch):
"""A profile-stored AGENTMAIL_API_KEY must pass through to the terminal
sandbox — otherwise the documented CLI workflow fails authentication."""
self._install_real_skill(tmp_path, monkeypatch)
monkeypatch.setenv("AGENTMAIL_API_KEY", "am_test_key_123")
with patch("tools.skills_tool._secret_capture_callback", None):
from tools.skills_tool import skill_view
result = json.loads(skill_view(name="agentmail"))
assert result["success"] is True
assert is_env_passthrough("AGENTMAIL_API_KEY"), (
"AGENTMAIL_API_KEY was set but not registered for sandbox passthrough — "
"the skill's frontmatter must declare it under required_environment_variables"
)
def test_agentmail_key_is_optional_so_self_signup_still_works(self, tmp_path, monkeypatch):
"""With no key present the skill must NOT be blocked as setup_needed on the
key — the CLI self-signup flow obtains one without a pre-provisioned key."""
self._install_real_skill(tmp_path, monkeypatch)
monkeypatch.delenv("AGENTMAIL_API_KEY", raising=False)
with patch("tools.skills_tool._secret_capture_callback", None):
from tools.skills_tool import skill_view
result = json.loads(skill_view(name="agentmail"))
assert result["success"] is True
# `optional: true` means a missing key must NOT be reported as a blocking
# requirement — that is what keeps the CLI self-signup path viable.
assert "AGENTMAIL_API_KEY" not in result["missing_required_environment_variables"], (
"AGENTMAIL_API_KEY must be declared optional so a missing key does not "
"block the self-signup path"
)
assert result["setup_needed"] is False, (
"an absent optional key must not force the skill into setup_needed"
)