hypermail-mcp 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,16 +3,24 @@
3
3
  A **Model Context Protocol** server that lets an agent operate any of the user's
4
4
  inboxes through a single, unified tool surface.
5
5
 
6
+ > **v0.7.5** — Attachments via file path on `send_email`/`draft_email`
7
+ > (`attachments` param). `edit_draft` gains `new_attachments` and
8
+ > `remove_attachments` — `add_attachment_to_draft` is removed (23 tools now).
9
+ > Draft editing uses multi-strategy thread boundary detection for more reliable
10
+ > quoted-thread preservation. Watcher now supports script emission (`runScript`)
11
+ > alongside webhook delivery.
12
+ >
13
+ > **v0.7.4** — `inReplyTo` is now a required parameter on `send_email` and
14
+ > `draft_email` (was optional). Set it to `false` for a new email, or pass a
15
+ > message ID to thread a reply. This forces the agent to make an explicit choice
16
+ > instead of silently treating replies as new conversations.
17
+ >
6
18
  > **v0.7.3** — `edit_draft` now preserves the quoted thread history when editing
7
19
  > Outlook reply/forward drafts. Previously, editing a draft body would overwrite
8
20
  > the entire content — including the quoted thread. Now only the answer part
9
21
  > (above the spacer delimiter) is replaced.
10
22
  >
11
- > **v0.7.2** — `add_attachment_to_draft` now accepts `filePath` (absolute path to
12
- a local file) as an alternative to `contentBytes`. The file is read from disk and
13
- base64-encoded automatically, the MIME type is inferred from the extension, and
14
- `name` defaults to the file's basename.
15
- >> **v0.7.1** — Every config field is now settable via a dedicated
23
+ > **v0.7.1** — Every config field is now settable via a dedicated
16
24
  > `HYPERMAIL_*` env var. Legacy env vars (`MS_CLIENT_ID`, `MS_TENANT_ID`,
17
25
  > `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`) still work as fallbacks. See
18
26
  > [Environment Variables](#environment-variables) for the full reference.
@@ -208,6 +216,10 @@ still work as fallbacks for backward compatibility.
208
216
  | `HYPERMAIL_WATCH_WEBHOOK_URL` | `watch.webhook.url` | `string` |
209
217
  | `HYPERMAIL_WATCH_WEBHOOK_RETRY_MAX_ATTEMPTS` | `watch.webhook.retry.maxAttempts` | `int` |
210
218
  | `HYPERMAIL_WATCH_WEBHOOK_RETRY_BASE_DELAY_MS` | `watch.webhook.retry.baseDelayMs` | `int` |
219
+ | `HYPERMAIL_WATCH_SCRIPT_COMMAND` | `watch.script.command` | `string` |
220
+ | `HYPERMAIL_WATCH_SCRIPT_TIMEOUT_MS` | `watch.script.timeoutMs` | `int` |
221
+ | `HYPERMAIL_WATCH_SCRIPT_RETRY_MAX_ATTEMPTS` | `watch.script.retry.maxAttempts` | `int` |
222
+ | `HYPERMAIL_WATCH_SCRIPT_RETRY_BASE_DELAY_MS` | `watch.script.retry.baseDelayMs` | `int` |
211
223
 
212
224
  **Priority order:** CLI flags > config file > `HYPERMAIL_*` env var > hardcoded default.
213
225
 
@@ -232,11 +244,10 @@ account store.
232
244
  | `archive_email` | `account`, `id` | Move a message to the Archive folder. Disabled under `--read-only`. |
233
245
  | `trash_email` | `account`, `id` | Move a message to Deleted Items (trash). Disabled under `--read-only`. |
234
246
  | `move_email` | `account`, `id`, `destination` | Move to any folder by well-known name (`inbox`, `drafts`, etc.) or custom folder ID. Disabled under `--read-only`. |
235
- | `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature?`, `inReplyTo?`, `replyAll?`, `forwardMessageId?` | Send an email. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Appends signature when `include_signature` is true. `inReplyTo` sends as threaded reply; `forwardMessageId` sends as forward. Disabled under `--read-only`. |
236
- | `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature?`, `inReplyTo?`, `replyAll?`, `forwardMessageId?` | Save as draft instead of sending. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Returns the draft message ID and HTML body (`draftHtml`). Disabled under `--read-only`. |
237
- | `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `format?`, `include_signature?` | Edit an existing draft by ID. Only provided fields are updated. `format` only meaningful when `body` is provided. Returns the updated draft ID and HTML body (`draftHtml`). Disabled under `--read-only`. |
247
+ | `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Send an email. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Appends signature when `include_signature` is true. `inReplyTo` sends as threaded reply; `forwardMessageId` sends as forward. `inReplyTo` is required — set to `false` for new emails. `attachments` is an optional array of `{filePath, name?}` — files are read from disk and encoded automatically. Disabled under `--read-only`. |
248
+ | `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Save as draft instead of sending. Same params as `send_email` including `attachments`. Returns the draft message ID and HTML body (`draftHtml`). `inReplyTo` is required — set to `false` for new emails. Disabled under `--read-only`. |
249
+ | `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `format?`, `include_signature?`, `new_attachments?`, `remove_attachments?` | Edit an existing draft by ID. Only provided fields are updated. `new_attachments` adds files (`{filePath, name?}[]`); `remove_attachments` removes by attachment ID (`string[]`). Returns the updated draft ID, HTML body (`draftHtml`), and attachment metadata. Disabled under `--read-only`. |
238
250
  | `send_draft` | `account`, `id` | Send an existing draft email by ID. Use with draft IDs returned by `draft_email` or `edit_draft`. Disabled under `--read-only`. |
239
- | `add_attachment_to_draft` | `account`, `id`, `name?`, `contentBytes?`, `filePath?`, `contentType?` | Attach a file to an existing draft by ID. Provide `contentBytes` (base64) or `filePath` (absolute path — auto-encodes). Disabled under `--read-only`. |
240
251
  | `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
241
252
  | `create_folder` | `account`, `displayName`, `parentFolderId?` | Create a new mail folder under root (default) or the given parent. Disabled under `--read-only`. |
242
253
  | `delete_folder` | `account`, `folderId` | Delete a mail folder by ID. Disabled under `--read-only`. |
@@ -247,9 +258,9 @@ account store.
247
258
  ## Email Watch
248
259
 
249
260
  When enabled, hypermail-mcp runs a background poll loop that scans inboxes for
250
- new messages and POSTs each one to a configurable webhook URL. Intended for
251
- push-based email triage — downstream agents (e.g. Mastra) receive full email
252
- content without polling.
261
+ new messages and delivers each one via webhook POST and/or script execution.
262
+ Intended for push-based email triage — downstream agents (e.g. Mastra) receive
263
+ full email content without polling.
253
264
 
254
265
  ```jsonc
255
266
  {
@@ -259,6 +270,11 @@ content without polling.
259
270
  "webhook": {
260
271
  "url": "http://localhost:3000/api/email-webhook",
261
272
  "retry": { "maxAttempts": 5, "baseDelayMs": 1000 }
273
+ },
274
+ "script": {
275
+ "command": "node /path/to/email-handler.js",
276
+ "timeoutMs": 30000,
277
+ "retry": { "maxAttempts": 3, "baseDelayMs": 1000 }
262
278
  }
263
279
  }
264
280
  }
