gomail/mailer.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 := gomail.NewMailer("host", "user", "pwd", 465, 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)
}