mail

package
v1.0.62 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package mail provides declarative shortcuts for the DingTalk mail (邮箱) service: mailbox / message / draft / thread / folder / tag / user / attachment / template / contact / auto-reply / rule operations. Each shortcut maps 1:1 onto an MCP tool declared in internal/helpers/mail.go.

Index

Constants

This section is empty.

Variables

View Source
var ContactList = shortcut.Shortcut{
	Service:     "mail",
	Command:     "+contact-list",
	Product:     "mail",
	Description: "列出指定邮箱的所有邮件联系人",
	Intent:      "当你想查看某邮箱通讯录里有哪些联系人、或需要取得联系人 ID 以便更新或删除时使用;传入邮箱和每页数量,返回联系人列表,支持分页。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "mail",
			Name:           "shortcut_contact_list",
			CanonicalPath:  "mail.shortcut_contact_list",
			CLIPath:        "mail +contact-list",
			PrimaryCLIPath: "mail +contact-list",
		},
		Description: "列出指定邮箱的所有邮件联系人",
		Parameters:  []contract.ParamDecl{{Name: "limit", Property: "limit"}},
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "列出指定邮箱的所有邮件联系人",
			UseWhen:      []string{"当你想查看某邮箱通讯录里有哪些联系人、或需要取得联系人 ID 以便更新或删除时使用;传入邮箱和每页数量,返回联系人列表,支持分页。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws mail +contact-list --email user@company.com --limit 20"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Desc: "用户邮箱地址", Required: true},
		{Name: "limit", Type: shortcut.FlagString, Desc: "每页返回数量,必须是 1-100 之间的整数", Required: true},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标,取自响应中的 nextCursor"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "--limit 必须在 1-100 之间"}},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateStringPageSize(rt, "limit", true)
	},
	Tips: []string{
		`dws mail +contact-list --email user@company.com --limit 20`,
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		size, err := mailStringPageSize(rt, "limit", true)
		if err != nil {
			return err
		}
		params := map[string]any{
			"email": rt.Str("email"),
			"size":  size,
		}
		if rt.Changed("cursor") {
			params["cursor"] = rt.Str("cursor")
		}
		data, err := rt.CallMCPData("mail", "list_user_mail_contacts", params)
		if err != nil {
			return err
		}
		contacts, err := mailProjectCollection(data, "mail/list_user_mail_contacts", "contacts", []string{"id"}, map[string][]string{
			"id": {"id"}, "contactEmail": {"contactEmail", "email"}, "displayName": {"displayName", "name"},
		})
		if err != nil {
			return err
		}
		complete, next, err := mailPage(data, "mail/list_user_mail_contacts", "", rt.Str("cursor"))
		if err != nil {
			return err
		}
		return mailOutputPage(rt, "contacts", contacts, complete, next)
	},
}

ContactCreate 创建邮件联系人。 ContactList 列举邮件联系人。

