agentmail-to/dsh-agentmail
dsh-agentmail
Give a DeepSeek Harness agent its own email inbox — inbound mail bound to one session per email thread. A dsh plugin.
Install
npx @deepseek-ai/dsh plugin --profile demo add dsh-agentmailRestart `dsh web` after install. Bundle APIs can change during the developer preview.
README badge
[](https://dshhub.dev/plugins/dsh-agentmail)Paste this into your README. The star count updates with every catalog sync.
From the README
Excerpt from agentmail-to/dsh-agentmail, cleaned of badges and images.
<a href="#two-ways-to-install">Install</a> · <a href="#tools">Tools</a> · <a href="#how-thread-binding-works">Thread binding</a> · <a href="#follow-ups-why-not-schedule_create">Follow-ups</a> · <a href="#security">Security</a> · <a href="#configuration">Config</a>
Two ways to install
The 5-minute on-ramp: the built-in MCP client
The harness ships @deepseek-ai/dsh-mcp-client, and AgentMail runs an MCP server. Zero code:
- id: mcp-agentmail
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: agentmail
transport: streamable-http
url: https://mcp.agentmail.to/mcp
headers:
Authorization: !!js '`Bearer ${process.env.AGENTMAIL_API_KEY}`'
That gives you mcp__agentmail__send_message and friends today. It does not give you the
four things below.
This plugin
export AGENTMAIL_API_KEY=...
dsh plugin --profile demo add dsh-agentmail # or: add github:agentmail-to/dsh-agentmail#<sha>
dsh --profile demo
| Capability | MCP client | This plugin |
|---|---|---|
| Send, read and search tools | yes | yes |
| Inbound mail reaches the agent | no | yes |
| Bounces reported back, so a failed send isn't assumed delivered | no | yes |
| Approval gate and recipient allowlist on outbound | no | yes |
| Follow-ups that survive the conversation ending | no | yes |
| Inbox identity and untrusted-content rules in the system prompt | no | yes |
Local development
npm install && npm run build
dsh web --patch ./cordis.patch.yml
What gets mounted
Four independent plugins, so a deployment can drop any one from its own patch layer:
| Entry | Injects | Role |
|---|---|---|
dsh-agentmail/tools | tools | The model-facing tool surface |
dsh-agentmail/identity | systemPrompt | Inbox identity and the untrusted-content rules |
dsh-agentmail/approval | tools | Recipient allowlist + human approval on outbound |
dsh-agentmail/inbound | agents | Inbound mail, thread sessions, follow-up sweep |
Tools
Eleven, curated rather than a mirror of the REST API — every registered schema is paid on every model request.
| Tool | Notes |
|---|---|
agentmail_list_inboxes | |
agentmail_create_inbox | |
agentmail_list_threads | Cursor-paged, label-filterable |
agentmail_get_thread | Bodies truncated to maxBodyChars |
agentmail_search | Relevance-ranked full text |
agentmail_send_message | Idempotency-keyed on the tool call id |
agentmail_reply | replyAll opt-in; idempotency-keyed |
agentmail_create_draft | The human-in-the-loop path |
agentmail_send_draft | |
agentmail_update_labels | Workflow state |
agentmail_followup | Due-date label; wakes a cold thread session |
Canonical returns are a programmatic API — ids and fields, never prose to re-parse — so Code
Mode can drive batch triage through await tools.agentmail_list_threads(...) in one call.
How thread binding works
The session id is a total function of the thread id:
sessionId = "agentmail-" + threadId
flowchart LR
M([inbound mail<br/>on thread T]) --> Q{"session<br/>agentmail-T ?"}
Q -->|live| L[inject the new message]
Q -->|persisted on disk| R[resume, then inject]
Q -->|neither| C[create, then seed<br/>from the AgentMail API]
L --> A([agent handling thread T])
R --> A
C --> A
Inbound mail on thread T takes one of three branches:
…
