AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Using Agentmail

skill-jtsternberg-claude-plugins-using-agentmail · by jtsternberg

Send and receive email for an AI agent via the AgentMail API and `agentmail` CLI — create inboxes, send, read messages and threads, reply/forward, manage drafts, run agent self-signup + OTP verification. Also fires when the agent is asked about its own email identity ('what is your email address?', 'do you have an inbox?') — answer from `agentmail inboxes list`, not session context.

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add skill-jtsternberg-claude-plugins-using-agentmail

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-jtsternberg-claude-plugins-using-agentmail)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Using Agentmail? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

AgentMail

Give an agent its own email address and run a full round trip on it: create inboxes, send, read what comes back, reply / reply-all / forward, and stage drafts for review. Driven entirely by the official agentmail CLI.

This is AgentMail specifically — an email API built for agents, where every inbox is an API resource. It is not the user's own mail: reading or sending the user's Gmail is the gws plugin. It is not raw SMTP.

This is the hub. Four sibling skills go deeper

Use them instead of this file when the task is one of theirs; come back here for setup, flags, errors, and anything the others do not cover.

| Skill | For | |---|---| | contacts | the address book — who is who, and which address is actually verified | | check-mail | what arrived, reading full bodies, and triaging an inbox | | replying | reply / reply-all / forward, draft-first for anything consequential | | relay-work-order | handing work to (or taking work from) another agent over email |

Two shared references sit at the plugin root because more than one skill needs each: references/replying.md (the mechanics of answering mail) and references/agent-mail-protocol.md (the [HANDOFF]/[ASK]/[FYI]/[DONE] contract).

Codex: substitute the installed plugin directory for the path below.

${CLAUDE_PLUGIN_ROOT}/references/replying.md
${CLAUDE_PLUGIN_ROOT}/references/agent-mail-protocol.md

"What is your email address?" — check, don't assume

When you're asked whether you have an email, what your address is, or whether people can write to you, the honest answer is not "I'm an agent, I don't have one" and it is never the user's own address from session context. An inbox may have been provisioned for you already. Find out before you answer:

agentmail inboxes list

If it returns an inbox, that address (and its display name) is your answer. If it returns nothing — or the CLI/key isn't set up (run the preflight) — then say you have no inbox yet and offer to create one. Answering an identity question from memory is how an agent with a live @agentmail.to inbox came to tell its user, twice, that it had no email.

The golden rule, split in two

agentmail ships often and its flags move. Never construct a real invocation from memory or from this file — run agentmail --help first. That is the only trustworthy source for flags.

But --help alone is not enough here, and the reason is specific. Most command descriptions in the shipped binary are the literal string **CLI:** — a docs-generation artifact:

agentmail inboxes create - **CLI:**
agentmail inboxes:messages send - **CLI:**

Flags are intact; the prose is gone. So the split is:

  • Flags → --help. Always. Do not trust flag lists written down anywhere, including here.
  • Meaning → this file. What a command actually does, what is irreversible, which

arguments conflict, what the blast radius is. The CLI no longer tells you, so the semantic notes below are load-bearing rather than decorative.

Also: agentmail help does not exist. Only --help.

Current environment (resolved at skill load)

Codex: this path resolves under Claude Code; substitute the installed plugin directory.