View Source
var DraftCreate = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+draft-create", Product: "mail",
	Description: "创建邮件草稿并按 messageId 读回验证", Intent: "撰写一封新邮件但暂不发送;取得稳定草稿 ID 后读取同一对象,并核对主题和正文。",
	Risk: shortcut.RiskWrite, Safety: mailWriteSafety("non_idempotent"),
	Contract: mailWriteContract("+draft-create", "创建邮件草稿并按 messageId 读回验证", "撰写一封新邮件但暂不发送;取得稳定草稿 ID 后读取同一对象,并核对主题和正文。", []contract.ParamDecl{{Name: "from", Property: "from"}, {Name: "to", Property: "toRecipients"}, {Name: "cc", Property: "ccRecipients"}, {Name: "body", Property: "body"}}, `dws mail +draft-create --from user@company.com --subject "草稿" --body "正文" --format json`),
	Flags: []shortcut.Flag{
		{Name: "from", Type: shortcut.FlagString, Required: true, Desc: "发件邮箱"},
		{Name: "to", Type: shortcut.FlagStringSlice, Desc: "收件邮箱,可多次指定或逗号分隔"},
		{Name: "cc", Type: shortcut.FlagStringSlice, Desc: "抄送邮箱,可多次指定或逗号分隔"},
		{Name: "subject", Type: shortcut.FlagString, Required: true, Desc: "草稿主题"},
		{Name: "body", Type: shortcut.FlagString, Desc: "草稿正文"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"to", "cc", "subject", "body"}}},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{"from": rt.Str("from"), "subject": rt.Str("subject")}
		if recipients := mailRecipients(rt.StrSlice("to")); len(recipients) > 0 {
			params["toRecipients"] = recipients
		}
		if recipients := mailRecipients(rt.StrSlice("cc")); len(recipients) > 0 {
			params["ccRecipients"] = recipients
		}
		if rt.Changed("body") {
			params["body"] = rt.Str("body")
		}
		if rt.DryRun() {
			return rt.Output(map[string]any{"value": map[string]any{"dryRun": true, "executed": false, "operation": "mail/create_draft"}})
		}
		written, err := rt.CallMCPWriteDataStrict("mail", "create_draft", params)
		if err != nil {
			return err
		}
		receipt, err := mailDraftMessage(written, "mail/create_draft")
		if err != nil {
			return err
		}
		id := mailFirstString(receipt, "id")
		verified, err := mailReadMessage(rt, rt.Str("from"), id)
		if err != nil {
			return err
		}
		if mailFirstString(verified, "subject") != rt.Str("subject") {
			return mailResponseError("mail/create_draft", "verification_mismatch", "草稿读回主题与请求不一致")
		}
		if rt.Changed("body") && mailFirstString(verified, "markdownBody", "body") != rt.Str("body") {
			return mailResponseError("mail/create_draft", "verification_mismatch", "草稿读回正文与请求不一致")
		}
		if err := mailVerifySender(verified, "mail/create_draft", rt.Str("from")); err != nil {
			return err
		}
		if rt.Changed("to") {
			if err := mailVerifyAddresses(verified, "mail/create_draft", "toRecipients", mailRecipients(rt.StrSlice("to"))); err != nil {
				return err
			}
		}
		if rt.Changed("cc") {
			if err := mailVerifyAddresses(verified, "mail/create_draft", "ccRecipients", mailRecipients(rt.StrSlice("cc"))); err != nil {
				return err
			}
		}
		return rt.Output(map[string]any{"value": verified})
	},
}
View Source
var DraftEdit = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+draft-edit", Product: "mail",
	Description: "更新已有草稿并精确读回验证", Intent: "修改尚未发送的草稿;要求至少提供一个变化字段,并在写后按原 messageId 核对修改内容。",
	Risk: shortcut.RiskWrite, Safety: mailWriteSafety("unknown"),
	Contract: mailWriteContract("+draft-edit", "更新已有草稿并精确读回验证", "修改尚未发送的草稿;要求至少提供一个变化字段,并在写后按原 messageId 核对修改内容。", []contract.ParamDecl{{Name: "id", Property: "id"}, {Name: "from", Property: "from"}, {Name: "to", Property: "toRecipients"}, {Name: "cc", Property: "ccRecipients"}, {Name: "body", Property: "body"}}, `dws mail +draft-edit --from user@company.com --id <messageId> --subject "新主题" --format json`),
	Flags: []shortcut.Flag{
		{Name: "from", Type: shortcut.FlagString, Required: true, Desc: "发件邮箱"},
		{Name: "id", Type: shortcut.FlagString, Required: true, Desc: "草稿 messageId"},
		{Name: "to", Type: shortcut.FlagStringSlice, Desc: "新收件邮箱"},
		{Name: "cc", Type: shortcut.FlagStringSlice, Desc: "新抄送邮箱"},
		{Name: "subject", Type: shortcut.FlagString, Desc: "新主题"},
		{Name: "body", Type: shortcut.FlagString, Desc: "新正文"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"to", "cc", "subject", "body"}}},
	Execute: func(rt *shortcut.RuntimeContext) error {
		if !rt.Changed("to") && !rt.Changed("cc") && !rt.Changed("subject") && !rt.Changed("body") {
			return apperrors.NewValidation("至少指定 --to、--cc、--subject、--body 之一")
		}
		params := map[string]any{"from": rt.Str("from"), "id": rt.Str("id")}
		if rt.Changed("to") {
			params["toRecipients"] = mailRecipients(rt.StrSlice("to"))
		}
		if rt.Changed("cc") {
			params["ccRecipients"] = mailRecipients(rt.StrSlice("cc"))
		}
		if rt.Changed("subject") {
			params["subject"] = rt.Str("subject")
		}
		if rt.Changed("body") {
			params["body"] = rt.Str("body")
		}
		if rt.DryRun() {
			return rt.Output(map[string]any{"value": map[string]any{"dryRun": true, "executed": false, "operation": "mail/update_draft"}})
		}
		written, err := rt.CallMCPWriteDataStrict("mail", "update_draft", params)
		if err != nil {
			return err
		}
		receipt, err := mailDraftMessage(written, "mail/update_draft")
		if err != nil {
			return err
		}
		if mailFirstString(receipt, "id") != rt.Str("id") {
			return mailResponseError("mail/update_draft", "identity_mismatch", "更新回执的 messageId 与请求不一致")
		}
		verified, err := mailReadMessage(rt, rt.Str("from"), rt.Str("id"))
		if err != nil {
			return err
		}
		if rt.Changed("subject") && mailFirstString(verified, "subject") != rt.Str("subject") {
			return mailResponseError("mail/update_draft", "verification_mismatch", "草稿读回主题与请求不一致")
		}
		if rt.Changed("body") && mailFirstString(verified, "markdownBody", "body") != rt.Str("body") {
			return mailResponseError("mail/update_draft", "verification_mismatch", "草稿读回正文与请求不一致")
		}
		if err := mailVerifySender(verified, "mail/update_draft", rt.Str("from")); err != nil {
			return err
		}
		if rt.Changed("to") {
			if err := mailVerifyAddresses(verified, "mail/update_draft", "toRecipients", mailRecipients(rt.StrSlice("to"))); err != nil {
				return err
			}
		}
		if rt.Changed("cc") {
			if err := mailVerifyAddresses(verified, "mail/update_draft", "ccRecipients", mailRecipients(rt.StrSlice("cc"))); err != nil {
				return err
			}
		}
		return rt.Output(map[string]any{"value": verified})
	},
}
View Source
var FolderList = shortcut.Shortcut{
	Service:     "mail",
	Command:     "+folder-list",
	Product:     "mail",
	Description: "列出顶层文件夹或指定父文件夹下的子文件夹",
	Intent:      "当你需要了解某个邮箱有哪些文件夹、或要取得文件夹 ID 以便移动邮件、按文件夹列信/建规则时使用;传入邮箱(可选父文件夹 ID 查子级),返回文件夹列表及其 ID。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "mail",
			Name:           "shortcut_folder_list",
			CanonicalPath:  "mail.shortcut_folder_list",
			CLIPath:        "mail +folder-list",
			PrimaryCLIPath: "mail +folder-list",
		},
		Description: "列出顶层文件夹或指定父文件夹下的子文件夹",
		Parameters:  []contract.ParamDecl{{Name: "folder", Property: "folder"}},
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "列出顶层文件夹或指定父文件夹下的子文件夹",
			UseWhen:      []string{"当你需要了解某个邮箱有哪些文件夹、或要取得文件夹 ID 以便移动邮件、按文件夹列信/建规则时使用;传入邮箱(可选父文件夹 ID 查子级),返回文件夹列表及其 ID。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws mail +folder-list --email user@company.com"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Desc: "邮件所属邮箱地址,不能为空", Required: true},
		{Name: "folder", Type: shortcut.FlagString, Desc: "父文件夹 ID,不传则返回顶层文件夹"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"email"}, Description: "不能为空"}},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateRequiredText(rt, "email")
	},
	Tips: []string{
		`dws mail +folder-list --email user@company.com`,
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{
			"email": rt.Str("email"),
		}
		if rt.Changed("folder") {
			params["folderId"] = rt.Str("folder")
		}
		data, err := rt.CallMCPData("mail", "list_folders", params)
		if err != nil {
			return err
		}
		folders, err := mailProjectCollection(data, "mail/list_folders", "folders", []string{"id"}, map[string][]string{
			"id": {"id"}, "name": {"displayName"}, "parentId": {"parentFolderId"},
		})
		if err != nil {
			return err
		}
		return rt.Output(mailBusinessCollectionPayload("folders", folders))
	},
}