@@ -271,16 +287,25 @@ content without polling.
271
287
  | `watch.webhook.url` | — | Endpoint that receives `POST` with `EmailFull` JSON |
272
288
  | `watch.webhook.retry.maxAttempts` | `5` | Max delivery attempts (1–10) |
273
289
  | `watch.webhook.retry.baseDelayMs` | `1000` | Base backoff delay (× 2^attempt) |
290
+ | `watch.script.command` | — | Shell command spawned with email JSON on stdin |
291
+ | `watch.script.timeoutMs` | `30000` | Max script runtime before SIGKILL |
292
+ | `watch.script.retry.maxAttempts` | `3` | Max script delivery attempts |
293
+ | `watch.script.retry.baseDelayMs` | `1000` | Base backoff delay (× 2^attempt) |
274
294
 
275
295
  **Behavior:**
276
296
  - Polls **all accounts** in the store, **inbox only**.
277
297
  - Detects new emails via `lastSeenIds` (capped at 200) stored in the encrypted
278
298
  account file — no duplicate emits across restarts.
279
- - One `POST` per email (full body: subject, sender, text, HTML, attachments
280
- metadata, thread ID via `EmailFull`).
281
- - Delivery uses exponential backoff (`baseDelay × 2^attempt`). Retries on
282
- non-2xx responses and connection errors. Logs and moves on after
283
- `maxAttempts` exhausted never blocks the poll loop.
299
+ - Two delivery modes (can be used together):
300
+ - **Webhook:** One `POST` per email (full body as `EmailFull` JSON).
301
+ - **Script:** Spawns a shell command with the `EmailFull` JSON piped to
302
+ stdin. The script receives `conversationId`, `subject`, `from`, `toRecipients`,
303
+ `body`, `isRead`, `sentDateTime`, `attachments`, plus an `account` field.
304
+ - Both modes use exponential backoff (`baseDelay × 2^attempt`). Retries on
305
+ failures (non-2xx for webhook, non-zero exit for script). Logs and moves on
306
+ after `maxAttempts` exhausted — never blocks the poll loop.
307
+ - Each delivery mode is fire-and-forget — the poll loop continues while
308
+ delivery runs in the background.
284
309
  - Works in both **stdio** and **HTTP** transport modes — the poll interval
285
310
  fires normally alongside MCP message handling.
286
311