diff --git a/example_test.go b/example_test.go index 8d9c6c2..e37298a 100644 --- a/example_test.go +++ b/example_test.go @@ -213,3 +213,7 @@ func ExampleSetCharset() { func ExampleSetEncoding() { m = gomail.NewMessage(gomail.SetEncoding(gomail.Base64)) } + +func ExampleWithEncoding() { + m.SetBody("text/plain", "Hello!", gomail.SetPartEncoding(gomail.Unencoded)) +} diff --git a/message.go b/message.go index 7fbfc3f..4a3fd0e 100644 --- a/message.go +++ b/message.go @@ -11,7 +11,7 @@ import ( // Message represents an email. type Message struct { header header - parts []part + parts []*part attachments []*file embedded []*file charset string @@ -23,8 +23,9 @@ type Message struct { type header map[string][]string type part struct { - header header - copier func(io.Writer) error + contentType string + copier func(io.Writer) error + encoding Encoding } // NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding @@ -179,16 +180,8 @@ func (m *Message) GetHeader(field string) []string { // SetBody sets the body of the message. It replaces any content previously set // by SetBody, AddAlternative or AddAlternativeWriter. -func (m *Message) SetBody(contentType, body string) { - m.parts = []part{ - { - header: m.getPartHeader(contentType), - copier: func(w io.Writer) error { - _, err := io.WriteString(w, body) - return err - }, - }, - } +func (m *Message) SetBody(contentType, body string, settings ...PartSetting) { + m.parts = []*part{m.newPart(contentType, newCopier(body), settings)} } // AddAlternative adds an alternative part to the message. @@ -197,30 +190,48 @@ func (m *Message) SetBody(contentType, body string) { // version for backward compatibility. AddAlternative appends the new part to // the end of the message. So the plain text part should be added before the // HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative -func (m *Message) AddAlternative(contentType, body string) { - m.parts = append(m.parts, part{ - header: m.getPartHeader(contentType), - copier: func(w io.Writer) error { - _, err := io.WriteString(w, body) - return err - }, - }) +func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) { + m.AddAlternativeWriter(contentType, newCopier(body), settings...) +} + +func newCopier(s string) func(io.Writer) error { + return func(w io.Writer) error { + _, err := io.WriteString(w, s) + return err + } } // AddAlternativeWriter adds an alternative part to the message. It can be // useful with the text/template or html/template packages. -func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error) { - m.parts = append(m.parts, part{ - header: m.getPartHeader(contentType), - copier: f, - }) +func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) { + m.parts = append(m.parts, m.newPart(contentType, f, settings)) } -func (m *Message) getPartHeader(contentType string) header { - return map[string][]string{ - "Content-Type": {contentType + "; charset=" + m.charset}, - "Content-Transfer-Encoding": {string(m.encoding)}, +func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part { + p := &part{ + contentType: contentType, + copier: f, + encoding: m.encoding, } + + for _, s := range settings { + s(p) + } + + return p +} + +// A PartSetting can be used as an argument in Message.SetBody, +// Message.AddAlternative or Message.AddAlternativeWriter to configure the part +// added to a message. +type PartSetting func(*part) + +// SetPartEncoding sets the encoding of the part added to the message. By +// default, parts use the same encoding than the message. +func SetPartEncoding(e Encoding) PartSetting { + return PartSetting(func(p *part) { + p.encoding = e + }) } type file struct { diff --git a/message_test.go b/message_test.go index 04c987e..40e44f2 100644 --- a/message_test.go +++ b/message_test.go @@ -168,6 +168,36 @@ func TestAlternative(t *testing.T) { testMessage(t, m, 1, want) } +func TestPartSetting(t *testing.T) { + m := NewMessage() + m.SetHeader("From", "from@example.com") + m.SetHeader("To", "to@example.com") + m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded)) + m.AddAlternative("text/html", "¡Hola, señor!") + + want := &message{ + from: "from@example.com", + to: []string{"to@example.com"}, + content: "From: from@example.com\r\n" + + "To: to@example.com\r\n" + + "Content-Type: multipart/alternative; boundary=_BOUNDARY_1_\r\n" + + "\r\n" + + "--_BOUNDARY_1_\r\n" + + "Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "¡Hola, señor!\r\n" + + "--_BOUNDARY_1_\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n" + + "Content-Transfer-Encoding: quoted-printable\r\n" + + "\r\n" + + "=C2=A1Hola, se=C3=B1or!\r\n" + + "--_BOUNDARY_1_--\r\n", + } + + testMessage(t, m, 1, want) +} + func TestBodyWriter(t *testing.T) { m := NewMessage() m.SetHeader("From", "from@example.com") diff --git a/writeto.go b/writeto.go index 0031bde..490f3b1 100644 --- a/writeto.go +++ b/writeto.go @@ -38,8 +38,7 @@ func (w *messageWriter) writeMessage(m *Message) { w.openMultipart("alternative") } for _, part := range m.parts { - w.writeHeaders(part.header) - w.writeBody(part.copier, m.encoding) + w.writePart(part, m.charset) } if m.hasAlternativePart() { w.closeMultipart() @@ -104,6 +103,14 @@ func (w *messageWriter) closeMultipart() { } } +func (w *messageWriter) writePart(p *part, charset string) { + w.writeHeaders(map[string][]string{ + "Content-Type": {p.contentType + "; charset=" + charset}, + "Content-Transfer-Encoding": {string(p.encoding)}, + }) + w.writeBody(p.copier, p.encoding) +} + func (w *messageWriter) addFiles(files []*file, isAttachment bool) { for _, f := range files { if _, ok := f.Header["Content-Type"]; !ok {