From ac4938f8fd959f4dd6a139bca29f5e3d64671e4f Mon Sep 17 00:00:00 2001 From: Mauro Marques Filho Date: Thu, 21 May 2026 05:50:02 -0300 Subject: [PATCH] fix(sender): stop claiming flowed text --- sender/sender.go | 11 +++++++---- sender/sender_test.go | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/sender/sender.go b/sender/sender.go index 816b3bd..fe68b76 100644 --- a/sender/sender.go +++ b/sender/sender.go @@ -195,6 +195,11 @@ func writeQuotedPrintable(w io.Writer, body string) error { return nil } +func writePlainTextPartHeaders(w io.Writer) { + fmt.Fprintf(w, "Content-Type: text/plain; charset=UTF-8\r\n") + fmt.Fprintf(w, "Content-Transfer-Encoding: quoted-printable\r\n\r\n") +} + // SendEmail constructs a multipart message with plain text, HTML, embedded images, and attachments. func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody, htmlBody string, images map[string][]byte, attachments map[string][]byte, inReplyTo string, references []string, signSMIME bool, encryptSMIME bool, signPGP bool, encryptPGP bool) ([]byte, error) { smtpServer := account.GetSMTPServer() @@ -263,8 +268,7 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody // Build the canonical MIME part (headers + body) used for signing/encryption var partBuf bytes.Buffer - fmt.Fprintf(&partBuf, "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n") - fmt.Fprintf(&partBuf, "Content-Transfer-Encoding: quoted-printable\r\n\r\n") + writePlainTextPartHeaders(&partBuf) partBuf.Write(encodedBody) canonicalPart := partBuf.Bytes() @@ -349,8 +353,7 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody payloadToEncrypt = canonicalBody } else { // Write Content-Type and body as top-level single part - fmt.Fprintf(&msg, "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n") - fmt.Fprintf(&msg, "Content-Transfer-Encoding: quoted-printable\r\n\r\n") + writePlainTextPartHeaders(&msg) msg.Write(encodedBody) } } diff --git a/sender/sender_test.go b/sender/sender_test.go index d6a0efb..83d3bfb 100644 --- a/sender/sender_test.go +++ b/sender/sender_test.go @@ -1,6 +1,7 @@ package sender import ( + "bytes" "errors" "io" "strings" @@ -92,6 +93,23 @@ func TestSMTPHelloHostname(t *testing.T) { } } +func TestWritePlainTextPartHeadersDoesNotClaimFormatFlowed(t *testing.T) { + var buf bytes.Buffer + + writePlainTextPartHeaders(&buf) + + got := buf.String() + if strings.Contains(got, "format=flowed") { + t.Fatalf("plaintext headers should not claim format=flowed: %q", got) + } + if !strings.Contains(got, "Content-Type: text/plain; charset=UTF-8\r\n") { + t.Fatalf("plaintext headers missing content type: %q", got) + } + if !strings.Contains(got, "Content-Transfer-Encoding: quoted-printable\r\n\r\n") { + t.Fatalf("plaintext headers missing quoted-printable encoding: %q", got) + } +} + // TestGenerateMessageID ensures the Message-ID has the correct format. func TestGenerateMessageID(t *testing.T) { from := "test@example.com"