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:
@@ -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
|
||||
```
|
||||
|
||||
116
optional-skills/email/agentmail/references/core.md
Normal file
116
optional-skills/email/agentmail/references/core.md
Normal 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.
|
||||
16
optional-skills/email/agentmail/references/mcp.md
Normal file
16
optional-skills/email/agentmail/references/mcp.md
Normal 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.
|
||||
50
optional-skills/email/agentmail/references/signup.md
Normal file
50
optional-skills/email/agentmail/references/signup.md
Normal 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).
|
||||
30
optional-skills/email/agentmail/references/webhooks.md
Normal file
30
optional-skills/email/agentmail/references/webhooks.md
Normal 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.
|
||||
55
optional-skills/email/agentmail/references/websockets.md
Normal file
55
optional-skills/email/agentmail/references/websockets.md
Normal 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.
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user