diff --git a/shortcuts/mail/mail_lint_writepath_test.go b/shortcuts/mail/mail_lint_writepath_test.go index 5b145d87ad..97712ea359 100644 --- a/shortcuts/mail/mail_lint_writepath_test.go +++ b/shortcuts/mail/mail_lint_writepath_test.go @@ -486,6 +486,7 @@ func TestMailSend_WritePathLintAutofixesFontInEML(t *testing.T) { "--to", "alice@example.com", "--subject", "Send", "--body", `payload`, + "--no-signature", "--show-lint-details", }, f, stdout) if err != nil { diff --git a/shortcuts/mail/mail_request_receipt_integration_test.go b/shortcuts/mail/mail_request_receipt_integration_test.go index 32d6f76ce6..4a53a37a93 100644 --- a/shortcuts/mail/mail_request_receipt_integration_test.go +++ b/shortcuts/mail/mail_request_receipt_integration_test.go @@ -127,6 +127,7 @@ func TestMailSend_RequestReceiptAddsHeader_Integration(t *testing.T) { "--to", "bob@example.com", "--subject", "hi", "--body", "please confirm", + "--no-signature", "--request-receipt", "--confirm-send", }, f, stdout); err != nil { @@ -153,6 +154,7 @@ func TestMailSend_RequestReceiptNoSender_FailsValidation(t *testing.T) { "--to", "bob@example.com", "--subject", "hi", "--body", "body", + "--no-signature", "--request-receipt", "--confirm-send", }, f, stdout) diff --git a/shortcuts/mail/mail_send.go b/shortcuts/mail/mail_send.go index b536aa827f..fc7dae9ab7 100644 --- a/shortcuts/mail/mail_send.go +++ b/shortcuts/mail/mail_send.go @@ -40,6 +40,7 @@ var MailSend = common.Shortcut{ {Name: "request-receipt", Type: "bool", Desc: "Request a read receipt (Message Disposition Notification, RFC 3798) addressed to the sender. Recipient mail clients may prompt the user, send automatically, or silently ignore — delivery of a receipt is not guaranteed."}, {Name: "template-id", Desc: "Optional. Apply a saved template by ID (decimal integer string) before composing. The template's subject/body/to/cc/bcc/attachments are merged with user-supplied flags (user flags win). Requires --as user."}, signatureFlag, + noSignatureFlag, priorityFlag, eventSummaryFlag, eventStartFlag, eventEndFlag, eventLocationFlag, showLintDetailsFlag}, @@ -57,8 +58,11 @@ var MailSend = common.Shortcut{ api = api.GET(templateMailboxPath(mailboxID, tid)). Desc("Fetch template to merge with compose flags (subject/body/to/cc/bcc/attachments).") } - api = api.GET(mailboxPath(mailboxID, "profile")). - POST(mailboxPath(mailboxID, "drafts")). + api = api.GET(mailboxPath(mailboxID, "profile")) + if !runtime.Bool("no-signature") { + api = api.GET(mailboxPath(mailboxID, "settings", "signatures")) + } + api = api.POST(mailboxPath(mailboxID, "drafts")). Body(map[string]interface{}{ "raw": "", "_preview": map[string]interface{}{ @@ -98,7 +102,7 @@ var MailSend = common.Shortcut{ if err := validateSendTime(runtime); err != nil { return err } - if err := validateSignatureWithPlainText(runtime.Bool("plain-text"), runtime.Str("signature-id")); err != nil { + if err := validateSignatureFlags(runtime.Bool("no-signature"), runtime.Str("signature-id")); err != nil { return err } // Resolve the body content first (reading --body-file if set) so @@ -195,7 +199,7 @@ var MailSend = common.Shortcut{ } } - sigResult, err := resolveSignature(ctx, runtime, mailboxID, signatureID, senderEmail) + sigResult, err := resolveComposeSignature(ctx, runtime, mailboxID, signatureID, senderEmail, runtime.Bool("no-signature"), !plainText) if err != nil { return err } @@ -230,7 +234,7 @@ var MailSend = common.Shortcut{ // `lint_applied[]` / `original_blocked[]` even on the plain-text path. lintApplied, lintBlocked := emptyLintEnvelopeFields() if plainText { - composedTextBody = body + composedTextBody = appendPlainTextSignature(body, sigResult, resolveLang(runtime)) bld = bld.TextBody([]byte(composedTextBody)) } else if bodyIsHTML(body) || sigResult != nil { // If signature is requested on plain-text body, auto-upgrade to HTML. diff --git a/shortcuts/mail/mail_send_confirm_output_test.go b/shortcuts/mail/mail_send_confirm_output_test.go index 10f89b030c..f3add66793 100644 --- a/shortcuts/mail/mail_send_confirm_output_test.go +++ b/shortcuts/mail/mail_send_confirm_output_test.go @@ -149,6 +149,7 @@ func TestMailSendConfirmSendOutputsAutomationDisable(t *testing.T) { "--to", "alice@example.com", "--subject", "hello", "--body", "world", + "--no-signature", "--confirm-send", }, f, stdout) if err != nil { @@ -203,6 +204,7 @@ func TestMailSendSaveDraftOutputsReference(t *testing.T) { "--to", "alice@example.com", "--subject", "hello", "--body", "world", + "--no-signature", }, f, stdout) if err != nil { t.Fatalf("save draft failed: %v", err) @@ -243,6 +245,7 @@ func TestMailSend_WithCalendarEventEmbedded(t *testing.T) { "--to", "alice@example.com", "--subject", "Team Sync", "--body", "

Please join us

", + "--no-signature", "--event-summary", "Team Sync", "--event-start", "2026-05-10T10:00+08:00", "--event-end", "2026-05-10T11:00+08:00", diff --git a/shortcuts/mail/mail_send_signature_test.go b/shortcuts/mail/mail_send_signature_test.go new file mode 100644 index 0000000000..ae50e809d3 --- /dev/null +++ b/shortcuts/mail/mail_send_signature_test.go @@ -0,0 +1,254 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package mail + +import ( + "encoding/base64" + "encoding/json" + "errors" + "strings" + "testing" + + "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/internal/httpmock" +) + +func TestMailSendDefaultSignatureHTML(t *testing.T) { + f, stdout, _, reg := mailShortcutTestFactory(t) + mailboxID := "sig-html@example.com" + draftStub := registerMailSendDraftStub(reg, mailboxID) + registerSignaturesStub(reg, mailboxID, []map[string]interface{}{ + {"id": "sig_default", "name": "Default", "content": "

Default Signature

"}, + }, []map[string]interface{}{ + {"email_address": mailboxID, "send_mail_signature_id": "sig_default"}, + }) + + err := runMountedMailShortcut(t, MailSend, []string{ + "+send", + "--mailbox", mailboxID, + "--to", "alice@example.com", + "--subject", "hello", + "--body", "

Hello

", + }, f, stdout) + if err != nil { + t.Fatalf("send failed: %v", err) + } + + eml := decodeDraftRawEML(t, draftStub) + if !strings.Contains(eml, "lark-mail-signature") { + t.Fatalf("expected signature wrapper in EML:\n%s", eml) + } + if !strings.Contains(eml, "Default Signature") { + t.Fatalf("expected default signature content in EML:\n%s", eml) + } +} + +func TestMailSendDefaultSignaturePlainText(t *testing.T) { + f, stdout, _, reg := mailShortcutTestFactory(t) + mailboxID := "sig-plain@example.com" + draftStub := registerMailSendDraftStub(reg, mailboxID) + registerSignaturesStub(reg, mailboxID, []map[string]interface{}{ + {"id": "sig_plain", "name": "Plain", "content": "
Best regards
Alice
"}, + }, []map[string]interface{}{ + {"email_address": mailboxID, "send_mail_signature_id": "sig_plain"}, + }) + + err := runMountedMailShortcut(t, MailSend, []string{ + "+send", + "--mailbox", mailboxID, + "--to", "alice@example.com", + "--subject", "hello", + "--body", "Hi", + "--plain-text", + }, f, stdout) + if err != nil { + t.Fatalf("send failed: %v", err) + } + + eml := decodeDraftRawEML(t, draftStub) + textBody := decodeSinglePartBase64Body(t, eml) + if !strings.Contains(textBody, "Hi\n\nBest regards") { + t.Fatalf("expected plain-text signature after blank line, body=%q EML:\n%s", textBody, eml) + } + if strings.Contains(textBody, "
") || strings.Contains(textBody, "") || strings.Contains(eml, "lark-mail-signature") { + t.Fatalf("plain-text signature should not include HTML tags or wrapper, body=%q EML:\n%s", textBody, eml) + } +} + +func TestMailSendNoSignatureSkipsSignatureQuery(t *testing.T) { + f, stdout, _, reg := mailShortcutTestFactory(t) + mailboxID := "no-sig@example.com" + draftStub := registerMailSendDraftStub(reg, mailboxID) + + err := runMountedMailShortcut(t, MailSend, []string{ + "+send", + "--mailbox", mailboxID, + "--to", "alice@example.com", + "--subject", "hello", + "--body", "

Hello

", + "--no-signature", + }, f, stdout) + if err != nil { + t.Fatalf("send failed: %v", err) + } + + eml := decodeDraftRawEML(t, draftStub) + if strings.Contains(eml, "lark-mail-signature") { + t.Fatalf("did not expect signature wrapper in EML:\n%s", eml) + } +} + +func TestMailSendExplicitSignatureOverridesDefault(t *testing.T) { + f, stdout, _, reg := mailShortcutTestFactory(t) + mailboxID := "sig-explicit@example.com" + draftStub := registerMailSendDraftStub(reg, mailboxID) + registerSignaturesStub(reg, mailboxID, []map[string]interface{}{ + {"id": "sig_default", "name": "Default", "content": "

Default Signature

"}, + {"id": "sig_explicit", "name": "Explicit", "content": "

Explicit Signature

"}, + }, []map[string]interface{}{ + {"email_address": mailboxID, "send_mail_signature_id": "sig_default"}, + }) + + err := runMountedMailShortcut(t, MailSend, []string{ + "+send", + "--mailbox", mailboxID, + "--to", "alice@example.com", + "--subject", "hello", + "--body", "

Hello

", + "--signature-id", "sig_explicit", + }, f, stdout) + if err != nil { + t.Fatalf("send failed: %v", err) + } + + eml := decodeDraftRawEML(t, draftStub) + if !strings.Contains(eml, "Explicit Signature") { + t.Fatalf("expected explicit signature in EML:\n%s", eml) + } + if strings.Contains(eml, "Default Signature") { + t.Fatalf("explicit signature should override default signature:\n%s", eml) + } +} + +func TestMailSendNoSignatureAndSignatureIDMutuallyExclusive(t *testing.T) { + f, stdout, _, _ := mailShortcutTestFactory(t) + err := runMountedMailShortcut(t, MailSend, []string{ + "+send", + "--to", "alice@example.com", + "--subject", "hello", + "--body", "Hello", + "--no-signature", + "--signature-id", "sig_123", + }, f, stdout) + var validationErr *errs.ValidationError + if !errors.As(err, &validationErr) { + t.Fatalf("expected validation error, got %T (%v)", err, err) + } + if !strings.Contains(err.Error(), "--no-signature and --signature-id are mutually exclusive") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestMailSendDefaultSignatureMatchesFromUsage(t *testing.T) { + f, stdout, _, reg := mailShortcutTestFactory(t) + mailboxID := "owner@example.com" + draftStub := registerMailSendDraftStub(reg, mailboxID) + registerSignaturesStub(reg, mailboxID, []map[string]interface{}{ + {"id": "sig_owner", "name": "Owner", "content": "

Owner Signature

"}, + {"id": "sig_alias", "name": "Alias", "content": "

Alias Signature

"}, + }, []map[string]interface{}{ + {"email_address": mailboxID, "send_mail_signature_id": "sig_owner"}, + {"email_address": "alias@example.com", "send_mail_signature_id": "sig_alias"}, + }) + + err := runMountedMailShortcut(t, MailSend, []string{ + "+send", + "--mailbox", mailboxID, + "--from", "alias@example.com", + "--to", "alice@example.com", + "--subject", "hello", + "--body", "

Hello

", + }, f, stdout) + if err != nil { + t.Fatalf("send failed: %v", err) + } + + eml := decodeDraftRawEML(t, draftStub) + if !strings.Contains(eml, "Alias Signature") { + t.Fatalf("expected alias signature in EML:\n%s", eml) + } + if strings.Contains(eml, "Owner Signature") { + t.Fatalf("expected --from usage to win over fallback default:\n%s", eml) + } +} + +func TestAppendPlainTextSignatureDoesNotTruncate(t *testing.T) { + longText := strings.Repeat("x", 240) + got := appendPlainTextSignature("Hi", &signatureResult{RenderedContent: "

" + longText + "

"}, "en_us") + if !strings.Contains(got, longText) { + t.Fatalf("plain-text signature was truncated: %q", got) + } +} + +func registerSignaturesStub(reg *httpmock.Registry, mailboxID string, signatures []map[string]interface{}, usages []map[string]interface{}) *httpmock.Stub { + stub := &httpmock.Stub{ + Method: "GET", + URL: "/user_mailboxes/" + mailboxID + "/settings/signatures", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "signatures": signatures, + "usages": usages, + }, + }, + } + reg.Register(stub) + return stub +} + +func registerMailSendDraftStub(reg *httpmock.Registry, mailboxID string) *httpmock.Stub { + stub := &httpmock.Stub{ + Method: "POST", + URL: "/user_mailboxes/" + mailboxID + "/drafts", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "draft_id": "draft_001", + }, + }, + } + reg.Register(stub) + return stub +} + +func decodeDraftRawEML(t *testing.T, stub *httpmock.Stub) string { + t.Helper() + var reqBody map[string]interface{} + if err := json.Unmarshal(stub.CapturedBody, &reqBody); err != nil { + t.Fatalf("unmarshal captured draft body: %v", err) + } + raw, _ := reqBody["raw"].(string) + decoded, err := base64.URLEncoding.DecodeString(raw) + if err != nil { + t.Fatalf("base64url decode raw EML: %v", err) + } + return string(decoded) +} + +func decodeSinglePartBase64Body(t *testing.T, eml string) string { + t.Helper() + parts := strings.SplitN(eml, "\r\n\r\n", 2) + if len(parts) != 2 { + parts = strings.SplitN(eml, "\n\n", 2) + } + if len(parts) != 2 { + t.Fatalf("EML missing body separator:\n%s", eml) + } + encoded := strings.TrimSpace(parts[1]) + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + t.Fatalf("decode base64 body: %v; body=%q", err, encoded) + } + return strings.ReplaceAll(string(decoded), "\r\n", "\n") +} diff --git a/shortcuts/mail/mail_template_shortcut_test.go b/shortcuts/mail/mail_template_shortcut_test.go index a7cfff1f2b..670d2c2e45 100644 --- a/shortcuts/mail/mail_template_shortcut_test.go +++ b/shortcuts/mail/mail_template_shortcut_test.go @@ -1041,6 +1041,7 @@ func TestFetchTemplateAttachmentURLs_FailedReasons(t *testing.T) { "--to", "alice@example.com", "--subject", "s", "--body", "

b

", + "--no-signature", "--template-id", "33", }, f, stdout) if err == nil || !strings.Contains(err.Error(), "download URL not returned") { @@ -1140,6 +1141,7 @@ func TestMailSend_TemplateIDAppliesInlineAndSmall(t *testing.T) { "--to", "alice@example.com", "--subject", "override-subj", "--body", "

user body

", + "--no-signature", "--template-id", "42", }, f, stdout) if err != nil { diff --git a/shortcuts/mail/signature_compose.go b/shortcuts/mail/signature_compose.go index ac7f13cde2..1edc597826 100644 --- a/shortcuts/mail/signature_compose.go +++ b/shortcuts/mail/signature_compose.go @@ -5,10 +5,12 @@ package mail import ( "context" + "html" "io" "net/http" "net/url" "path/filepath" + "regexp" "strings" "time" @@ -22,7 +24,13 @@ import ( // signatureFlag is the common flag definition for --signature-id, shared by all compose shortcuts. var signatureFlag = common.Flag{ Name: "signature-id", - Desc: "Optional. Signature ID to append after body content. Run `mail +signature` to list available signatures.", + Desc: "Optional. Signature ID to append after body content. For mail +send, overrides the default send signature. Run `mail +signature` to list available signatures.", +} + +var noSignatureFlag = common.Flag{ + Name: "no-signature", + Type: "bool", + Desc: "Skip appending the default mail signature. Mutually exclusive with --signature-id.", } // signatureResult holds the pre-processed signature data ready for HTML injection. @@ -36,6 +44,10 @@ type signatureResult struct { // fromEmail is the --from address (may be an alias); used to match the correct // sender identity for template interpolation. Pass "" to use the primary address. func resolveSignature(ctx context.Context, runtime *common.RuntimeContext, mailboxID, signatureID, fromEmail string) (*signatureResult, error) { + return resolveSignatureForCompose(ctx, runtime, mailboxID, signatureID, fromEmail, true) +} + +func resolveSignatureForCompose(ctx context.Context, runtime *common.RuntimeContext, mailboxID, signatureID, fromEmail string, includeImages bool) (*signatureResult, error) { if signatureID == "" { return nil, nil } @@ -53,20 +65,22 @@ func resolveSignature(ctx context.Context, runtime *common.RuntimeContext, mailb // Download signature inline images. The file_key field contains a // direct download URL provided by the mail backend. var images []draftpkg.SignatureImage - for _, img := range sig.Images { - if img.DownloadURL == "" || img.CID == "" { - continue - } - data, ct, err := downloadSignatureImage(runtime, img.DownloadURL, img.ImageName) - if err != nil { - return nil, mailDecorateProblemMessage(err, "failed to download signature image %s", img.ImageName) + if includeImages { + for _, img := range sig.Images { + if img.DownloadURL == "" || img.CID == "" { + continue + } + data, ct, err := downloadSignatureImage(runtime, img.DownloadURL, img.ImageName) + if err != nil { + return nil, mailDecorateProblemMessage(err, "failed to download signature image %s", img.ImageName) + } + images = append(images, draftpkg.SignatureImage{ + CID: img.CID, + ContentType: ct, + FileName: img.ImageName, + Data: data, + }) } - images = append(images, draftpkg.SignatureImage{ - CID: img.CID, - ContentType: ct, - FileName: img.ImageName, - Data: data, - }) } return &signatureResult{ @@ -76,6 +90,57 @@ func resolveSignature(ctx context.Context, runtime *common.RuntimeContext, mailb }, nil } +func resolveComposeSignature(ctx context.Context, runtime *common.RuntimeContext, mailboxID, explicitID, fromEmail string, noSignature bool, includeImages bool) (*signatureResult, error) { + if noSignature { + return nil, nil + } + if explicitID != "" { + return resolveSignatureForCompose(ctx, runtime, mailboxID, explicitID, fromEmail, includeImages) + } + + resp, err := signature.ListAll(runtime, mailboxID) + if err != nil { + return nil, err + } + sigID := resolveDefaultSendSignatureID(resp, fromEmail) + if sigID == "" { + return nil, nil + } + return resolveSignatureForCompose(ctx, runtime, mailboxID, sigID, fromEmail, includeImages) +} + +func resolveDefaultSendSignatureID(resp *signature.GetSignaturesResponse, fromEmail string) string { + if resp == nil { + return "" + } + for _, usage := range resp.Usages { + if !validSignatureUsageID(resp, usage.SendMailSignatureID) { + continue + } + if fromEmail != "" && strings.EqualFold(usage.EmailAddress, fromEmail) { + return usage.SendMailSignatureID + } + } + for _, usage := range resp.Usages { + if validSignatureUsageID(resp, usage.SendMailSignatureID) { + return usage.SendMailSignatureID + } + } + return "" +} + +func validSignatureUsageID(resp *signature.GetSignaturesResponse, sigID string) bool { + if sigID == "" || sigID == "0" || resp == nil { + return false + } + for _, sig := range resp.Signatures { + if sig.ID == sigID { + return true + } + } + return false +} + // injectSignatureIntoBody inserts signature HTML into the body, placing // it right after the user-authored region and before any system-managed // tail (large attachment card or quote block). Any existing signature is @@ -244,6 +309,59 @@ func signatureCIDs(sig *signatureResult) []string { return cids } +var ( + plainTextImageTagRe = regexp.MustCompile(`(?is)]*>`) + plainTextLineBreakTagRe = regexp.MustCompile(`(?i)`) + plainTextBlockCloseTagRe = regexp.MustCompile(`(?i)`) + plainTextTagRe = regexp.MustCompile(`(?s)<[^>]+>`) +) + +func renderSignaturePlainText(sig *signatureResult, lang string) string { + if sig == nil { + return "" + } + placeholder := "[image]" + if strings.HasPrefix(lang, "zh") { + placeholder = "[图片]" + } + s := plainTextImageTagRe.ReplaceAllString(sig.RenderedContent, placeholder) + s = plainTextLineBreakTagRe.ReplaceAllString(s, "\n") + s = plainTextBlockCloseTagRe.ReplaceAllString(s, "\n") + s = plainTextTagRe.ReplaceAllString(s, " ") + s = html.UnescapeString(s) + + lines := strings.Split(s, "\n") + out := make([]string, 0, len(lines)) + blank := false + for _, line := range lines { + line = strings.Join(strings.Fields(line), " ") + if line == "" { + if len(out) > 0 && !blank { + out = append(out, "") + blank = true + } + continue + } + out = append(out, line) + blank = false + } + for len(out) > 0 && out[len(out)-1] == "" { + out = out[:len(out)-1] + } + return strings.TrimSpace(strings.Join(out, "\n")) +} + +func appendPlainTextSignature(body string, sig *signatureResult, lang string) string { + text := renderSignaturePlainText(sig, lang) + if text == "" { + return body + } + if strings.TrimSpace(body) == "" { + return text + } + return strings.TrimRight(body, "\r\n") + "\n\n" + text +} + // validateSignatureWithPlainText returns an error if both --plain-text and --signature-id are set. func validateSignatureWithPlainText(plainText bool, signatureID string) error { if plainText && signatureID != "" { @@ -255,3 +373,14 @@ func validateSignatureWithPlainText(plainText bool, signatureID string) error { } return nil } + +func validateSignatureFlags(noSignature bool, signatureID string) error { + if noSignature && strings.TrimSpace(signatureID) != "" { + return mailValidationError("--no-signature and --signature-id are mutually exclusive"). + WithParams( + mailInvalidParam("--no-signature", "mutually exclusive with --signature-id"), + mailInvalidParam("--signature-id", "mutually exclusive with --no-signature"), + ) + } + return nil +} diff --git a/skills/lark-mail/SKILL.md b/skills/lark-mail/SKILL.md index e045c6e274..94dfdbc4e8 100644 --- a/skills/lark-mail/SKILL.md +++ b/skills/lark-mail/SKILL.md @@ -335,10 +335,16 @@ lark-cli mail +send --as user \ lark-cli mail +send --to alice@example.com --subject '周报' \ --body '

本周进展:

  • 完成 A 模块
  • 修复 3 个 bug
' +# 跳过当前账号/发件地址的默认发信签名 +lark-cli mail +send --to alice@example.com --subject '简短通知' \ + --body '

收到,谢谢。

' --no-signature + # ⚠️ 仅在内容极简时使用纯文本 lark-cli mail +reply --message-id --body '收到,谢谢' ``` +`+send` 默认会追加当前账号 / `--from` 发件地址对应的发信默认签名;需要完全不带签名时传 `--no-signature`,需要指定某个签名时传 `--signature-id ` 覆盖默认签名。`--signature-id` 与 `--no-signature` 互斥。纯文本 `+send --plain-text` 不会因为签名升级为 HTML;签名会先转换为纯文本再拼接。 + ## 邮件书写规范 - 写信时**必须**遵守 [邮件 HTML 写法规范](references/lark-mail-html.md) — **CRITICAL** 飞书邮箱已验证的最纯净美观写法集合 @@ -657,4 +663,3 @@ lark-cli mail [flags] # 调用 API | `user_mailbox.threads.list` | `mail:user_mailbox.message:readonly` | | `user_mailbox.threads.modify` | `mail:user_mailbox.message:modify` | | `user_mailbox.threads.trash` | `mail:user_mailbox.message:modify` | - diff --git a/skills/lark-mail/references/lark-mail-send.md b/skills/lark-mail/references/lark-mail-send.md index 500feca414..2fe7bbc6ff 100644 --- a/skills/lark-mail/references/lark-mail-send.md +++ b/skills/lark-mail/references/lark-mail-send.md @@ -7,6 +7,7 @@ - 抄送/密送 - 本地文件附件(`--attach`) - 内嵌图片(`--inline`,CID 可用随机字符串) +- 默认追加当前发件地址的发信签名;可用 `--no-signature` 跳过,或用 `--signature-id` 覆盖默认签名 本 skill 对应 shortcut:`lark-cli mail +send`。 @@ -59,6 +60,9 @@ lark-cli mail +send --to alice@example.com --subject '预览图' --body 'test

' --no-signature + # Dry Run(仅打印请求,不执行) lark-cli mail +send --to alice@example.com --subject '测试' --body '

test

' --dry-run ``` @@ -78,7 +82,8 @@ lark-cli mail +send --to alice@example.com --subject '测试' --body '

test

` | 否 | 附件文件路径,多个用逗号分隔。相对路径。当附件导致 EML 总大小超过 25 MB 时,超出部分自动上传为超大附件(HTML 邮件插入下载卡片,纯文本邮件追加下载链接),单个文件上限 3 GB | | `--inline ` | 否 | 高级用法:手动指定内嵌图片 CID 映射。推荐直接在 `--body` 中使用 ``(自动解析)。仅在需要精确控制 CID 命名时使用此参数。格式:`'[{"cid":"mycid","file_path":"./logo.png"}]'`,在 body 中用 `` 引用。不可与 `--plain-text` 同时使用 | -| `--signature-id ` | 否 | 签名 ID。附加邮箱签名到正文末尾。运行 `mail +signature` 查看可用签名。不可与 `--plain-text` 同时使用 | +| `--signature-id ` | 否 | 签名 ID。覆盖默认发信签名并附加到正文末尾。仅支持 HTML 邮件,保留签名样式和内嵌图片;纯文本邮件不支持指定签名 ID。运行 `mail +signature` 查看可用签名。不可与 `--plain-text` 或 `--no-signature` 同时使用 | +| `--no-signature` | 否 | 跳过默认签名查询与追加。需要保持正文完全不加签名时使用。不可与 `--signature-id` 同时使用 | | `--priority ` | 否 | 邮件优先级:`high`、`normal`、`low`。省略或 `normal` 时不设置优先级 | | `--event-summary ` | 否 | 日程标题。设置此参数即在邮件中嵌入日程邀请(text/calendar)。需同时设置 `--event-start` 和 `--event-end` | | `--event-start