From 229b1090d5b171b052c093a80db5849ce7d6ae1e Mon Sep 17 00:00:00 2001 From: Sidharth Reddy <70673367+sidharth0612@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:27:41 -0700 Subject: [PATCH 1/2] Release 0.5.0 Date the 0.5.0 entry, track the current AgentMail releases in compatibility.json (TypeScript SDK 0.5.35, Python SDK 2.0.8, CLI 1.8.0, toolkit 0.10.0 / 0.3.0), and re-export the agentmail skill's deliverability reference with metrics.query_events / queryEvents (agentmail-skills #20). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +++- compatibility.json | 10 +++++----- skills/agentmail/references/deliverability.md | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a69c06d..fd8ddf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ All notable changes to the AgentMail plugin are documented here. -## 0.5.0 - Unreleased +## 0.5.0 - 2026-10-02 - AgentID calls the services an agent creates accounts at "apps" now, not "providers". The `agentid` and `agentmail-mcp` skills use the renamed hosted MCP tools: `list_apps`, `search_apps`, `get_app`, and `connect_app` replace `list_providers`, `search_providers`, `get_provider`, and `connect_provider`, with no aliases. They take `appId` and return `appId` and `appName`. `list_accounts` keeps its name. - The authorization row for connecting is now "Connect inbox to app". - The permission `connect_app` needs is now named `app_connect`, not `provider_connect`. The `agentid` skill's 403 `missing_permission` guidance and the README name `app_connect`, the name the AgentMail API and console use. +- The `agentmail` skill's deliverability reference calls `client.metrics.query_events` / `queryEvents`; `metrics.query` is gone from the current SDKs. +- Track AgentMail SDK TypeScript 0.5.35 and Python 2.0.8, CLI 1.8.0, and AgentMail Toolkit TypeScript 0.10.0 and Python 0.3.0. ## 0.4.0 - 2026-09-29 diff --git a/compatibility.json b/compatibility.json index c4dec2c..aa77b47 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,12 +1,12 @@ { - "verifiedAt": "2026-07-21", + "verifiedAt": "2026-10-02", "agentmail": { - "typescript": "0.5.17", - "python": "0.5.8" + "typescript": "0.5.35", + "python": "2.0.8" }, - "cli": "0.7.14", + "cli": "1.8.0", "toolkit": { - "typescript": "0.5.1", + "typescript": "0.10.0", "python": "0.3.0" } } diff --git a/skills/agentmail/references/deliverability.md b/skills/agentmail/references/deliverability.md index 9ba1ba4..ebc93a2 100644 --- a/skills/agentmail/references/deliverability.md +++ b/skills/agentmail/references/deliverability.md @@ -6,7 +6,7 @@ Use this when "my agent's email didn't arrive." Find the branch that matches the - Subscribe to the `message.bounced` event (webhook or WebSocket) — a successful `send()` call only confirms AgentMail accepted the message, not that it was delivered. - Check whether `feedback_enabled` is set on the sending domain. When enabled, AgentMail routes bounce and complaint notifications to your inboxes; if it was never set, you may be missing that feedback entirely. See [admin.md](admin.md#domains). -- Self-monitor bounce rate with `client.metrics.query(event_types=["message.bounced"], ...)` / `client.metrics.query({ eventTypes: ["message.bounced"], ... })`. +- Self-monitor bounce rate with `client.metrics.query_events(event_types=["message.bounced"], ...)` / `client.metrics.queryEvents({ eventTypes: ["message.bounced"], ... })`. ## Delivered, but landing in spam From 8ec96de82a946f37da2444b856cc823ac4ba8ecf Mon Sep 17 00:00:00 2001 From: Sidharth Reddy <70673367+sidharth0612@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:43:02 -0700 Subject: [PATCH 2/2] Fix the compatibility job for Svix 2.x and CLI 1.x - Svix 2.x Webhook.verify returns nothing (undefined / None). The webhook fixtures and the skill's examples now verify, then parse the raw body (re-exported from agentmail-skills #21); works on Svix 1.x and 2.x. - agentmail-cli 1.x uses nested subcommands: the workflow checks 'inboxes messages --help' and the agentmail-cli skill drops the colon form (re-exported from agentmail-skills #21). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/compatibility.yml | 2 +- CHANGELOG.md | 2 ++ skills/agentmail-cli/SKILL.md | 34 ++++++++++++------------- skills/agentmail/references/webhooks.md | 16 +++++++----- tests/test_webhook_verification.py | 5 ++-- tests/webhook_typescript.ts | 6 ++--- tests/webhook_verification.ts | 5 ++-- 7 files changed, 38 insertions(+), 32 deletions(-) diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index 10382a9..62dcc5d 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -57,7 +57,7 @@ jobs: run: | version="$(node -p "require('./compatibility.json').cli")" npx -y "agentmail-cli@$version" inboxes --help | tee /tmp/inboxes-help - npx -y "agentmail-cli@$version" inboxes:messages --help | tee /tmp/messages-help + npx -y "agentmail-cli@$version" inboxes messages --help | tee /tmp/messages-help grep -q "get" /tmp/inboxes-help grep -q "get" /tmp/messages-help ! grep -q "retrieve" /tmp/inboxes-help diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8ddf4..902ed5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the AgentMail plugin are documented here. - The authorization row for connecting is now "Connect inbox to app". - The permission `connect_app` needs is now named `app_connect`, not `provider_connect`. The `agentid` skill's 403 `missing_permission` guidance and the README name `app_connect`, the name the AgentMail API and console use. - The `agentmail` skill's deliverability reference calls `client.metrics.query_events` / `queryEvents`; `metrics.query` is gone from the current SDKs. +- The webhook verification examples verify the signature and then parse the raw body: Svix 2.x `Webhook.verify` returns nothing (`undefined` / `None`), so the old examples dispatched an empty event. +- The `agentmail-cli` skill uses nested subcommands (`agentmail inboxes messages list`); CLI 1.x rejects the colon form (`inboxes:messages`) the skill used before. - Track AgentMail SDK TypeScript 0.5.35 and Python 2.0.8, CLI 1.8.0, and AgentMail Toolkit TypeScript 0.10.0 and Python 0.3.0. ## 0.4.0 - 2026-09-29 diff --git a/skills/agentmail-cli/SKILL.md b/skills/agentmail-cli/SKILL.md index 518b84f..5a707fd 100644 --- a/skills/agentmail-cli/SKILL.md +++ b/skills/agentmail-cli/SKILL.md @@ -12,7 +12,7 @@ npm install -g agentmail-cli export AGENTMAIL_API_KEY="am_..." ``` -Subcommands for a resource nested under another use a colon, e.g. `inboxes:messages`, `inboxes:threads`, `pods:inboxes`, `pods:threads`. +A resource nested under another is a nested subcommand, e.g. `inboxes messages`, `inboxes threads`, `pods inboxes`, `pods threads` (CLI 1.x; the colon form `inboxes:messages` is rejected). Run `agentmail --help` to list what a resource supports. ## Inboxes @@ -28,30 +28,30 @@ Confirm the exact inbox before running the destructive delete command. ## Messages and threads ```bash -agentmail inboxes:messages list --inbox-id -agentmail inboxes:messages get --inbox-id --message-id +agentmail inboxes messages list --inbox-id +agentmail inboxes messages get --inbox-id --message-id -agentmail inboxes:messages send --inbox-id \ +agentmail inboxes messages send --inbox-id \ --to "recipient@example.com" \ --subject "Hello" \ --text "Message body" # HTML body instead of plain text -agentmail inboxes:messages send --inbox-id \ +agentmail inboxes messages send --inbox-id \ --to "recipient@example.com" \ --subject "Hello" \ --html "

Hello

" -agentmail inboxes:messages reply --inbox-id \ +agentmail inboxes messages reply --inbox-id \ --message-id \ --text "Reply body" -agentmail inboxes:messages forward --inbox-id \ +agentmail inboxes messages forward --inbox-id \ --message-id \ --to "someone@example.com" -agentmail inboxes:threads list --inbox-id -agentmail inboxes:threads get --inbox-id --thread-id +agentmail inboxes threads list --inbox-id +agentmail inboxes threads get --inbox-id --thread-id ``` Use a message ID for replies. Fetch the full message before relying on body content. @@ -59,14 +59,14 @@ Use a message ID for replies. Fetch the full message before relying on body cont ## Drafts ```bash -agentmail inboxes:drafts create --inbox-id \ +agentmail inboxes drafts create --inbox-id \ --to "recipient@example.com" \ --subject "Pending approval" \ --text "Draft body" -agentmail inboxes:drafts list --inbox-id -agentmail inboxes:drafts get --inbox-id --draft-id -agentmail inboxes:drafts send --inbox-id --draft-id +agentmail inboxes drafts list --inbox-id +agentmail inboxes drafts get --inbox-id --draft-id +agentmail inboxes drafts send --inbox-id --draft-id ``` ## Pods @@ -77,11 +77,11 @@ Pods group inboxes together. agentmail pods create --name "My Pod" agentmail pods list -agentmail pods:inboxes create --pod-id --display-name "Pod Inbox" -agentmail pods:inboxes list --pod-id +agentmail pods inboxes create --pod-id --display-name "Pod Inbox" +agentmail pods inboxes list --pod-id -agentmail pods:threads list --pod-id -agentmail pods:threads get --pod-id --thread-id +agentmail pods threads list --pod-id +agentmail pods threads get --pod-id --thread-id ``` ## Webhooks diff --git a/skills/agentmail/references/webhooks.md b/skills/agentmail/references/webhooks.md index e9766b0..73386f3 100644 --- a/skills/agentmail/references/webhooks.md +++ b/skills/agentmail/references/webhooks.md @@ -63,10 +63,10 @@ if (!secret) throw new Error("AGENTMAIL_WEBHOOK_SECRET is required"); const app = express(); app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => { try { - const event = new Webhook(secret).verify( - req.body, - req.headers as Record, - ); + // verify() throws on a bad signature or stale timestamp. Svix 2.x returns nothing, so parse the + // raw body yourself once it passes. + new Webhook(secret).verify(req.body, req.headers as Record); + const event = JSON.parse(req.body.toString()); void event; // Enqueue or dispatch the verified event here. res.status(204).send(); } catch { @@ -78,6 +78,7 @@ app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => { ## Python verification ```python +import json import os from flask import Flask, request @@ -88,12 +89,15 @@ secret = os.environ["AGENTMAIL_WEBHOOK_SECRET"] @app.post("/webhooks") def receive_webhook(): + payload = request.get_data() try: - event = Webhook(secret).verify(request.get_data(), request.headers) + # Raises on a bad signature or stale timestamp. Svix 2.x returns None, so parse the raw body + # yourself once it passes. + Webhook(secret).verify(payload, request.headers) except WebhookVerificationError: return "", 400 - # Enqueue or dispatch the verified event here. + event = json.loads(payload) # Enqueue or dispatch the verified event here. return "", 204 ``` diff --git a/tests/test_webhook_verification.py b/tests/test_webhook_verification.py index 4fcbce8..3bfcf9d 100644 --- a/tests/test_webhook_verification.py +++ b/tests/test_webhook_verification.py @@ -25,8 +25,9 @@ def signed_headers(payload: str, timestamp: datetime) -> dict[str, str]: class WebhookVerificationTests(unittest.TestCase): def test_valid_signature_verifies(self) -> None: now = datetime.now(tz=timezone.utc) - event = Webhook(SECRET).verify(PAYLOAD, signed_headers(PAYLOAD, now)) - self.assertEqual(event["event_type"], "message.received") + # verify() raises on failure; Svix 2.x returns None, so the event is the parsed raw body. + Webhook(SECRET).verify(PAYLOAD, signed_headers(PAYLOAD, now)) + self.assertEqual(json.loads(PAYLOAD)["event_type"], "message.received") def test_tampered_body_is_rejected(self) -> None: headers = signed_headers(PAYLOAD, datetime.now(tz=timezone.utc)) diff --git a/tests/webhook_typescript.ts b/tests/webhook_typescript.ts index 7969c74..0c10142 100644 --- a/tests/webhook_typescript.ts +++ b/tests/webhook_typescript.ts @@ -7,10 +7,8 @@ if (!secret) throw new Error("AGENTMAIL_WEBHOOK_SECRET is required"); const app = express(); app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => { try { - const event = new Webhook(secret).verify( - req.body, - req.headers as Record, - ); + new Webhook(secret).verify(req.body, req.headers as Record); + const event = JSON.parse(req.body.toString()); void event; res.status(204).send(); } catch { diff --git a/tests/webhook_verification.ts b/tests/webhook_verification.ts index 40b8bd6..5692b8b 100644 --- a/tests/webhook_verification.ts +++ b/tests/webhook_verification.ts @@ -12,8 +12,9 @@ function signedHeaders(body: string, timestamp: Date): Record { }; } -const verified = new Webhook(secret).verify(payload, signedHeaders(payload, new Date())); -assert.deepEqual(verified, JSON.parse(payload)); +// verify() throws on failure; Svix 2.x returns undefined, so the event is the parsed raw body. +assert.doesNotThrow(() => new Webhook(secret).verify(payload, signedHeaders(payload, new Date()))); +assert.equal(JSON.parse(payload).event_type, "message.received"); assert.throws(() => new Webhook(secret).verify(payload.replace("received", "sent"), signedHeaders(payload, new Date())),