FolderList 列举邮件文件夹。

View Source
var Message = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+message", Product: "mail",
	Description: "读取一封邮件的完整正文与附件元数据", Intent: "已知单个 messageId,需要读取完整正文和附件元数据时使用;自动解析当前邮箱,并要求返回邮件 ID 与请求精确一致。",
	Risk: shortcut.RiskRead, Safety: mailReadSafety(),
	Contract: mailReadContract("+message", "读取一封邮件的完整正文与附件元数据", "已知单个 messageId,需要读取完整正文和附件元数据时使用;自动解析当前邮箱,并要求返回邮件 ID 与请求精确一致。", mailObjectResult("身份匹配的单封邮件详情"), []contract.ParamDecl{{Name: "id", Property: "messageId"}}, `dws mail +message --id <messageId> --format json`),
	Flags: []shortcut.Flag{
		{Name: "id", Type: shortcut.FlagString, Required: true, Desc: "邮件 messageId,不能为空"},
		{Name: "email", Type: shortcut.FlagString, Desc: "邮箱地址;不传时自动取当前身份首个邮箱"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"id"}, Description: "不能为空"}},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateRequiredText(rt, "id")
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		email, err := mailResolveMailbox(rt)
		if err != nil {
			return err
		}
		message, err := mailReadMessage(rt, email, rt.Str("id"))
		if err != nil {
			return err
		}
		return rt.Output(map[string]any{"value": message})
	},
}
View Source
var Messages = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+messages", Product: "mail",
	Description: "按请求顺序读取多封邮件并逐封验证身份", Intent: "需要一次读取多个 messageId 时使用;按输入顺序逐封读取,任何缺失、错型或身份不匹配都会使整次任务失败。",
	Risk: shortcut.RiskRead, Safety: mailReadSafety(),
	Contract: mailReadContract("+messages", "按请求顺序读取多封邮件并逐封验证身份", "需要一次读取多个 messageId 时使用;按输入顺序逐封读取,任何缺失、错型或身份不匹配都会使整次任务失败。", mailCollectionResult("messages", "身份匹配且保持请求顺序的邮件详情"), []contract.ParamDecl{{Name: "ids", Property: "messageIds"}}, `dws mail +messages --ids <id1>,<id2> --format json`),
	Flags: []shortcut.Flag{
		{Name: "ids", Type: shortcut.FlagStringSlice, Required: true, Desc: "邮件 messageId 列表,1-100 个且每项不能为空"},
		{Name: "email", Type: shortcut.FlagString, Desc: "邮箱地址;不传时自动取当前身份首个邮箱"},
	},
	Constraints: []shortcut.Constraint{{
		Kind: shortcut.ConstraintCustom, Flags: []string{"ids"},
		Description: "1-100 个且不能为空",
	}},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateMessageIDs(rt.StrSlice("ids"))
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		ids := rt.StrSlice("ids")
		email, err := mailResolveMailbox(rt)
		if err != nil {
			return err
		}
		messages := make([]map[string]any, 0, len(ids))
		for index, id := range ids {
			id = strings.TrimSpace(id)
			if id == "" {
				return apperrors.NewValidation(fmt.Sprintf("--ids 第 %d 项为空", index))
			}
			message, err := mailReadMessage(rt, email, id)
			if err != nil {
				return err
			}
			messages = append(messages, message)
		}
		return rt.Output(mailBusinessCollectionPayload("messages", messages))
	},
}
View Source
var TagList = shortcut.Shortcut{
	Service:     "mail",
	Command:     "+tag-list",
	Product:     "mail",
	Description: "列出指定邮箱下的所有邮件标签",
	Intent:      "当你要查看邮箱里有哪些标签、或需要取得标签 ID 以便给邮件/会话加标签时使用;传入邮箱地址,返回全部邮件标签及其 ID。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "mail",
			Name:           "shortcut_tag_list",
			CanonicalPath:  "mail.shortcut_tag_list",
			CLIPath:        "mail +tag-list",
			PrimaryCLIPath: "mail +tag-list",
		},
		Description: "列出指定邮箱下的所有邮件标签",
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "列出指定邮箱下的所有邮件标签",
			UseWhen:      []string{"当你要查看邮箱里有哪些标签、或需要取得标签 ID 以便给邮件/会话加标签时使用;传入邮箱地址,返回全部邮件标签及其 ID。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws mail +tag-list --email user@company.com"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Desc: "用户的邮箱地址", Required: true},
	},
	Tips: []string{
		`dws mail +tag-list --email user@company.com`,
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		data, err := rt.CallMCPData("mail", "list_tags", map[string]any{
			"email": rt.Str("email"),
		})
		if err != nil {
			return err
		}
		tags, err := mailProjectCollection(data, "mail/list_tags", "tags", []string{"id"}, map[string][]string{
			"id": {"id"}, "name": {"name"}, "parentId": {"parentId"},
		})
		if err != nil {
			return err
		}
		return rt.Output(mailBusinessCollectionPayload("tags", tags))
	},
}

