mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 05:40:50 +01:00
v0.1.1: Added embeds/attachments
This commit is contained in:
parent
13f4c54a27
commit
1157180369
8 changed files with 271 additions and 60 deletions
|
@ -27,8 +27,8 @@ Some of the features of this library:
|
||||||
* [X] RFC5322 compliant mail address validation
|
* [X] RFC5322 compliant mail address validation
|
||||||
* [X] Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, etc.)
|
* [X] Support for common mail header field generation (Message-ID, Date, Bulk-Precedence, etc.)
|
||||||
* [X] Reusing the same SMTP connection to send multiple mails
|
* [X] Reusing the same SMTP connection to send multiple mails
|
||||||
|
* [X] Support for attachments and inline embeds
|
||||||
* [ ] Support for different encodings
|
* [ ] Support for different encodings
|
||||||
* [ ] Support for attachments
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
The packageis shipped with example code. Check it out on the packages
|
The packageis shipped with example code. Check it out on the packages
|
||||||
|
|
|
@ -83,6 +83,10 @@ var (
|
||||||
// ErrNoActiveConnection should be used when a method is used that requies a server connection
|
// ErrNoActiveConnection should be used when a method is used that requies a server connection
|
||||||
// but is not yet connected
|
// but is not yet connected
|
||||||
ErrNoActiveConnection = errors.New("not connected to SMTP server")
|
ErrNoActiveConnection = errors.New("not connected to SMTP server")
|
||||||
|
|
||||||
|
// ErrServerNoUnencoded should be used when 8BIT encoding is selected for a message, but
|
||||||
|
// the server does not offer 8BITMIME mode
|
||||||
|
ErrServerNoUnencoded = errors.New("message is 8bit unencoded, but server does not support 8BITMIME")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClient returns a new Session client object
|
// NewClient returns a new Session client object
|
||||||
|
@ -281,6 +285,11 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
return fmt.Errorf("failed to send mail: %w", err)
|
return fmt.Errorf("failed to send mail: %w", err)
|
||||||
}
|
}
|
||||||
for _, m := range ml {
|
for _, m := range ml {
|
||||||
|
if m.encoding == NoEncoding {
|
||||||
|
if ok, _ := c.sc.Extension("8BITMIME"); !ok {
|
||||||
|
return ErrServerNoUnencoded
|
||||||
|
}
|
||||||
|
}
|
||||||
f, err := m.GetSender(false)
|
f, err := m.GetSender(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
58
cmd/main.go
58
cmd/main.go
|
@ -1,9 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/wneessen/go-mail"
|
"github.com/wneessen/go-mail"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,34 +27,46 @@ func main() {
|
||||||
fmt.Printf("failed to set TO address: %s", err)
|
fmt.Printf("failed to set TO address: %s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
m.Subject("This is the Subject with Umlauts: üöäß")
|
m.Subject("This is a mail with attachments")
|
||||||
m.SetHeader(mail.HeaderContentLang, "de", "en", "fr", "sp", "de", "en", "fr", "sp", "de", "en", "fr",
|
|
||||||
"sp", "de", "en", "fr", "sp")
|
|
||||||
m.SetHeader(mail.HeaderListUnsubscribePost, "üüüüüüüü", "aaaaääää", "ßßßßßßßßß", "XXXXXX", "ZZZZZ", "XXXXXXXX",
|
|
||||||
"äää äää", "YYYYYY", "XXXXXX", "ZZZZZ", "üäö´")
|
|
||||||
m.SetMessageID()
|
m.SetMessageID()
|
||||||
m.SetDate()
|
m.SetDate()
|
||||||
m.SetBulk()
|
m.SetBulk()
|
||||||
|
m.SetBodyString(mail.TypeTextPlain, "This should have an attachment.")
|
||||||
|
|
||||||
m.SetBodyWriter(mail.TypeTextPlain, func(fw io.Writer) error {
|
f, err := os.Open("/home/wneessen/certs.csv")
|
||||||
_, err := io.WriteString(fw, "This is a writer test")
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
m.AddAlternativeString(mail.TypeTextHTML, "This is HTML content")
|
|
||||||
//m.Write(os.Stdout)
|
|
||||||
|
|
||||||
tu := os.Getenv("TEST_USER")
|
|
||||||
tp := os.Getenv("TEST_PASS")
|
|
||||||
c, err := mail.NewClient(th, mail.WithTLSPolicy(mail.TLSMandatory),
|
|
||||||
mail.WithSMTPAuth(mail.SMTPAuthLogin), mail.WithUsername(tu),
|
|
||||||
mail.WithPassword(tp))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to create new client: %s\n", err)
|
fmt.Printf("failed to open file for reading: %s\n", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err := c.DialAndSend(m); err != nil {
|
|
||||||
fmt.Printf("failed to dial: %s\n", err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
fmt.Printf("failed to close file: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
m.AttachReader("certs.csv", f, mail.WithFileName("test.txt"))
|
||||||
|
|
||||||
|
sendMail := flag.Bool("send", false, "wether to send mail or output to STDOUT")
|
||||||
|
flag.Parse()
|
||||||
|
if !*sendMail {
|
||||||
|
_, err := m.Write(os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to write mail: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tu := os.Getenv("TEST_USER")
|
||||||
|
tp := os.Getenv("TEST_PASS")
|
||||||
|
c, err := mail.NewClient(th, mail.WithTLSPolicy(mail.TLSMandatory),
|
||||||
|
mail.WithSMTPAuth(mail.SMTPAuthLogin), mail.WithUsername(tu),
|
||||||
|
mail.WithPassword(tp))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to create new client: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := c.DialAndSend(m); err != nil {
|
||||||
|
fmt.Printf("failed to dial: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
33
file.go
Normal file
33
file.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/textproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileOption returns a function that can be used for grouping File options
|
||||||
|
type FileOption func(*File)
|
||||||
|
|
||||||
|
// File is an attachment or embedded file of the Msg
|
||||||
|
type File struct {
|
||||||
|
Name string
|
||||||
|
Header textproto.MIMEHeader
|
||||||
|
Writer func(w io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFileName sets the filename of the File
|
||||||
|
func WithFileName(n string) FileOption {
|
||||||
|
return func(f *File) {
|
||||||
|
f.Name = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setHeader sets header fields to a File
|
||||||
|
func (f *File) setHeader(h Header, v string) {
|
||||||
|
f.Header.Set(string(h), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) getHeader(h Header) (string, bool) {
|
||||||
|
v := f.Header.Get(string(h))
|
||||||
|
return v, v != ""
|
||||||
|
}
|
|
@ -14,6 +14,9 @@ const (
|
||||||
// HeaderContentDisposition is the "Content-Disposition" header
|
// HeaderContentDisposition is the "Content-Disposition" header
|
||||||
HeaderContentDisposition Header = "Content-Disposition"
|
HeaderContentDisposition Header = "Content-Disposition"
|
||||||
|
|
||||||
|
// HeaderContentID is the "Content-ID" header
|
||||||
|
HeaderContentID Header = "Content-ID"
|
||||||
|
|
||||||
// HeaderContentLang is the "Content-Language" header
|
// HeaderContentLang is the "Content-Language" header
|
||||||
HeaderContentLang Header = "Content-Language"
|
HeaderContentLang Header = "Content-Language"
|
||||||
|
|
||||||
|
|
137
msg.go
137
msg.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"mime"
|
"mime"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,17 +46,13 @@ type Msg struct {
|
||||||
|
|
||||||
// parts represent the different parts of the Msg
|
// parts represent the different parts of the Msg
|
||||||
parts []*Part
|
parts []*Part
|
||||||
}
|
|
||||||
|
|
||||||
// Part is a part of the Msg
|
// attachments represent the different attachment File of the Msg
|
||||||
type Part struct {
|
attachments []*File
|
||||||
w func(io.Writer) error
|
|
||||||
ctype ContentType
|
|
||||||
enc Encoding
|
|
||||||
}
|
|
||||||
|
|
||||||
// PartOption returns a function that can be used for grouping Part options
|
// embeds represent the different embedded File of the Msg
|
||||||
type PartOption func(*Part)
|
embeds []*File
|
||||||
|
}
|
||||||
|
|
||||||
// MsgOption returns a function that can be used for grouping Msg options
|
// MsgOption returns a function that can be used for grouping Msg options
|
||||||
type MsgOption func(*Msg)
|
type MsgOption func(*Msg)
|
||||||
|
@ -372,7 +369,7 @@ func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) {
|
||||||
|
|
||||||
// SetBodyWriter sets the body of the message.
|
// SetBodyWriter sets the body of the message.
|
||||||
func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) {
|
func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) {
|
||||||
p := m.NewPart(ct, o...)
|
p := m.newPart(ct, o...)
|
||||||
p.w = w
|
p.w = w
|
||||||
m.parts = []*Part{p}
|
m.parts = []*Part{p}
|
||||||
}
|
}
|
||||||
|
@ -389,11 +386,31 @@ func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) {
|
||||||
|
|
||||||
// AddAlternativeWriter sets the body of the message.
|
// AddAlternativeWriter sets the body of the message.
|
||||||
func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) {
|
func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) {
|
||||||
p := m.NewPart(ct, o...)
|
p := m.newPart(ct, o...)
|
||||||
p.w = w
|
p.w = w
|
||||||
m.parts = append(m.parts, p)
|
m.parts = append(m.parts, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttachFile adds an attachment File to the Msg
|
||||||
|
func (m *Msg) AttachFile(n string, o ...FileOption) {
|
||||||
|
m.attachments = m.appendFile(m.attachments, fileFromFS(n), o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachReader adds an attachment File via io.Reader to the Msg
|
||||||
|
func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) {
|
||||||
|
m.attachments = m.appendFile(m.attachments, fileFromReader(n, r), o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedFile adds an embedded File to the Msg
|
||||||
|
func (m *Msg) EmbedFile(n string, o ...FileOption) {
|
||||||
|
m.embeds = m.appendFile(m.embeds, fileFromFS(n), o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedReader adds an embedded File from an io.Reader to the Msg
|
||||||
|
func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) {
|
||||||
|
m.embeds = m.appendFile(m.embeds, fileFromReader(n, r), o...)
|
||||||
|
}
|
||||||
|
|
||||||
// Write writes the formated Msg into a give io.Writer
|
// Write writes the formated Msg into a give io.Writer
|
||||||
func (m *Msg) Write(w io.Writer) (int64, error) {
|
func (m *Msg) Write(w io.Writer) (int64, error) {
|
||||||
mw := &msgWriter{w: w}
|
mw := &msgWriter{w: w}
|
||||||
|
@ -401,8 +418,46 @@ func (m *Msg) Write(w io.Writer) (int64, error) {
|
||||||
return mw.n, mw.err
|
return mw.n, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPart returns a new Part for the Msg
|
// appendFile adds a File to the Msg (as attachment or embed)
|
||||||
func (m *Msg) NewPart(ct ContentType, o ...PartOption) *Part {
|
func (m *Msg) appendFile(c []*File, f *File, o ...FileOption) []*File {
|
||||||
|
// Override defaults with optionally provided FileOption functions
|
||||||
|
for _, co := range o {
|
||||||
|
if co == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
co(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return []*File{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(c, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeString encodes a string based on the configured message encoder and the corresponding
|
||||||
|
// charset for the Msg
|
||||||
|
func (m *Msg) encodeString(s string) string {
|
||||||
|
return m.encoder.Encode(string(m.charset), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasAlt returns true if the Msg has more than one part
|
||||||
|
func (m *Msg) hasAlt() bool {
|
||||||
|
return len(m.parts) > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasMixed returns true if the Msg has mixed parts
|
||||||
|
func (m *Msg) hasMixed() bool {
|
||||||
|
return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasRelated returns true if the Msg has related parts
|
||||||
|
func (m *Msg) hasRelated() bool {
|
||||||
|
return (len(m.parts) > 0 && len(m.embeds) > 0) || len(m.embeds) > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPart returns a new Part for the Msg
|
||||||
|
func (m *Msg) newPart(ct ContentType, o ...PartOption) *Part {
|
||||||
p := &Part{
|
p := &Part{
|
||||||
ctype: ct,
|
ctype: ct,
|
||||||
enc: m.encoding,
|
enc: m.encoding,
|
||||||
|
@ -419,23 +474,44 @@ func (m *Msg) NewPart(ct ContentType, o ...PartOption) *Part {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPartEncoding overrides the default Part encoding
|
|
||||||
func WithPartEncoding(e Encoding) PartOption {
|
|
||||||
return func(p *Part) {
|
|
||||||
p.enc = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message
|
|
||||||
func (p *Part) SetEncoding(e Encoding) {
|
|
||||||
p.enc = e
|
|
||||||
}
|
|
||||||
|
|
||||||
// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
func (m *Msg) setEncoder() {
|
func (m *Msg) setEncoder() {
|
||||||
m.encoder = getEncoder(m.encoding)
|
m.encoder = getEncoder(m.encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fileFromFS returns a File pointer from a given file in the system's file system
|
||||||
|
func fileFromFS(n string) *File {
|
||||||
|
return &File{
|
||||||
|
Name: filepath.Base(n),
|
||||||
|
Header: make(map[string][]string),
|
||||||
|
Writer: func(w io.Writer) error {
|
||||||
|
h, err := os.Open(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, h); err != nil {
|
||||||
|
_ = h.Close()
|
||||||
|
return fmt.Errorf("failed to copy file to io.Writer: %w", err)
|
||||||
|
}
|
||||||
|
return h.Close()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileFromReader returns a File pointer from a given io.Reader
|
||||||
|
func fileFromReader(n string, r io.Reader) *File {
|
||||||
|
return &File{
|
||||||
|
Name: filepath.Base(n),
|
||||||
|
Header: make(map[string][]string),
|
||||||
|
Writer: func(w io.Writer) error {
|
||||||
|
if _, err := io.Copy(w, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
func getEncoder(e Encoding) mime.WordEncoder {
|
func getEncoder(e Encoding) mime.WordEncoder {
|
||||||
switch e {
|
switch e {
|
||||||
|
@ -447,14 +523,3 @@ func getEncoder(e Encoding) mime.WordEncoder {
|
||||||
return mime.QEncoding
|
return mime.QEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeString encodes a string based on the configured message encoder and the corresponding
|
|
||||||
// charset for the Msg
|
|
||||||
func (m *Msg) encodeString(s string) string {
|
|
||||||
return m.encoder.Encode(string(m.charset), s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasAlt returns true if the Msg has more than one part
|
|
||||||
func (m *Msg) hasAlt() bool {
|
|
||||||
return len(m.parts) > 1
|
|
||||||
}
|
|
||||||
|
|
64
msgwriter.go
64
msgwriter.go
|
@ -4,9 +4,11 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"mime/quotedprintable"
|
"mime/quotedprintable"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,16 +57,37 @@ func (mw *msgWriter) writeMsg(m *Msg) {
|
||||||
}
|
}
|
||||||
mw.writeHeader(HeaderMIMEVersion, string(m.mimever))
|
mw.writeHeader(HeaderMIMEVersion, string(m.mimever))
|
||||||
|
|
||||||
|
if m.hasMixed() {
|
||||||
|
mw.startMP("mixed", m.boundary)
|
||||||
|
mw.writeString("\r\n\r\n")
|
||||||
|
}
|
||||||
|
if m.hasRelated() {
|
||||||
|
mw.startMP("related", m.boundary)
|
||||||
|
mw.writeString("\r\n\r\n")
|
||||||
|
}
|
||||||
if m.hasAlt() {
|
if m.hasAlt() {
|
||||||
mw.startMP(MIMEAlternative, m.boundary)
|
mw.startMP(MIMEAlternative, m.boundary)
|
||||||
mw.writeString("\r\n\r\n")
|
mw.writeString("\r\n\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range m.parts {
|
for _, p := range m.parts {
|
||||||
mw.writePart(p, m.charset)
|
mw.writePart(p, m.charset)
|
||||||
}
|
}
|
||||||
if m.hasAlt() {
|
if m.hasAlt() {
|
||||||
mw.stopMP()
|
mw.stopMP()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add embeds
|
||||||
|
mw.addFiles(m.embeds, false)
|
||||||
|
if m.hasRelated() {
|
||||||
|
mw.stopMP()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add attachments
|
||||||
|
mw.addFiles(m.attachments, true)
|
||||||
|
if m.hasMixed() {
|
||||||
|
mw.stopMP()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startMP writes a multipart beginning
|
// startMP writes a multipart beginning
|
||||||
|
@ -94,6 +117,47 @@ func (mw *msgWriter) stopMP() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addFiles adds the attachments/embeds file content to the mail body
|
||||||
|
func (mw *msgWriter) addFiles(fl []*File, a bool) {
|
||||||
|
for _, f := range fl {
|
||||||
|
if _, ok := f.getHeader(HeaderContentType); !ok {
|
||||||
|
mt := mime.TypeByExtension(filepath.Ext(f.Name))
|
||||||
|
if mt == "" {
|
||||||
|
mt = "application/octet-stream"
|
||||||
|
}
|
||||||
|
f.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mt, f.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := f.getHeader(HeaderContentTransferEnc); !ok {
|
||||||
|
f.setHeader(HeaderContentTransferEnc, string(EncodingB64))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := f.getHeader(HeaderContentDisposition); !ok {
|
||||||
|
d := "inline"
|
||||||
|
if a {
|
||||||
|
d = "attachment"
|
||||||
|
}
|
||||||
|
f.setHeader(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, d, f.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a {
|
||||||
|
if _, ok := f.getHeader(HeaderContentID); !ok {
|
||||||
|
f.setHeader(HeaderContentID, fmt.Sprintf("<%s>", f.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mw.d == 0 {
|
||||||
|
for h, v := range f.Header {
|
||||||
|
mw.writeHeader(Header(h), v...)
|
||||||
|
}
|
||||||
|
mw.writeString("\r\n")
|
||||||
|
}
|
||||||
|
if mw.d > 0 {
|
||||||
|
mw.newPart(f.Header)
|
||||||
|
}
|
||||||
|
mw.writeBody(f.Writer, EncodingB64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// newPart creates a new MIME multipart io.Writer and sets the partwriter to it
|
// newPart creates a new MIME multipart io.Writer and sets the partwriter to it
|
||||||
func (mw *msgWriter) newPart(h map[string][]string) {
|
func (mw *msgWriter) newPart(h map[string][]string) {
|
||||||
mw.pw, mw.err = mw.mpw[mw.d-1].CreatePart(h)
|
mw.pw, mw.err = mw.mpw[mw.d-1].CreatePart(h)
|
||||||
|
|
25
part.go
Normal file
25
part.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// PartOption returns a function that can be used for grouping Part options
|
||||||
|
type PartOption func(*Part)
|
||||||
|
|
||||||
|
// Part is a part of the Msg
|
||||||
|
type Part struct {
|
||||||
|
ctype ContentType
|
||||||
|
enc Encoding
|
||||||
|
w func(io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
|
func (p *Part) SetEncoding(e Encoding) {
|
||||||
|
p.enc = e
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPartEncoding overrides the default Part encoding
|
||||||
|
func WithPartEncoding(e Encoding) PartOption {
|
||||||
|
return func(p *Part) {
|
||||||
|
p.enc = e
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue