xuan25/dsh-email ↗★ 0

dsh-email

通过IMAP/SMTP收发、搜索和管理电子邮件 适合需要让Agent直接读取、检索、标记或发送电子邮件的用户。

套件
dsh-email
相容性
待驗證
Harness 依賴範圍
>=0.1.0-rc.6
Cordis 依賴範圍
>=4.0.2
版本
0.1.0
授權
MIT
最近更新
2026年10月15日

同名套件的其他儲存庫

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:xuan25/dsh-email

Configuration

The env block is static per process boot; env changes take effect on a host restart. One account needs four variables, the rest are optional:

VariableRequiredMeaning
EMAIL__USERyeslogin name shared by IMAP and SMTP (usually the mailbox address)
EMAIL__PASSyespassword or the provider's app-specific password / auth code
EMAIL__IMAP_HOSTyesIMAP server host
EMAIL__IMAP_PORTnoIMAP port, default 993
EMAIL__IMAP_SECUREnoIMAP TLS mode: tls = implicit TLS / starttls = mandatory STARTTLS upgrade / none = full plaintext, no STARTTLS attempted; default tls
EMAIL__IMAP_ALLOW_INSECURE_TLSnocertificate validation: false = strict (default) / true = accept untrusted / self-signed certificates (only meaningful with the tls / starttls modes)
EMAIL__SMTP_HOSTyesSMTP server host
EMAIL__SMTP_PORTnoSMTP port, default 587
EMAIL__SMTP_SECUREnoSMTP TLS mode: the same three values as the IMAP side; default starttls
EMAIL__SMTP_ALLOW_INSECURE_TLSnosame as the IMAP side
EMAIL__FROMnosender address, default = EMAIL__USER
EMAIL__FROM_NAMEnosender display name
EMAIL__SENT_FOLDERnowhen set, sends also copy the sent message into this folder
EMAIL__SENT_FOLDER_AUTOCREATEnodefault true: when the SENT_FOLDER copy fails because the folder is missing, create it (the exact name INBOX is never created) and retry once; false = never create
EMAIL__ALLOW_DELETEnodefault false (fail-closed): the gate for the delete and delete_folder verbs on this account; true = the agent may delete messages and mailboxes on it
EMAIL__TIMEOUT_MSnoper-call timeout budget, default 120000
EMAIL__SESSION_RETENTIONnodefault 0 (no offload): the retention window for trigger sessions on this account (env-only) - a non-negative integer ms or a duration of 1-2 segments in d/h/m/s/ms descending order (e.g. 24h, 4h30m, 90s, 5000ms; a bare numeric string is invalid); invalid values warn at boot and resolve to 0 (see Session offload)
EMAIL__TRIGGER_ENABLEnodefault false: enable the new-mail trigger watcher on this account (see New mail trigger)
EMAIL__TRIGGER_PROMPTrequired when TRIGGER_ENABLE=truethe operator policy injected verbatim into every trigger session
EMAIL__TRIGGER_FOLDERnothe folder the watcher listens on, default INBOX
EMAIL__TRIGGER_IDLE_MSnoheartbeat resync period in ms, default 600000 (lower bound 1000, rejected not clamped)
EMAIL__TRIGGER_MAX_CONCURRENTnomax concurrent trigger sessions, default 1
EMAIL__TRIGGER_QUEUE_MAXnomax queued new mails, default 100 (overflow stays UNSEEN and is retried)
EMAIL__TRIGGER_PRESETnonamed agent preset for trigger sessions; absent = the deployment default
EMAIL__TRIGGER_REASONING_EFFORTnothe reasoning effort of the trigger sessions: a member of the closed set off/minimal/low/medium/high/xhigh/max (validated at load; an invalid value makes the trigger malformed - warned, no watcher, the account stays usable; the framework re-checks the per-model support of the route at first request); absent = the framework provider default
EMAIL_DEFAULT_ACCOUNTnothe account used when a call names no account
EMAIL_READ_BODY_LIMITnoread body truncation budget in characters, default 20000
EMAIL_OFFLOAD_BATCHnoper-sweep offload budget (max trigger sessions one sweep may move to the archive), default 50; an invalid value warns and falls back to 50, never disables the sweep

