Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up some copy-paste code related to SMTP communication #143

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
138 changes: 53 additions & 85 deletions email.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,32 +504,51 @@ func (e *Email) Bytes() ([]byte, error) {
return buff.Bytes(), nil
}

// Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
// This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) Send(addr string, a smtp.Auth) error {
type smtpInfo struct {
to []string
sender string
raw []byte
}

func (e *Email) generateSMTPInfo() (*smtpInfo, error) {
// Merge the To, Cc, and Bcc fields
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
for i := 0; i < len(to); i++ {
addr, err := mail.ParseAddress(to[i])
if err != nil {
return err
return nil, err
}
to[i] = addr.Address
}
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
return nil, errors.New("Must specify at least one From address and one To address")
}
sender, err := e.parseSender()
if err != nil {
return err
return nil, err
}
raw, err := e.Bytes()
if err != nil {
return nil, err
}

return &smtpInfo{
to: to,
sender: sender,
raw: raw}, err
}

// Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
// This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) Send(addr string, a smtp.Auth) error {
s, err := e.generateSMTPInfo()
if err != nil {
return err
}
return smtp.SendMail(addr, a, sender, to, raw)

return smtp.SendMail(addr, a, s.sender, s.to, s.raw)
}

// Select and parse an SMTP envelope sender address. Choose Email.Sender if set, or fallback to Email.From.
Expand All @@ -549,30 +568,36 @@ func (e *Email) parseSender() (string, error) {
}
}

// SendWithTLS sends an email over tls with an optional TLS config.
//
// The TLS Config is helpful if you need to connect to a host that is used an untrusted
// certificate.
func (e *Email) SendWithTLS(addr string, a smtp.Auth, t *tls.Config) error {
// Merge the To, Cc, and Bcc fields
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
for i := 0; i < len(to); i++ {
addr, err := mail.ParseAddress(to[i])
if err != nil {
func (e *Email) sendHelper(c *smtp.Client, s *smtpInfo) error {
if err := c.Mail(s.sender); err != nil {
return err
}
for _, addr := range s.to {
if err := c.Rcpt(addr); err != nil {
return err
}
to[i] = addr.Address
}
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
w, err := c.Data()
if err != nil {
return err
}
sender, err := e.parseSender()
_, err = w.Write(s.raw)
if err != nil {
return err
}
raw, err := e.Bytes()
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}

// SendWithTLS sends an email over tls with an optional TLS config.
//
// The TLS Config is helpful if you need to connect to a host that is used an untrusted
// certificate.
func (e *Email) SendWithTLS(addr string, a smtp.Auth, t *tls.Config) error {
s, err := e.generateSMTPInfo()
if err != nil {
return err
}
Expand All @@ -598,53 +623,16 @@ func (e *Email) SendWithTLS(addr string, a smtp.Auth, t *tls.Config) error {
}
}
}
if err = c.Mail(sender); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(raw)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()

return e.sendHelper(c, s)
}

// SendWithStartTLS sends an email over TLS using STARTTLS with an optional TLS config.
//
// The TLS Config is helpful if you need to connect to a host that is used an untrusted
// certificate.
func (e *Email) SendWithStartTLS(addr string, a smtp.Auth, t *tls.Config) error {
// Merge the To, Cc, and Bcc fields
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
for i := 0; i < len(to); i++ {
addr, err := mail.ParseAddress(to[i])
if err != nil {
return err
}
to[i] = addr.Address
}
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
}
sender, err := e.parseSender()
if err != nil {
return err
}
raw, err := e.Bytes()
s, err := e.generateSMTPInfo()
if err != nil {
return err
}
Expand Down Expand Up @@ -673,27 +661,7 @@ func (e *Email) SendWithStartTLS(addr string, a smtp.Auth, t *tls.Config) error
}
}
}
if err = c.Mail(sender); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(raw)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
return e.sendHelper(c, s)
}

// Attachment is a struct representing an email attachment.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/jordan-wright/email

go 1.15
68 changes: 5 additions & 63 deletions pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"io"
"net"
"net/mail"
"net/smtp"
"net/textproto"
"sync"
Expand Down Expand Up @@ -285,75 +284,18 @@ func (p *Pool) Send(e *Email, timeout time.Duration) (err error) {
return p.failedToGet(start)
}

defer func() {
p.maybeReplace(err, c)
}()

recipients, err := addressLists(e.To, e.Cc, e.Bcc)
if err != nil {
return
}

msg, err := e.Bytes()
if err != nil {
return
}

from, err := emailOnly(e.From)
if err != nil {
return
}
if err = c.Mail(from); err != nil {
return
}

for _, recip := range recipients {
if err = c.Rcpt(recip); err != nil {
return
}
}

w, err := c.Data()
var s *smtpInfo
s, err = e.generateSMTPInfo()
if err != nil {
return
}
if _, err = w.Write(msg); err != nil {
return
return err
}

err = w.Close()
err = e.sendHelper(c.Client, s)
p.maybeReplace(err, c)

return
}

func emailOnly(full string) (string, error) {
addr, err := mail.ParseAddress(full)
if err != nil {
return "", err
}
return addr.Address, nil
}

func addressLists(lists ...[]string) ([]string, error) {
length := 0
for _, lst := range lists {
length += len(lst)
}
combined := make([]string, 0, length)

for _, lst := range lists {
for _, full := range lst {
addr, err := emailOnly(full)
if err != nil {
return nil, err
}
combined = append(combined, addr)
}
}

return combined, nil
}

// Close immediately changes the pool's state so no new connections will be
// created, then gets and closes the existing ones as they become available.
func (p *Pool) Close() {
Expand Down