TagList 列举邮件标签。

View Source
var TemplateCreate = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+template-create", Product: "mail",
	Description: "创建个人邮件模板并按模板 ID 读回", Intent: "保存可复用邮件主题和正文;创建后按稳定模板 ID 读取并核对名称、主题、正文和服务端回显的草稿状态。",
	Risk: shortcut.RiskWrite, Safety: mailWriteSafety("non_idempotent"),
	Contract: mailWriteContract("+template-create", "创建个人邮件模板并按模板 ID 读回", "保存可复用邮件主题和正文;创建后按稳定模板 ID 读取并核对名称、主题、正文和服务端回显的草稿状态。", []contract.ParamDecl{{Name: "email", Property: "email"}, {Name: "body", Property: "body"}, {Name: "is-draft", Property: "isDraft", InterfaceType: "boolean"}}, `dws mail +template-create --email user@company.com --name "模板" --subject "主题" --body "正文" --format json`),
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Required: true, Desc: "模板所属邮箱"},
		{Name: "name", Type: shortcut.FlagString, Required: true, Desc: "模板名称"},
		{Name: "subject", Type: shortcut.FlagString, Required: true, Desc: "模板主题"},
		{Name: "body", Type: shortcut.FlagString, Required: true, Desc: "模板正文"},
		{Name: "is-draft", Type: shortcut.FlagBool, Desc: "是否创建可编辑的草稿模板;默认 false", Aliases: []string{"draft"}},
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		isDraft := mailRequestedTemplateDraftMode(rt)
		draftModeExplicit := rt.Changed("is-draft") || rt.Changed("draft")
		params := map[string]any{"email": rt.Str("email"), "name": rt.Str("name"), "subject": rt.Str("subject"), "body": rt.Str("body"), "isDraft": isDraft}
		if rt.DryRun() {
			return rt.Output(map[string]any{"value": map[string]any{"dryRun": true, "executed": false, "operation": "mail/create_user_message_template", "isDraft": isDraft}})
		}
		written, err := rt.CallMCPWriteDataStrict("mail", "create_user_message_template", params)
		if err != nil {
			return err
		}
		if err := mailRequireSuccess(written, "mail/create_user_message_template"); err != nil {
			return err
		}
		id := mailFirstString(written, "id")
		if id == "" {
			return mailResponseError("mail/create_user_message_template", "missing_stable_id", "创建模板响应缺少稳定 ID")
		}
		verified, err := mailReadTemplate(rt, rt.Str("email"), id)
		if err != nil {
			return err
		}
		if mailFirstString(verified, "name") != rt.Str("name") || mailTemplateSubject(verified) != rt.Str("subject") || mailTemplateBody(verified) != rt.Str("body") {
			return mailResponseError("mail/create_user_message_template", "verification_mismatch", "模板读回内容与请求不一致")
		}
		if draft, present, err := mailTemplateDraftMode(verified); err != nil {
			return err
		} else if draftModeExplicit && !present {
			return mailResponseError("mail/create_user_message_template", "missing_verification_field", "显式草稿模式请求缺少 isDraft 读回证据")
		} else if present && draft != isDraft {
			return mailResponseError("mail/create_user_message_template", "verification_mismatch", "模板读回 isDraft 与请求不一致")
		}
		return rt.Output(map[string]any{"value": verified})
	},
}
View Source
var TemplateList = shortcut.Shortcut{
	Service:     "mail",
	Command:     "+template-list",
	Product:     "mail",
	Description: "列出指定邮箱的所有邮件模板",
	Intent:      "当你想查看某个邮箱下已有哪些邮件模板、或需要取得模板 ID 以便查看详情或更新时使用;传入邮箱和每页数量,返回模板列表,支持分页。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "mail",
			Name:           "shortcut_template_list",
			CanonicalPath:  "mail.shortcut_template_list",
			CLIPath:        "mail +template-list",
			PrimaryCLIPath: "mail +template-list",
		},
		Description: "列出指定邮箱的所有邮件模板",
		Parameters:  []contract.ParamDecl{{Name: "limit", Property: "limit"}},
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "列出指定邮箱的所有邮件模板",
			UseWhen:      []string{"当你想查看某个邮箱下已有哪些邮件模板、或需要取得模板 ID 以便查看详情或更新时使用;传入邮箱和每页数量,返回模板列表,支持分页。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws mail +template-list --email user@company.com --limit 20"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Desc: "用户邮箱地址", Required: true},
		{Name: "limit", Type: shortcut.FlagString, Desc: "每页返回数量,必须是 1-100 之间的整数", Required: true},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标,取自响应中的 nextCursor"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "--limit 必须在 1-100 之间"}},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateStringPageSize(rt, "limit", true)
	},
	Tips: []string{
		`dws mail +template-list --email user@company.com --limit 20`,
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		size, err := mailStringPageSize(rt, "limit", true)
		if err != nil {
			return err
		}
		params := map[string]any{
			"email": rt.Str("email"),
			"size":  size,
		}
		if rt.Changed("cursor") {
			params["cursor"] = rt.Str("cursor")
		}
		data, err := rt.CallMCPData("mail", "list_user_message_templates", params)
		if err != nil {
			return err
		}
		templates, err := mailProjectCollection(data, "mail/list_user_message_templates", "templates", []string{"id"}, map[string][]string{
			"id": {"id"}, "name": {"name"}, "subject": {"subject"},
		})
		if err != nil {
			return err
		}
		complete, next, err := mailPage(data, "mail/list_user_message_templates", "", rt.Str("cursor"))
		if err != nil {
			return err
		}
		return mailOutputPage(rt, "templates", templates, complete, next)
	},
}

