161 lines
3.6 KiB
Go
161 lines
3.6 KiB
Go
|
package gomail
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"net/mail"
|
||
|
"net/smtp"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// A Mailer represents an SMTP server.
|
||
|
type Mailer struct {
|
||
|
addr string
|
||
|
auth smtp.Auth
|
||
|
send SendMailFunc
|
||
|
}
|
||
|
|
||
|
// A MailerSetting can be used in a mailer constructor to configure it.
|
||
|
type MailerSetting func(m *Mailer)
|
||
|
|
||
|
// SetSendMail is an option to set the email-sending function of a mailer.
|
||
|
//
|
||
|
// Example:
|
||
|
//
|
||
|
// myFunc := func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
||
|
// // Implement your email-sending function similar to smtp.SendMail
|
||
|
// }
|
||
|
// mailer := NewMailer("host", "username", "password", 25, SetSendMail(myFunc))
|
||
|
func SetSendMail(s SendMailFunc) MailerSetting {
|
||
|
return func(m *Mailer) {
|
||
|
m.send = s
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// A SendMailFunc is a function to send emails with the same signature than
|
||
|
// smtp.SendMail.
|
||
|
type SendMailFunc func(addr string, a smtp.Auth, from string, to []string, msg []byte) error
|
||
|
|
||
|
// NewMailer returns a mailer. The given parameters are used to connect to the
|
||
|
// SMTP server via a PLAIN authentication mechanism.
|
||
|
func NewMailer(host string, username string, password string, port int, settings ...MailerSetting) *Mailer {
|
||
|
return NewCustomMailer(
|
||
|
fmt.Sprintf("%s:%d", host, port),
|
||
|
smtp.PlainAuth("", username, password, host),
|
||
|
settings...,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// NewCustomMailer creates a mailer with the given authentication mechanism.
|
||
|
//
|
||
|
// Example:
|
||
|
//
|
||
|
// gomail.NewCustomMailer("host:25", smtp.CRAMMD5Auth("username", "secret"))
|
||
|
func NewCustomMailer(addr string, auth smtp.Auth, settings ...MailerSetting) *Mailer {
|
||
|
m := &Mailer{
|
||
|
addr: addr,
|
||
|
auth: auth,
|
||
|
send: smtp.SendMail,
|
||
|
}
|
||
|
m.applySettings(settings)
|
||
|
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
func (m *Mailer) applySettings(settings []MailerSetting) {
|
||
|
for _, s := range settings {
|
||
|
s(m)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Send sends the emails to all the recipients of the message.
|
||
|
func (m *Mailer) Send(msg *Message) error {
|
||
|
message := msg.Export()
|
||
|
|
||
|
from, err := getFrom(message)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
recipients, bcc := getRecipients(message)
|
||
|
|
||
|
h := flattenHeader(message, "")
|
||
|
body, err := ioutil.ReadAll(message.Body)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
mail := append(h, body...)
|
||
|
if err := m.send(m.addr, m.auth, from, recipients, mail); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
for _, to := range bcc {
|
||
|
h = flattenHeader(message, to)
|
||
|
mail = append(h, body...)
|
||
|
if err := m.send(m.addr, m.auth, from, []string{to}, mail); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func flattenHeader(msg *mail.Message, bcc string) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
for field, value := range msg.Header {
|
||
|
if field != "Bcc" {
|
||
|
buf.WriteString(field + ": " + strings.Join(value, ", ") + "\r\n")
|
||
|
} else if bcc != "" {
|
||
|
for _, to := range value {
|
||
|
if strings.Contains(to, bcc) {
|
||
|
buf.WriteString(field + ": " + to + "\r\n")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
buf.WriteString("\r\n")
|
||
|
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
func getFrom(msg *mail.Message) (string, error) {
|
||
|
from := msg.Header.Get("Sender")
|
||
|
if from == "" {
|
||
|
from = msg.Header.Get("From")
|
||
|
if from == "" {
|
||
|
return "", errors.New("mailer: invalid message, \"From\" field is absent")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return from, nil
|
||
|
}
|
||
|
|
||
|
func getRecipients(msg *mail.Message) (recipients, bcc []string) {
|
||
|
for _, field := range []string{"Bcc", "To", "Cc"} {
|
||
|
if addresses, ok := msg.Header[field]; ok {
|
||
|
for _, addr := range addresses {
|
||
|
switch field {
|
||
|
case "Bcc":
|
||
|
bcc = addAdress(bcc, addr)
|
||
|
default:
|
||
|
recipients = addAdress(recipients, addr)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return recipients, bcc
|
||
|
}
|
||
|
|
||
|
func addAdress(list []string, addr string) []string {
|
||
|
for _, a := range list {
|
||
|
if addr == a {
|
||
|
return list
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return append(list, addr)
|
||
|
}
|