diff --git a/shortcuts/mail/emlbuilder/builder.go b/shortcuts/mail/emlbuilder/builder.go index d4b5b22f9..5c0ba60bb 100644 --- a/shortcuts/mail/emlbuilder/builder.go +++ b/shortcuts/mail/emlbuilder/builder.go @@ -86,6 +86,7 @@ type Builder struct { inReplyTo string // raw value, without angle brackets references string // space-separated list of message IDs, with angle brackets lmsReplyToMessageID string // Lark internal message_id of the original message + lmsReplyType string // REPLY or FORWARD, written as X-LMS-Reply-Type header textBody []byte htmlBody []byte calendarBody []byte @@ -391,6 +392,26 @@ func (b Builder) LMSReplyToMessageID(id string) Builder { return b } +// LMSReplyType sets the reply type so the backend can label the original message +// as replied ("REPLY") or forwarded ("FORWARD"). Written as the X-LMS-Reply-Type +// header. Only "REPLY" and "FORWARD" are accepted; any other value is a no-op +// (no header emitted) to avoid leaking unexpected values. +// Returns an error builder if the value contains CR or LF. +func (b Builder) LMSReplyType(t string) Builder { + if b.err != nil { + return b + } + if t != "REPLY" && t != "FORWARD" { + return b + } + if err := validateHeaderValue(t); err != nil { + b.err = err + return b + } + b.lmsReplyType = t + return b +} + // References sets the References header value verbatim. // Typically a space-separated list of message IDs including angle brackets, // e.g. " ". @@ -721,6 +742,9 @@ func (b Builder) Build() ([]byte, error) { if b.lmsReplyToMessageID != "" { writeHeader(&buf, "X-LMS-Reply-To-Message-Id", b.lmsReplyToMessageID) } + if b.lmsReplyType != "" { + writeHeader(&buf, "X-LMS-Reply-Type", b.lmsReplyType) + } } if b.references != "" { writeHeader(&buf, "References", b.references) diff --git a/shortcuts/mail/emlbuilder/builder_test.go b/shortcuts/mail/emlbuilder/builder_test.go index 024b602c0..c78fec9da 100644 --- a/shortcuts/mail/emlbuilder/builder_test.go +++ b/shortcuts/mail/emlbuilder/builder_test.go @@ -264,6 +264,55 @@ func TestBuild_LMSReplyToMessageID(t *testing.T) { } } +// TestBuild_LMSReplyType verifies build LMS reply type header. +func TestBuild_LMSReplyType(t *testing.T) { + raw, err := New(). + From("", "alice@example.com"). + To("", "bob@example.com"). + Subject("Re: hello"). + Date(fixedDate). + InReplyTo("original@smtp"). + LMSReplyToMessageID("740000000000000067"). + LMSReplyType("REPLY"). + TextBody([]byte("my reply")). + Build() + if err != nil { + t.Fatal(err) + } + eml := string(raw) + + got := headerValue(eml, "X-LMS-Reply-Type") + if got != "REPLY" { + t.Errorf("X-LMS-Reply-Type: got %q, want REPLY", got) + } + if !strings.Contains(eml, "X-LMS-Reply-Type: REPLY") { + t.Errorf("eml should contain header line \"X-LMS-Reply-Type: REPLY\", got:\n%s", eml) + } +} + +// TestBuild_LMSReplyType_InvalidValueNotWritten verifies an invalid reply type is ignored. +func TestBuild_LMSReplyType_InvalidValueNotWritten(t *testing.T) { + raw, err := New(). + From("", "alice@example.com"). + To("", "bob@example.com"). + Subject("Re: hello"). + Date(fixedDate). + InReplyTo("original@smtp"). + LMSReplyToMessageID("740000000000000067"). + LMSReplyType("BOGUS"). + TextBody([]byte("my reply")). + Build() + if err != nil { + t.Fatal(err) + } + eml := string(raw) + + got := headerValue(eml, "X-LMS-Reply-Type") + if got != "" { + t.Errorf("X-LMS-Reply-Type should be absent for invalid value, got %q", got) + } +} + // TestBuild_LMSReplyToMessageID_NotWrittenWithoutInReplyTo verifies build LMS reply to message ID not written without in reply to. func TestBuild_LMSReplyToMessageID_NotWrittenWithoutInReplyTo(t *testing.T) { raw, err := New(). diff --git a/shortcuts/mail/mail_forward.go b/shortcuts/mail/mail_forward.go index 466db2e12..4c15b9dcf 100644 --- a/shortcuts/mail/mail_forward.go +++ b/shortcuts/mail/mail_forward.go @@ -229,6 +229,7 @@ var MailForward = common.Shortcut{ } if messageId != "" { bld = bld.LMSReplyToMessageID(messageId) + bld = bld.LMSReplyType("FORWARD") } useHTML := !plainText && (bodyIsHTML(body) || bodyIsHTML(orig.bodyRaw) || sigResult != nil) if strings.TrimSpace(inlineFlag) != "" && !useHTML { diff --git a/shortcuts/mail/mail_reply.go b/shortcuts/mail/mail_reply.go index a7674295e..ddbf2923c 100644 --- a/shortcuts/mail/mail_reply.go +++ b/shortcuts/mail/mail_reply.go @@ -239,6 +239,7 @@ var MailReply = common.Shortcut{ } if messageId != "" { bld = bld.LMSReplyToMessageID(messageId) + bld = bld.LMSReplyType("REPLY") } var autoResolvedPaths []string var composedHTMLBody string diff --git a/shortcuts/mail/mail_reply_all.go b/shortcuts/mail/mail_reply_all.go index e7eaf02ce..78c82d89b 100644 --- a/shortcuts/mail/mail_reply_all.go +++ b/shortcuts/mail/mail_reply_all.go @@ -248,6 +248,7 @@ var MailReplyAll = common.Shortcut{ } if messageId != "" { bld = bld.LMSReplyToMessageID(messageId) + bld = bld.LMSReplyType("REPLY") } var autoResolvedPaths []string var composedHTMLBody string