TemplateCreate 创建邮件模板。 TemplateList 列举邮件模板。

View Source
var TemplateUpdate = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+template-update", Product: "mail",
	Description: "更新草稿模板并按原 ID 读回验证", Intent: "修改可编辑的草稿模板;写响应成功后按原模板 ID 读取并核对所有请求变更。",
	Risk: shortcut.RiskWrite, Safety: mailWriteSafety("unknown"),
	Contract: mailWriteContract("+template-update", "更新草稿模板并按原 ID 读回验证", "修改可编辑的草稿模板;写响应成功后按原模板 ID 读取并核对所有请求变更。", []contract.ParamDecl{{Name: "email", Property: "email"}, {Name: "id", Property: "id"}, {Name: "body", Property: "body"}}, `dws mail +template-update --email user@company.com --id <templateId> --subject "新主题" --format json`),
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Required: true, Desc: "模板所属邮箱"},
		{Name: "id", Type: shortcut.FlagString, Required: true, Desc: "模板 ID;仅草稿模板可更新"},
		{Name: "name", Type: shortcut.FlagString, Desc: "新模板名称"},
		{Name: "subject", Type: shortcut.FlagString, Desc: "新主题"},
		{Name: "body", Type: shortcut.FlagString, Desc: "新正文"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"name", "subject", "body"}}},
	Execute: func(rt *shortcut.RuntimeContext) error {
		if !rt.Changed("name") && !rt.Changed("subject") && !rt.Changed("body") {
			return apperrors.NewValidation("至少指定 --name、--subject、--body 之一")
		}
		params := map[string]any{"email": rt.Str("email"), "id": rt.Str("id")}
		for _, flag := range []string{"name", "subject", "body"} {
			if rt.Changed(flag) {
				params[flag] = rt.Str(flag)
			}
		}
		if rt.DryRun() {
			return rt.Output(map[string]any{"value": map[string]any{"dryRun": true, "executed": false, "operation": "mail/update_user_message_template"}})
		}
		written, err := rt.CallMCPWriteDataStrict("mail", "update_user_message_template", params)
		if err != nil {
			return err
		}
		if err := mailRequireSuccess(written, "mail/update_user_message_template"); err != nil {
			return err
		}
		verified, err := mailReadTemplate(rt, rt.Str("email"), rt.Str("id"))
		if err != nil {
			return err
		}
		if rt.Changed("name") && mailFirstString(verified, "name") != rt.Str("name") {
			return mailResponseError("mail/update_user_message_template", "verification_mismatch", "模板读回名称与请求不一致")
		}
		if rt.Changed("subject") && mailTemplateSubject(verified) != rt.Str("subject") {
			return mailResponseError("mail/update_user_message_template", "verification_mismatch", "模板读回主题与请求不一致")
		}
		if rt.Changed("body") && mailTemplateBody(verified) != rt.Str("body") {
			return mailResponseError("mail/update_user_message_template", "verification_mismatch", "模板读回正文与请求不一致")
		}
		return rt.Output(map[string]any{"value": verified})
	},
}
View Source
var Thread = shortcut.Shortcut{
	OutputRollout: output.RolloutUnifiedActive,
	Service:       "mail", Command: "+thread", Product: "mail",
	Description: "读取完整邮件会话并精确验证 conversationId", Intent: "已知一个 conversationId,需要查看同一主题的会话上下文时使用;自动解析邮箱并拒绝空对象或错会话。",
	Risk: shortcut.RiskRead, Safety: mailReadSafety(),
	Contract: mailReadContract("+thread", "读取完整邮件会话并精确验证 conversationId", "已知一个 conversationId,需要查看同一主题的会话上下文时使用;自动解析邮箱并拒绝空对象或错会话。", mailObjectResult("身份匹配的邮件会话详情"), []contract.ParamDecl{{Name: "id", Property: "conversationId"}}, `dws mail +thread --id <conversationId> --format json`),
	Flags: []shortcut.Flag{
		{Name: "id", Type: shortcut.FlagString, Required: true, Desc: "邮件会话 conversationId,不能为空"},
		{Name: "email", Type: shortcut.FlagString, Desc: "邮箱地址;不传时自动取当前身份首个邮箱"},
	},
	Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"id"}, Description: "不能为空"}},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateRequiredText(rt, "id")
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		email, err := mailResolveMailbox(rt)
		if err != nil {
			return err
		}
		data, err := rt.CallMCPData("mail", "get_thread", map[string]any{"email": email, "conversationId": rt.Str("id")})
		if err != nil {
			return err
		}
		conversation, err := mailRequireObject(data, "mail/get_thread", "conversation")
		if err != nil {
			return err
		}
		if err := mailRequireIdentity(conversation, "mail/get_thread", rt.Str("id"), "id"); err != nil {
			return err
		}
		return rt.Output(map[string]any{"value": conversation})
	},
}
View Source
var ThreadList = shortcut.Shortcut{
	Service:     "mail",
	Command:     "+thread-list",
	Product:     "mail",
	Description: "列出指定邮箱文件夹下的邮件会话(thread)",
	Intent:      "当你想按会话(同一往来主题的邮件串)而非单封邮件来浏览某个文件夹时使用;传入邮箱和文件夹 ID,可按时间范围和升降序筛选,返回会话列表及其 conversationId,供 +thread 查看详情。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "mail",
			Name:           "shortcut_thread_list",
			CanonicalPath:  "mail.shortcut_thread_list",
			CLIPath:        "mail +thread-list",
			PrimaryCLIPath: "mail +thread-list",
		},
		Description: "列出指定邮箱文件夹下的邮件会话(thread)",
		Parameters: []contract.ParamDecl{
			{Name: "folder", Property: "folder"},
			{Name: "limit", Property: "limit"},
			{Name: "start", Property: "start"},
			{Name: "end", Property: "end"},
			{Name: "ascending", Property: "ascending"},
		},
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "列出指定邮箱文件夹下的邮件会话(thread)",
			UseWhen:      []string{"当你想按会话(同一往来主题的邮件串)而非单封邮件来浏览某个文件夹时使用;传入邮箱和文件夹 ID,可按时间范围和升降序筛选,返回会话列表及其 conversationId,供 +thread 查看详情。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples:     []string{"dws mail +thread-list --email user@company.com --folder 104 --limit 20"},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "email", Type: shortcut.FlagString, Desc: "会话所属邮箱地址", Required: true},
		{Name: "folder", Type: shortcut.FlagString, Desc: "邮件文件夹 ID(不是文件夹名称)", Required: true},
		{Name: "limit", Type: shortcut.FlagInt, Default: "20", Desc: "本次列出的会话数,最大 100"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标,首次请求可不传"},
		{Name: "start", Type: shortcut.FlagString, Desc: "开始 UTC 时间,如 2024-01-01T00:00:00Z"},
		{Name: "end", Type: shortcut.FlagString, Desc: "结束 UTC 时间,如 2024-12-31T23:59:59Z"},
		{Name: "ascending", Type: shortcut.FlagBool, Desc: "是否按时间升序"},
	},
	Constraints: []shortcut.Constraint{
		{Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "--limit 必须在 1-100 之间"},
		{Kind: shortcut.ConstraintCustom, Flags: []string{"start", "end"}, Description: "--start/--end 必须是 UTC RFC3339 时间,且 end 不能早于 start"},
	},
	Validate: func(rt *shortcut.RuntimeContext) error {
		return mailValidateThreadList(rt)
	},
	Tips: []string{
		`dws mail +thread-list --email user@company.com --folder 104 --limit 20`,
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		params := map[string]any{
			"email":    rt.Str("email"),
			"folderId": rt.Str("folder"),
			"size":     rt.Int("limit"),
		}
		if rt.Changed("cursor") {
			params["cursor"] = rt.Str("cursor")
		}
		if rt.Changed("start") {
			params["startTime"] = rt.Str("start")
		}
		if rt.Changed("end") {
			params["endTime"] = rt.Str("end")
		}
		if rt.Changed("ascending") {
			params["isAscending"] = rt.Bool("ascending")
		}
		data, err := rt.CallMCPData("mail", "list_mailbox_threads", params)
		if err != nil {
			return err
		}
		threads, err := mailProjectCollection(data, "mail/list_mailbox_threads", "result.conversations", []string{"id"}, map[string][]string{
			"conversationId": {"id"}, "subject": {"subject"}, "lastUpdated": {"lastModifiedDateTime"}, "isRead": {"isRead"},
		})
		if err != nil {
			return err
		}
		complete, next, err := mailPage(data, "mail/list_mailbox_threads", "result", rt.Str("cursor"))
		if err != nil {
			return err
		}
		return mailOutputPage(rt, "threads", threads, complete, next)
	},
}