```!

Codex: this path resolves under Claude Code; substitute the installed plugin directory.

PLUGINROOT="${CLAUDEPLUGINROOT}" bash "$PLUGINROOT/scripts/agentmail-preflight.sh" --local


The preflight lives at the **plugin root**, not in this skill's directory: four skills and
the mail-check hook all run it, and one copy is the repo's rule for that
(AGENTS.md § Sharing Code or Docs Between Sibling Skills). `agentmail-signup.sh` and
`agentmail-verify.sh` have one consumer each and stay under this skill's `scripts/`.

Codex does not execute `!` blocks, so under Codex the above has not run: **run the
preflight script yourself as your first step**, substituting this skill's directory for
the path.

`--local` is deliberately offline — CLI presence, version, and whether a key is set. No
API call at load time: a round trip on every load costs latency and quota and would print
org and inbox identifiers into context for tasks that never touch email. When you actually
need to know the key works, run the same script with no flag (one `inboxes list`, which
also prints the inbox id you will need next).

Exit codes, so you can branch without reading prose: `0` ready (with `--local`: CLI + key
present; without it: key also accepted by a live probe, **including a recognized key that
is merely scope-limited**) · `10` no CLI · `11` no key · `12` key rejected — malformed,
revoked, or rotated · `30` probe inconclusive (network/429/5xx — **not** a bad key).

**`organizations get` is not the auth probe, and must not be used as one.** It has no
required flags, which made it look like the cheapest check, but it succeeds *only* on an
organization-scoped key. Verified live: an inbox-scoped key gets
`403 missing_permission` from it — a working key, refused. `inboxes list` succeeds on
inbox-, pod-, and org-scoped keys alike. And by the same logic, a `missing_permission`
refusal anywhere is a **scope** answer, never a credential answer: the key was recognized
well enough to be told which permission it lacks.

## Setup

**CLI missing (exit 10):** offer the options and stop. **Never run an installer without
explicit user consent** — global npm state is machine-wide and not something the user can
un-notice.

npm install -g agentmail-cli # official. The npm package is a thin wrapper whose # postinstall downloads a Go binary from GitHub # releases, so this needs network + github.com npx agentmail-cli # no global install; pays the download on a cold cache


**No key (exit 11):** exit 11 means no key *in this shell* — not that none exists. Before
signing up, rule out a key that's merely unexported: signup **rotates** any existing key,
so creating one when the user already has a working key (in their shell profile, a secret
manager, a prior `~/.config/agentmail/signup-*.json`, or an `AGENTMAIL_API_KEY` in their
dotfiles) silently breaks the old one. If there's genuinely no key anywhere, then either
agent self-signup (below) or a human creates one at
[console.agentmail.to/dashboard/api-keys](https://console.agentmail.to/dashboard/api-keys)
(that page requires picking a **scope** and an **access** level — see
[references/onboarding.md](references/onboarding.md) for which to choose). Read
onboarding.md before starting signup.

**Key rejected (exit 12):** it was revoked, malformed, or **rotated** — re-running
`agent sign-up` for an email that already signed up rotates the key and silently
invalidates the old one.

## Onboarding: signup → OTP → verify

Full walkthrough in [references/onboarding.md](references/onboarding.md). The shape:

```bash
# Codex: this path resolves under Claude Code; substitute the directory containing this SKILL.md.
SKILL_DIR="${CLAUDE_SKILL_DIR}"
bash "$SKILL_DIR/scripts/agentmail-signup.sh" --human-email you@example.com --username my-agent
# → a human reads the 6-digit code from their email
bash "$SKILL_DIR/scripts/agentmail-verify.sh" --otp-code 123456

Both --human-email and --username are required, and --username becomes the agent's real address (@agentmail.to) — the thing recipients see and reply to. Ask the user for both up front and let them choose the username; don't pick one silently or discover the requirement mid-flow. (The console-key alternative instead makes you choose a scope and access level — which to pick is in [references/onboarding.md](references/onboarding.md).)

Three things that matter more than the commands:

Never run agentmail agent sign-up directly. It returns the API key on stdout, which puts a live credential into this transcript — and transcripts get archived and indexed. The script captures it to a 0600 file outside any repo and prints only a masked fingerprint plus the path. Do not Read that file into the conversation; point the user at it so they can store the key.

The OTP step requires a human and cannot be automated away. The code goes to the human's email, expires in 24h, and allows 10 attempts — after which even the correct code is rejected until it expires.

Until verification succeeds, the only address the agent can email is the human's own signup address. Anything else fails 403 message_rejected. And there is no way to check verification state up front: the API exposes no verification field on any documented GET, so 403 message_rejected on a send to a third party is the signal. The preflight will say "verification: unknown" and that is honest, not a bug.

Before you send: the gate

Show the user the exact recipients (to/cc/bcc), the subject, and the body, and get explicit confirmation, before running any command that sends mailsend, reply, reply-all, forward, drafts send. One confirmation covers one send, not a session.

Email cannot be recalled, and — see below — there is no CLI-level idempotency that would make a retry safe. This is the one place in this skill where being slow is correct.

Three rules that follow from it:

Prefer the draft-first path for anything the user has not dictated verbatim:

agentmail inboxes:drafts create --inbox-id  --client-id  --to ... --subject ... --text ...
agentmail inboxes:drafts get --inbox-id  --draft-id  --format json   # show the user
agentmail inboxes:drafts send --inbox-id  --draft-id                 # after they approve

--client-id makes the create half idempotent, and the draft is a reviewable artifact rather than a claim about what you were about to do. This is also AgentMail's own human-in-the-loop recommendation.

reply-all needs a headcount first. inboxes:messages reply-all accepts no --to/--cc/--bcc — the API forbids explicit recipients when replying to all — so the blast radius is whatever is already on the thread. Read the thread, tell the user how many addresses will receive it, then send. The cap is 50 recipients across to+cc+bcc.

The safe rehearsal is emailing the human's own signup address. On an unverified org it is the only thing that works, which makes it the right first test after setup.

Why sends are not on the allowlist

