github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/notify/mailer.go (about)

     1  /**
     2   *  mailer.go
     3   *
     4   *  Created on: October 23 2013
     5   *      Author: Valeri Karpov <valeri.karpov@mongodb.com>
     6   *
     7   *  Defines a functional abstraction for sending an email and a few concrete
     8   *  functions matching that abstraction.
     9   *
    10   */
    11  
    12  package notify
    13  
    14  import (
    15  	"bytes"
    16  	"crypto/tls"
    17  	"encoding/base64"
    18  	"fmt"
    19  	"net/mail"
    20  	"net/smtp"
    21  	"strings"
    22  
    23  	"github.com/mongodb/grip"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  type Mailer interface {
    28  	SendMail([]string, string, string) error
    29  }
    30  
    31  type SmtpMailer struct {
    32  	From     string
    33  	Server   string
    34  	Port     int
    35  	UseSSL   bool
    36  	Username string
    37  	Password string
    38  }
    39  
    40  /* Connects an SMTP server (usually localhost:25 in prod) and uses that to
    41     send an email with the body encoded in base64. */
    42  func (self SmtpMailer) SendMail(recipients []string, subject, body string) error {
    43  	// 'recipients' is expected a comma-separated list of emails in either of
    44  	// these formats:
    45  	// - bob@example.com
    46  	// - Bob Smith <bob@example.com>
    47  	var c *smtp.Client
    48  	var err error
    49  
    50  	if self.UseSSL {
    51  		var tlsCon *tls.Conn
    52  
    53  		tlsCon, err = tls.Dial("tcp", fmt.Sprintf("%v:%v", self.Server, self.Port), &tls.Config{})
    54  		if err != nil {
    55  			return err
    56  		}
    57  		c, err = smtp.NewClient(tlsCon, self.Server)
    58  	} else {
    59  		c, err = smtp.Dial(fmt.Sprintf("%v:%v", self.Server, self.Port))
    60  	}
    61  
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	if self.Username != "" {
    67  		err = c.Auth(smtp.PlainAuth("", self.Username, self.Password, self.Server))
    68  		if err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	// Set the sender
    74  	from := mail.Address{
    75  		Name:    "Evergreen Alerts",
    76  		Address: self.From,
    77  	}
    78  
    79  	if err = c.Mail(self.From); err != nil {
    80  		grip.Errorf("Error establishing mail sender (%s): %+v", self.From, err)
    81  		return err
    82  	}
    83  
    84  	// Set the recipient
    85  	for _, recipient := range recipients {
    86  		err = c.Rcpt(recipient)
    87  		if err != nil {
    88  			grip.Errorf("Error establishing mail recipient (%s): %+v", recipient, err)
    89  			return err
    90  		}
    91  	}
    92  
    93  	// Send the email body.
    94  	wc, err := c.Data()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	defer wc.Close()
    99  
   100  	// set header information
   101  	header := make(map[string]string)
   102  	header["From"] = from.String()
   103  	header["To"] = strings.Join(recipients, ", ")
   104  	header["Subject"] = encodeRFC2047(subject)
   105  	header["MIME-Version"] = "1.0"
   106  	header["Content-Type"] = "text/html; charset=\"utf-8\""
   107  	header["Content-Transfer-Encoding"] = "base64"
   108  
   109  	message := ""
   110  	for k, v := range header {
   111  		message += fmt.Sprintf("%s: %s\r\n", k, v)
   112  	}
   113  
   114  	message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body))
   115  
   116  	// write the body
   117  	buf := bytes.NewBufferString(message)
   118  	_, err = buf.WriteTo(wc)
   119  
   120  	return errors.WithStack(err)
   121  }