The `` segment is the account name uppercased; the account name must match ^[a-z][a-z0-9-]{0,31}$. Accounts missing any required variable are dropped with a boot warning (boot never fails). Multiple accounts are supported.

Example:

EMAIL_DEFAULT_ACCOUNT=local
EMAIL_LOCAL_USER=agent@local.example
EMAIL_LOCAL_PASS=
EMAIL_LOCAL_IMAP_HOST=mail.local.example
EMAIL_LOCAL_SMTP_HOST=mail.local.example
EMAIL_LOCAL_SENT_FOLDER=Sent

Config layer (cordis patch)

An optional config layer sits on top of the env block: a patch entry targeting the plugin id dsh-email (the profile's cordis.patch.yml, the user-global ~/.dsh/cordis.patch.yml, or a --patch overlay) carries a config object whose keys are camelCase mirrors of the env fields - defaultAccount, readBodyLimit, and accounts (a map of account name to user, pass, imapHost, imapPort, imapSecure, imapAllowInsecureTls, smtpHost, smtpPort, smtpSecure, smtpAllowInsecureTls, from, fromName, sentFolder, sentFolderAutocreate, allowDelete, timeoutMs, plus the trigger fields triggerEnable, triggerPrompt, triggerFolder, triggerIdleMs, triggerMaxConcurrent, triggerQueueMax, triggerPreset, triggerReasoningEffort). Every key is optional.

- id: dsh-email
  config:
    readBodyLimit: 500
    accounts:
      default:
        imapPort: 1043
  • One precedence chain: delivered value > env value > built-in default; keys the layer omits resolve as the env block, so config: {} (or no entry) is exactly the env-only behavior.
  • A patch targets a row by id and replaces its whole config - no deep merge, so a layer that overrides one field restates the fields it keeps (layer order: the plugin's own default entry, the profile patch, the user-global patch, any --patch overlay).
  • The layer is validated before the plugin starts: an unknown key or an out-of-range value fails the plugin entry at load; incomplete account data only warns, as in the env block.
  • With the live patch reload, editing the patch file re-runs the plugin without a restart; otherwise the change applies at the next restart.
  • The agent edits the patch file as a plain file operation under its own permissions; the plugin provides no tool for changing its own configuration. The accounts verb shows values only.

Usage

The plugin adds the email tool and the dsh-email skill (verb reference, env contract details, limits, troubleshooting). The sixteen verbs, one line each:

email { verb: "accounts" }                                  # configured accounts (local, no network)
email { verb: "verify" }                                    # live connectivity + credentials, zero side effects
email { verb: "folders" }                                    # mailbox names
email { verb: "list", flagged: true }                       # newest-first summaries, flagged only
email { verb: "list_unseen" }                                # the unread view
email { verb: "search", since: "2026-10-01", subject: "ci" } # structured criteria
email { verb: "read", uid: 42 }                              # parts + body (PEEK: does not mark seen)
email { verb: "mark", uids: [42], seen: true }               # set/clear seen and flagged
email { verb: "save_part", uid: 42, part: 1 }                # write one part to disk
email { verb: "send", to: "a@b.c", subject: "hi", text: "..." }  # plus html / attachments / cc / bcc
email { verb: "reply", uid: 42, text: "thanks" }             # quotes the original by default
email { verb: "forward", uid: 42, to: "d@e.f" }              # includes the original by default
email { verb: "create_folder", folder: "Archive" }           # idempotent; INBOX is reserved
email { verb: "move", folder: "INBOX", dest: "Archive", uids: [42] } # dest must exist; not gated
email { verb: "delete", uids: [42] }                         # gated: EMAIL__ALLOW_DELETE
email { verb: "delete_folder", folder: "Archive" }           # gated: EMAIL__ALLOW_DELETE

Reads never change server-side flags; mark is the only way to write flags. Failures carry an error code for transport origins (auth / network / tls / timeout / protocol / server); local validation errors carry none.