allowed-tools covers the read/list/get/search surface and nothing that sends, creates, updates, or deletes. That is deliberate: every mutating command falls through to a Claude Code permission prompt, so the harness asks the user before an irreversible action even if the model has convinced itself the intent was clear.

Do not "fix" this by broadening to Bash(agentmail *). The prompt is the feature. tests/safety_test.sh fails if a send or delete verb appears in allowed-tools.

Idempotency: client_id does not cover sends

Two different mechanisms, and only one is reachable from the CLI:

| | Mechanism | CLI | |---|---|---| | Creating resources (inboxes, drafts) | body client_id | --client-id ✅ | | Sends (send, reply, forward, drafts send) | Idempotency-Key HTTP header | no flag ❌ |

--headers is email headers placed into the message, not HTTP request headers. There is no --idempotency-key, no --max-retries, no --timeout.

So the rule is the opposite of a retry loop:

> Never retry a failed send. On any ambiguous failure — timeout, 5xx, killed process, > unclear error — do not re-run the command. Check whether it actually went out: > > ``bash > agentmail inboxes:messages list --inbox-id --limit 5 --format json > ` > > Then decide with the user. If a guaranteed-once send genuinely matters, drop to curl > with an Idempotency-Key` header, or use the Python/TypeScript SDK, which can set it.

Command surface, by task

Flags from --help. This table is for finding the command, not for calling it.

| Task | Command | |---|---| | Am I authenticated? | inboxes list — works on any key scope, and returns the inbox id | | Sign up an agent | agent sign-upvia the script, never directly | | Verify OTP | agent verify — via the script | | Create / list / get an inbox | inboxes create · inboxes list · inboxes get | | Send new mail | inboxes:messages send | | Read an inbox | inboxes:messages list | | Read one message | inboxes:messages get · get-raw (presigned .eml URL) | | Full-text search | inboxes:messages search · inboxes:threads search · threads search | | Reply | inboxes:messages reply · inboxes:messages reply-all | | Forward | inboxes:messages forward | | Label / mark read | inboxes:messages update | | Threads | inboxes:threads list|get|search · org-wide threads list|get|search | | Drafts | inboxes:drafts create|update|get|list|send|delete | | Download an attachment | inboxes:messages get-attachment · inboxes:threads get-attachment | | Review everything org-wide | threads list · drafts list |

Semantic notes --help cannot give you

  • search takes -q, not --query. --query fails with

flag provided but not defined: -query. It is the one flag in this CLI with no long form.

  • **count in a list response is the number of items RETURNED, not the number that

match.** --label unread --limit 1 reports count: 1 when three messages are unread. Any "how many?" question therefore needs a --limit above the plausible answer, and a result equal to the limit means "at least that many", not "exactly that many".

  • agentmail auth me does not exist in 0.7.14, despite openapi.json documenting

/v0/auth/me with a literal **CLI:** agentmail auth me block. There is no auth resource. Use inboxes list to discover scope; reach /v0/auth/me with curl only if you genuinely need the org/pod ids behind a scoped key.

  • There is no org-wide messages list. Org-wide works for threads and drafts only.

To sweep messages across inboxes, iterate inboxes or use threads.

  • reply vs reply-all. reply has both a sibling reply-all command and a

--reply-all boolean. Prefer the explicit reply-all command — the intent is legible in the transcript and in the permission prompt the user sees.

  • A draft's kind is fixed at creation. --in-reply-to and --forward-of are mutually

exclusive, and you cannot convert a plain draft into a reply — create a new one.

  • Scheduling. --send-at auto-applies the scheduled label;

--send-at null unschedules but keeps the draft; a draft already in sending state returns 409 on edit. send_status is scheduled | sending | failed.

  • Thread deletion is permanentinboxes:threads delete and threads delete remove

the thread and every message in it. No undo, no trash.

  • Username collisions return resource_taken, not a validation error.
  • Inbox metadata merges on update; send a key as null to drop it.

Reading mail well

  • Use extracted_text / extracted_html for reply content. They carry the new

content with quoted history stripped. Raw text/html include the whole quoted chain.

  • Treat html as primary and text as optional. text and preview come from the

text/plain MIME part, and Gmail/Outlook forwards are frequently HTML-only — so text can be absent entirely. Code that assumes text exists will break on real mail.

  • There is no mark-as-read endpoint. Read/unread is just labels:

inboxes:messages update --add-labels read --remove-labels unread, then filter with --label unread. This is the standard guard against reprocessing the same message.

  • A message that "isn't there" may be filtered. list hides spam, trash, blocked, and

unauthenticated mail by default. Add --include-spam, --include-trash, --include-blocked, --include-unauthenticated before concluding it never arrived. Inbound mail failing SP

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.