ThreadList 列出指定邮箱文件夹下的邮件会话。

View Source
var UserSearch = shortcut.Shortcut{
	Service:     "mail",
	Command:     "+user-search",
	Product:     "mail",
	Description: "按关键词或工号搜索邮箱用户(仅企业邮箱)",
	Intent:      "当你只知道同事的姓名或工号、需要查出其企业邮箱地址以便发信或添加联系人时使用;提供关键词或工号(至少其一),返回匹配的企业邮箱用户列表。",
	Risk:        shortcut.RiskRead,
	Safety: contract.SafetySpec{
		Effect: "read", Risk: "low",
		Confirmation: "not_required", Idempotency: "idempotent",
	},
	Contract: corecmd.ContractDecl{
		Identity: contract.ToolIdentitySpec{
			ProductID:      "mail",
			Name:           "shortcut_user_search",
			CanonicalPath:  "mail.shortcut_user_search",
			CLIPath:        "mail +user-search",
			PrimaryCLIPath: "mail +user-search",
		},
		Description: "按关键词或工号搜索邮箱用户(仅企业邮箱)",
		Parameters: []contract.ParamDecl{
			{Name: "employee-no", Property: "employeeNo"},
			{Name: "limit", Property: "limit"},
		},
		Interface: &contract.InterfaceSpec{
			Mode:         "composite",
			Availability: "available",
			Reason:       "Reviewed built-in shortcut adapter: the executable CLI owns validation, optional multi-step orchestration, output projection, and confirmation; the complete command contract is not represented by one pinned MCP interface_ref.",
		},
		Selection: contract.SelectionSpec{
			AgentSummary: "按关键词或工号搜索邮箱用户(仅企业邮箱)",
			UseWhen:      []string{"当你只知道同事的姓名或工号、需要查出其企业邮箱地址以便发信或添加联系人时使用;提供关键词或工号(至少其一),返回匹配的企业邮箱用户列表。"},
			AvoidWhen:    []string{"需要该 Shortcut 未公开的底层参数、原始响应或不同执行语义时,改用对应原子命令"},
			Examples: []string{
				"dws mail +user-search --keyword \"张三\"",
				"dws mail +user-search --email user@company.com --employee-no \"E123456\"",
			},
		},
	},
	Flags: []shortcut.Flag{
		{Name: "keyword", Type: shortcut.FlagString, Desc: "搜索关键词;显式提供时不能为空(未提供 --employee-no 时为必填)"},
		{Name: "employee-no", Type: shortcut.FlagString, Desc: "按工号精确搜索;显式提供时不能为空"},
		{Name: "email", Type: shortcut.FlagString, Desc: "搜索目标邮箱地址"},
		{Name: "cursor", Type: shortcut.FlagString, Desc: "分页游标,取自响应中的 nextCursor"},
		{Name: "limit", Type: shortcut.FlagString, Desc: "每页返回数量,必须是 1-100 之间的整数"},
	},
	Constraints: []shortcut.Constraint{
		{Kind: shortcut.ConstraintAtLeastOne, Flags: []string{"keyword", "employee-no"}},
		{Kind: shortcut.ConstraintCustom, Flags: []string{"keyword", "employee-no"}, Description: "不能为空"},
		{Kind: shortcut.ConstraintCustom, Flags: []string{"limit"}, Description: "1-100"},
	},
	Validate: func(rt *shortcut.RuntimeContext) error {
		if err := mailValidateStringPageSize(rt, "limit", false); err != nil {
			return err
		}
		for _, name := range []string{"keyword", "employee-no"} {
			if rt.Changed(name) && strings.TrimSpace(rt.Str(name)) == "" {
				return apperrors.NewValidation("--" + name + " 显式提供时不能为空")
			}
		}
		if strings.TrimSpace(rt.Str("keyword")) == "" && strings.TrimSpace(rt.Str("employee-no")) == "" {
			return apperrors.NewValidation("--keyword 和 --employee-no 至少需要一个非空值")
		}
		return nil
	},
	Tips: []string{
		`dws mail +user-search --keyword "张三"`,
		`dws mail +user-search --email user@company.com --employee-no "E123456"`,
	},
	Execute: func(rt *shortcut.RuntimeContext) error {
		size, err := mailStringPageSize(rt, "limit", false)
		if err != nil {
			return err
		}
		params := map[string]any{}
		if rt.Str("keyword") != "" {
			params["keyword"] = rt.Str("keyword")
		}
		if rt.Str("employee-no") != "" {
			params["employeeNo"] = rt.Str("employee-no")
		}
		if rt.Changed("email") {
			params["email"] = rt.Str("email")
		}
		if rt.Changed("cursor") {
			params["cursor"] = rt.Str("cursor")
		}
		if rt.Changed("limit") {
			params["size"] = size
		}
		data, err := rt.CallMCPData("mail", "search_mail_users", params)
		if err != nil {
			return err
		}
		users, err := mailProjectCollection(data, "mail/search_mail_users", "users", []string{"id", "email"}, map[string][]string{
			"name": {"name"}, "email": {"email"}, "employeeNo": {"employeeNo"}, "userId": {"id"},
		})
		if err != nil {
			return err
		}
		complete, next, err := mailPage(data, "mail/search_mail_users", "", rt.Str("cursor"))
		if err != nil {
			return err
		}
		return mailOutputPage(rt, "users", users, complete, next)
	},
}

UserSearch 按关键词或工号搜索邮箱用户。

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL