Probably incorrect MIME data in case if plain/html wasn't specified
Darkclainer opened this issue · comments
Hello! I'm not an expert in MIME data format, but I have encountered with next problem while trying to send message to Maddy mail server.
Initially I tried to send mail that contain only attachment and Maddy server reported that:
submission: DATA error {"msg_id":"e9728fad","reason":"multipart: NextPart: EOF"}
After some digging I have found, that MailYak "declare" multipart/alternative
section without starting and ending it. Consider next example:
package main
import (
"fmt"
"net/smtp"
"strings"
"github.com/domodwyer/mailyak/v3"
)
func main() {
mail := mailyak.New("", smtp.PlainAuth("", "", "", ""))
mail.To("dom@itsallbroken.com")
buf, err := mail.MimeBuf()
if err != nil {
panic(err)
}
fmt.Printf("Without plain/html or attachment: \n%s\n\n", buf.String())
mail = mailyak.New("", smtp.PlainAuth("", "", "", ""))
mail.To("dom@itsallbroken.com")
mail.Plain().Set("test")
buf, err = mail.MimeBuf()
if err != nil {
panic(err)
}
fmt.Printf("With plain only: \n%s\n\n", buf.String())
mail = mailyak.New("", smtp.PlainAuth("", "", "", ""))
mail.To("dom@itsallbroken.com")
mail.Attach("myattachment", strings.NewReader("content"))
buf, err = mail.MimeBuf()
if err != nil {
panic(err)
}
fmt.Printf("With attachment only: \n%s\n\n", buf.String())
}
The program produces next output:
Without plain/html or attachment:
From:
Mime-Version: 1.0
Date: Tue, 18 Jul 2023 16:10:51 +0400
Subject:
To: dom@itsallbroken.com
Content-Type: multipart/mixed;
boundary="8d455f169f5b75c648ace9dc418bc6cc66a9ad91bc1b84c5422a96ca119d"; charset=UTF-8
--8d455f169f5b75c648ace9dc418bc6cc66a9ad91bc1b84c5422a96ca119d
Content-Type: multipart/alternative;
boundary="215e77d6e9720917deaf807da239776f10c78ad00e797773767055c35b62"
--8d455f169f5b75c648ace9dc418bc6cc66a9ad91bc1b84c5422a96ca119d--
With plain only:
From:
Mime-Version: 1.0
Date: Tue, 18 Jul 2023 16:10:51 +0400
Subject:
To: dom@itsallbroken.com
Content-Type: multipart/mixed;
boundary="a2877f81c63ed570e6cbcedb6d66023659c84beeda56255193965019d24c"; charset=UTF-8
--a2877f81c63ed570e6cbcedb6d66023659c84beeda56255193965019d24c
Content-Type: multipart/alternative;
boundary="ab744051ed44f83173cf7023d22b54443d987acfea3c43906ed8b4734887"
--ab744051ed44f83173cf7023d22b54443d987acfea3c43906ed8b4734887
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8
test
--ab744051ed44f83173cf7023d22b54443d987acfea3c43906ed8b4734887--
--a2877f81c63ed570e6cbcedb6d66023659c84beeda56255193965019d24c--
With attachment only:
From:
Mime-Version: 1.0
Date: Tue, 18 Jul 2023 16:10:51 +0400
Subject:
To: dom@itsallbroken.com
Content-Type: multipart/mixed;
boundary="5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f"; charset=UTF-8
--5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f
Content-Type: multipart/alternative;
boundary="20466d6e600bd4dd414edd0fd41d39d6f635eab3e6704a3c621bebf7bb65"
--5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f
Content-Disposition: attachment;
filename="myattachment"; name="myattachment"
Content-ID: <myattachment>
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=utf-8;
filename="myattachment"; name="myattachment"
Y29udGVudA==
--5b015828a43b39e9c393a7602b852053de3f11a4632e5acdbad189bc129f--
In the case denoted as Without plain/html or attachment
we can see:
Content-Type: multipart/alternative;
boundary="215e77d6e9720917deaf807da239776f10c78ad00e797773767055c35b62"
Without section containing 215e77d6e9720917deaf807da239776f10c78ad00e797773767055c35b62
which is different from case when we set plain data.
As I said I'm no expert, so probably this is correct MIME format, but probably for better interoperability empty section should omitted entirely or written anyway, see early skip in MailYak code:
// writeBody writes the text/plain and text/html mime parts.
func (m *MailYak) writeBody(w io.Writer, boundary string) error {
if m.plain.Len() == 0 && m.html.Len() == 0 {
// No body to write - just skip it
return nil
}
// ...
}
Probably I found relevant part in the RFC that describes MIME media types: https://datatracker.ietf.org/doc/html/rfc2046#section-5.1
Quote:
In the case of multipart entities, in which one or more different
sets of data are combined in a single body, a "multipart" media type
field must appear in the entity's header. The body must then contain
one or more body parts, each preceded by a boundary delimiter line,
and the last one followed by a closing boundary delimiter line.
Which I understood as if multipart defined then body parts must be present.
Hmm top investigating!
I certainly read your RFC quote the same way - I think the multipart sections shouldn't be included when empty - you're right!
Is this something you'd be interested in opening a PR for? If not I can stick it on my TODO list 👍
Thanks for the help!
(ps: sorry for the slow response - I was on holiday!)
Thank you for the response!
OK, I will gladly submit fix for this issue.