email.go 2.67 KB
package webutility

// TODO(markO): test test test test test (and open source?)

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net/smtp"
	"strings"
)

type Email struct {
	To      []string
	From    string
	Subject string
	Body    string
}

func NewEmail(to []string, from, subject, body string) *Email {
	return &Email{
		To:      to,
		From:    from,
		Subject: subject,
		Body:    body,
	}
}

func (e *Email) String() string {
	var str strings.Builder

	str.WriteString("From:" + e.From + "\r\n")

	str.WriteString("To:")
	for i, _ := range e.To {
		if i > 0 {
			str.WriteString(",")
		}
		str.WriteString(e.To[i])
	}
	str.WriteString("\r\n")

	str.WriteString("Subject:" + e.Subject + "\r\n")

	// body
	str.WriteString("\r\n" + e.Body + "\r\n")

	return str.String()
}

func (e *Email) Bytes() []byte {
	return []byte(e.String())
}

func SendEmail(email *Email, conf *EmailConfig) error {
	if email == nil {
		return errors.New("no email to send")
	}

	if conf == nil {
		return errors.New("email configuration not provided")
	}

	c, err := smtp.Dial(conf.ServerName)
	if err != nil {
		return err
	}
	defer c.Close()

	// not sure if this is needed
	//if err = c.Hello(conf.ServerName); err != nil {
	//	return err
	//}

	if ok, _ := c.Extension("STARTTLS"); ok {
		// disable stupid tls check for self-signed certificates
		config := &tls.Config{
			ServerName:         conf.ServerName,
			InsecureSkipVerify: true,
		}
		// for golang testing
		//if testHookStartTLS != nil {
		//	testHookStartTLS(config)
		//}
		if err = c.StartTLS(config); err != nil {
			return err
		}
	}

	/*
		// don't know what to do with this
		if a != nil && c.ext != nil {
			if _, ok := c.ext["AUTH"]; !ok {
				return errors.New("smtp: server doesn't support AUTH")
			}
			if err = c.Auth(a); err != nil {
				return err
			}
		}
	*/

	// Set up authentication information.
	auth := smtp.PlainAuth(conf.Identity, conf.Username, conf.Password, conf.Host)
	if err = c.Auth(auth); err != nil {
		return err
	}

	if err = c.Mail(email.From); err != nil {
		return err
	}

	for _, addr := range email.To {
		if err = c.Rcpt(addr); err != nil {
			return err
		}
	}

	w, err := c.Data()
	if err != nil {
		return err
	}

	_, err = w.Write(email.Bytes())
	if err != nil {
		return err
	}

	err = w.Close()
	if err != nil {
		return err
	}

	return c.Quit()
}

type EmailConfig struct {
	ServerName string
	Identity   string
	Username   string
	Password   string
	Host       string
	Port       int
}

func NewEmailConfig(ident, uname, pword, host string, port int) *EmailConfig {
	return &EmailConfig{
		ServerName: host + fmt.Sprintf(":%d", port),
		Identity:   ident,
		Username:   uname,
		Password:   pword,
		Host:       host,
		Port:       port,
	}
}