|
|
@@ -1,913 +0,0 @@
|
|
|
-package mail
|
|
|
-
|
|
|
-import (
|
|
|
- "bytes"
|
|
|
- "crypto/tls"
|
|
|
- "encoding/base64"
|
|
|
- "errors"
|
|
|
- "fmt"
|
|
|
- "io/ioutil"
|
|
|
- "mime"
|
|
|
- "net"
|
|
|
- "net/mail"
|
|
|
- "net/textproto"
|
|
|
- "path/filepath"
|
|
|
- "time"
|
|
|
-)
|
|
|
-
|
|
|
-// Email represents an email message.
|
|
|
-type Email struct {
|
|
|
- from string
|
|
|
- sender string
|
|
|
- replyTo string
|
|
|
- returnPath string
|
|
|
- recipients []string
|
|
|
- headers textproto.MIMEHeader
|
|
|
- parts []part
|
|
|
- attachments []*file
|
|
|
- inlines []*file
|
|
|
- Charset string
|
|
|
- Encoding encoding
|
|
|
- Error error
|
|
|
- SMTPServer *smtpClient
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
-SMTPServer represents a SMTP Server
|
|
|
-If authentication is CRAM-MD5 then the Password is the Secret
|
|
|
-*/
|
|
|
-type SMTPServer struct {
|
|
|
- Authentication authType
|
|
|
- Encryption encryption
|
|
|
- Username string
|
|
|
- Password string
|
|
|
- ConnectTimeout time.Duration
|
|
|
- SendTimeout time.Duration
|
|
|
- Host string
|
|
|
- Port int
|
|
|
- KeepAlive bool
|
|
|
-}
|
|
|
-
|
|
|
-//SMTPClient represents a SMTP Client for send email
|
|
|
-type SMTPClient struct {
|
|
|
- Client *smtpClient
|
|
|
- KeepAlive bool
|
|
|
- SendTimeout time.Duration
|
|
|
-}
|
|
|
-
|
|
|
-// part represents the different content parts of an email body.
|
|
|
-type part struct {
|
|
|
- contentType string
|
|
|
- body *bytes.Buffer
|
|
|
-}
|
|
|
-
|
|
|
-// file represents the files that can be added to the email message.
|
|
|
-type file struct {
|
|
|
- filename string
|
|
|
- mimeType string
|
|
|
- data []byte
|
|
|
-}
|
|
|
-
|
|
|
-type encryption int
|
|
|
-
|
|
|
-const (
|
|
|
- // EncryptionNone uses no encryption when sending email
|
|
|
- EncryptionNone encryption = iota
|
|
|
- // EncryptionSSL sets encryption type to SSL when sending email
|
|
|
- EncryptionSSL
|
|
|
- // EncryptionTLS sets encryption type to TLS when sending email
|
|
|
- EncryptionTLS
|
|
|
-)
|
|
|
-
|
|
|
-var encryptionTypes = [...]string{"None", "SSL", "TLS"}
|
|
|
-
|
|
|
-func (encryption encryption) string() string {
|
|
|
- return encryptionTypes[encryption]
|
|
|
-}
|
|
|
-
|
|
|
-type encoding int
|
|
|
-
|
|
|
-const (
|
|
|
- // EncodingNone turns off encoding on the message body
|
|
|
- EncodingNone encoding = iota
|
|
|
- // EncodingBase64 sets the message body encoding to base64
|
|
|
- EncodingBase64
|
|
|
- // EncodingQuotedPrintable sets the message body encoding to quoted-printable
|
|
|
- EncodingQuotedPrintable
|
|
|
-)
|
|
|
-
|
|
|
-var encodingTypes = [...]string{"binary", "base64", "quoted-printable"}
|
|
|
-
|
|
|
-func (encoding encoding) string() string {
|
|
|
- return encodingTypes[encoding]
|
|
|
-}
|
|
|
-
|
|
|
-type contentType int
|
|
|
-
|
|
|
-const (
|
|
|
- // TextPlain sets body type to text/plain in message body
|
|
|
- TextPlain contentType = iota
|
|
|
- // TextHTML sets body type to text/html in message body
|
|
|
- TextHTML
|
|
|
-)
|
|
|
-
|
|
|
-var contentTypes = [...]string{"text/plain", "text/html"}
|
|
|
-
|
|
|
-func (contentType contentType) string() string {
|
|
|
- return contentTypes[contentType]
|
|
|
-}
|
|
|
-
|
|
|
-type authType int
|
|
|
-
|
|
|
-const (
|
|
|
- // AuthPlain implements the PLAIN authentication
|
|
|
- AuthPlain authType = iota
|
|
|
- // AuthLogin implements the LOGIN authentication
|
|
|
- AuthLogin
|
|
|
- // AuthCRAMMD5 implements the CRAM-MD5 authentication
|
|
|
- AuthCRAMMD5
|
|
|
-)
|
|
|
-
|
|
|
-// NewMSG creates a new email. It uses UTF-8 by default. All charsets: http://webcheatsheet.com/HTML/character_sets_list.php
|
|
|
-func NewMSG() *Email {
|
|
|
- email := &Email{
|
|
|
- headers: make(textproto.MIMEHeader),
|
|
|
- Charset: "UTF-8",
|
|
|
- Encoding: EncodingQuotedPrintable,
|
|
|
- }
|
|
|
-
|
|
|
- email.AddHeader("MIME-Version", "1.0")
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-//NewSMTPClient returns the client for send email
|
|
|
-func NewSMTPClient() *SMTPServer {
|
|
|
- server := &SMTPServer{
|
|
|
- Authentication: AuthPlain,
|
|
|
- Encryption: EncryptionNone,
|
|
|
- ConnectTimeout: 10 * time.Second,
|
|
|
- SendTimeout: 10 * time.Second,
|
|
|
- }
|
|
|
- return server
|
|
|
-}
|
|
|
-
|
|
|
-// GetError returns the first email error encountered
|
|
|
-func (email *Email) GetError() error {
|
|
|
- return email.Error
|
|
|
-}
|
|
|
-
|
|
|
-// SetFrom sets the From address.
|
|
|
-func (email *Email) SetFrom(address string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("From", address)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// SetSender sets the Sender address.
|
|
|
-func (email *Email) SetSender(address string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("Sender", address)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// SetReplyTo sets the Reply-To address.
|
|
|
-func (email *Email) SetReplyTo(address string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("Reply-To", address)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// SetReturnPath sets the Return-Path address. This is most often used
|
|
|
-// to send bounced emails to a different email address.
|
|
|
-func (email *Email) SetReturnPath(address string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("Return-Path", address)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddTo adds a To address. You can provide multiple
|
|
|
-// addresses at the same time.
|
|
|
-func (email *Email) AddTo(addresses ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("To", addresses...)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddCc adds a Cc address. You can provide multiple
|
|
|
-// addresses at the same time.
|
|
|
-func (email *Email) AddCc(addresses ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("Cc", addresses...)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddBcc adds a Bcc address. You can provide multiple
|
|
|
-// addresses at the same time.
|
|
|
-func (email *Email) AddBcc(addresses ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddAddresses("Bcc", addresses...)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddAddresses allows you to add addresses to the specified address header.
|
|
|
-func (email *Email) AddAddresses(header string, addresses ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- found := false
|
|
|
-
|
|
|
- // check for a valid address header
|
|
|
- for _, h := range []string{"To", "Cc", "Bcc", "From", "Sender", "Reply-To", "Return-Path"} {
|
|
|
- if header == h {
|
|
|
- found = true
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if !found {
|
|
|
- email.Error = errors.New("Mail Error: Invalid address header; Header: [" + header + "]")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- // check to see if the addresses are valid
|
|
|
- for i := range addresses {
|
|
|
- address, err := mail.ParseAddress(addresses[i])
|
|
|
- if err != nil {
|
|
|
- email.Error = errors.New("Mail Error: " + err.Error() + "; Header: [" + header + "] Address: [" + addresses[i] + "]")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- // check for more than one address
|
|
|
- switch {
|
|
|
- case header == "From" && len(email.from) > 0:
|
|
|
- fallthrough
|
|
|
- case header == "Sender" && len(email.sender) > 0:
|
|
|
- fallthrough
|
|
|
- case header == "Reply-To" && len(email.replyTo) > 0:
|
|
|
- fallthrough
|
|
|
- case header == "Return-Path" && len(email.returnPath) > 0:
|
|
|
- email.Error = errors.New("Mail Error: There can only be one \"" + header + "\" address; Header: [" + header + "] Address: [" + addresses[i] + "]")
|
|
|
- return email
|
|
|
- default:
|
|
|
- // other address types can have more than one address
|
|
|
- }
|
|
|
-
|
|
|
- // save the address
|
|
|
- switch header {
|
|
|
- case "From":
|
|
|
- email.from = address.Address
|
|
|
- case "Sender":
|
|
|
- email.sender = address.Address
|
|
|
- case "Reply-To":
|
|
|
- email.replyTo = address.Address
|
|
|
- case "Return-Path":
|
|
|
- email.returnPath = address.Address
|
|
|
- default:
|
|
|
- // check that the address was added to the recipients list
|
|
|
- email.recipients, err = addAddress(email.recipients, address.Address)
|
|
|
- if err != nil {
|
|
|
- email.Error = errors.New("Mail Error: " + err.Error() + "; Header: [" + header + "] Address: [" + addresses[i] + "]")
|
|
|
- return email
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // make sure the from and sender addresses are different
|
|
|
- if email.from != "" && email.sender != "" && email.from == email.sender {
|
|
|
- email.sender = ""
|
|
|
- email.headers.Del("Sender")
|
|
|
- email.Error = errors.New("Mail Error: From and Sender should not be set to the same address")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- // add all addresses to the headers except for Bcc and Return-Path
|
|
|
- if header != "Bcc" && header != "Return-Path" {
|
|
|
- // add the address to the headers
|
|
|
- email.headers.Add(header, address.String())
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// addAddress adds an address to the address list if it hasn't already been added
|
|
|
-func addAddress(addressList []string, address string) ([]string, error) {
|
|
|
- // loop through the address list to check for dups
|
|
|
- for _, a := range addressList {
|
|
|
- if address == a {
|
|
|
- return addressList, errors.New("Mail Error: Address: [" + address + "] has already been added")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return append(addressList, address), nil
|
|
|
-}
|
|
|
-
|
|
|
-type priority int
|
|
|
-
|
|
|
-const (
|
|
|
- // PriorityLow sets the email priority to Low
|
|
|
- PriorityLow priority = iota
|
|
|
- // PriorityHigh sets the email priority to High
|
|
|
- PriorityHigh
|
|
|
-)
|
|
|
-
|
|
|
-// SetPriority sets the email message priority. Use with
|
|
|
-// either "High" or "Low".
|
|
|
-func (email *Email) SetPriority(priority priority) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- switch priority {
|
|
|
- case PriorityLow:
|
|
|
- email.AddHeaders(textproto.MIMEHeader{
|
|
|
- "X-Priority": {"5 (Lowest)"},
|
|
|
- "X-MSMail-Priority": {"Low"},
|
|
|
- "Importance": {"Low"},
|
|
|
- })
|
|
|
- case PriorityHigh:
|
|
|
- email.AddHeaders(textproto.MIMEHeader{
|
|
|
- "X-Priority": {"1 (Highest)"},
|
|
|
- "X-MSMail-Priority": {"High"},
|
|
|
- "Importance": {"High"},
|
|
|
- })
|
|
|
- default:
|
|
|
- }
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// SetDate sets the date header to the provided date/time.
|
|
|
-// The format of the string should be YYYY-MM-DD HH:MM:SS Time Zone.
|
|
|
-//
|
|
|
-// Example: SetDate("2015-04-28 10:32:00 CDT")
|
|
|
-func (email *Email) SetDate(dateTime string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- const dateFormat = "2006-01-02 15:04:05 MST"
|
|
|
-
|
|
|
- // Try to parse the provided date/time
|
|
|
- dt, err := time.Parse(dateFormat, dateTime)
|
|
|
- if err != nil {
|
|
|
- email.Error = errors.New("Mail Error: Setting date failed with: " + err.Error())
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.headers.Set("Date", dt.Format(time.RFC1123Z))
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// SetSubject sets the subject of the email message.
|
|
|
-func (email *Email) SetSubject(subject string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.AddHeader("Subject", subject)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// SetBody sets the body of the email message.
|
|
|
-func (email *Email) SetBody(contentType contentType, body string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.parts = []part{
|
|
|
- {
|
|
|
- contentType: contentType.string(),
|
|
|
- body: bytes.NewBufferString(body),
|
|
|
- },
|
|
|
- }
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddHeader adds the given "header" with the passed "value".
|
|
|
-func (email *Email) AddHeader(header string, values ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- // check that there is actually a value
|
|
|
- if len(values) < 1 {
|
|
|
- email.Error = errors.New("Mail Error: no value provided; Header: [" + header + "]")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- switch header {
|
|
|
- case "Sender":
|
|
|
- fallthrough
|
|
|
- case "From":
|
|
|
- fallthrough
|
|
|
- case "To":
|
|
|
- fallthrough
|
|
|
- case "Bcc":
|
|
|
- fallthrough
|
|
|
- case "Cc":
|
|
|
- fallthrough
|
|
|
- case "Reply-To":
|
|
|
- fallthrough
|
|
|
- case "Return-Path":
|
|
|
- email.AddAddresses(header, values...)
|
|
|
- case "Date":
|
|
|
- if len(values) > 1 {
|
|
|
- email.Error = errors.New("Mail Error: To many dates provided")
|
|
|
- return email
|
|
|
- }
|
|
|
- email.SetDate(values[0])
|
|
|
- default:
|
|
|
- email.headers[header] = values
|
|
|
- }
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddHeaders is used to add multiple headers at once
|
|
|
-func (email *Email) AddHeaders(headers textproto.MIMEHeader) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- for header, values := range headers {
|
|
|
- email.AddHeader(header, values...)
|
|
|
- }
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddAlternative allows you to add alternative parts to the body
|
|
|
-// of the email message. This is most commonly used to add an
|
|
|
-// html version in addition to a plain text version that was
|
|
|
-// already added with SetBody.
|
|
|
-func (email *Email) AddAlternative(contentType contentType, body string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.parts = append(email.parts,
|
|
|
- part{
|
|
|
- contentType: contentType.string(),
|
|
|
- body: bytes.NewBufferString(body),
|
|
|
- },
|
|
|
- )
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddAttachment allows you to add an attachment to the email message.
|
|
|
-// You can optionally provide a different name for the file.
|
|
|
-func (email *Email) AddAttachment(file string, name ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- if len(name) > 1 {
|
|
|
- email.Error = errors.New("Mail Error: Attach can only have a file and an optional name")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.Error = email.attach(file, false, name...)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddAttachmentBase64 allows you to add an attachment in base64 to the email message.
|
|
|
-// You need provide a name for the file.
|
|
|
-func (email *Email) AddAttachmentBase64(b64File string, name string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- if len(name) < 1 || len(b64File) < 1 {
|
|
|
- email.Error = errors.New("Mail Error: Attach Base64 need have a base64 string and name")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.Error = email.attachB64(b64File, name)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// AddInline allows you to add an inline attachment to the email message.
|
|
|
-// You can optionally provide a different name for the file.
|
|
|
-func (email *Email) AddInline(file string, name ...string) *Email {
|
|
|
- if email.Error != nil {
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- if len(name) > 1 {
|
|
|
- email.Error = errors.New("Mail Error: Inline can only have a file and an optional name")
|
|
|
- return email
|
|
|
- }
|
|
|
-
|
|
|
- email.Error = email.attach(file, true, name...)
|
|
|
-
|
|
|
- return email
|
|
|
-}
|
|
|
-
|
|
|
-// attach does the low level attaching of the files
|
|
|
-func (email *Email) attach(f string, inline bool, name ...string) error {
|
|
|
- // Get the file data
|
|
|
- data, err := ioutil.ReadFile(f)
|
|
|
- if err != nil {
|
|
|
- return errors.New("Mail Error: Failed to add file with following error: " + err.Error())
|
|
|
- }
|
|
|
-
|
|
|
- // get the file mime type
|
|
|
- mimeType := mime.TypeByExtension(filepath.Ext(f))
|
|
|
- if mimeType == "" {
|
|
|
- mimeType = "application/octet-stream"
|
|
|
- }
|
|
|
-
|
|
|
- // get the filename
|
|
|
- _, filename := filepath.Split(f)
|
|
|
-
|
|
|
- // if an alternative filename was provided, use that instead
|
|
|
- if len(name) == 1 {
|
|
|
- filename = name[0]
|
|
|
- }
|
|
|
-
|
|
|
- if inline {
|
|
|
- email.inlines = append(email.inlines, &file{
|
|
|
- filename: filename,
|
|
|
- mimeType: mimeType,
|
|
|
- data: data,
|
|
|
- })
|
|
|
- } else {
|
|
|
- email.attachments = append(email.attachments, &file{
|
|
|
- filename: filename,
|
|
|
- mimeType: mimeType,
|
|
|
- data: data,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// attachB64 does the low level attaching of the files but decoding base64 instead have a filepath
|
|
|
-func (email *Email) attachB64(b64File string, name string) error {
|
|
|
-
|
|
|
- // decode the string
|
|
|
- dec, err := base64.StdEncoding.DecodeString(b64File)
|
|
|
- if err != nil {
|
|
|
- return errors.New("Mail Error: Failed to decode base64 attachment with following error: " + err.Error())
|
|
|
- }
|
|
|
-
|
|
|
- // get the file mime type
|
|
|
- mimeType := mime.TypeByExtension(name)
|
|
|
- if mimeType == "" {
|
|
|
- mimeType = "application/octet-stream"
|
|
|
- }
|
|
|
-
|
|
|
- email.attachments = append(email.attachments, &file{
|
|
|
- filename: name,
|
|
|
- mimeType: mimeType,
|
|
|
- data: dec,
|
|
|
- })
|
|
|
-
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// getFrom returns the sender of the email, if any
|
|
|
-func (email *Email) getFrom() string {
|
|
|
- from := email.returnPath
|
|
|
- if from == "" {
|
|
|
- from = email.sender
|
|
|
- if from == "" {
|
|
|
- from = email.from
|
|
|
- if from == "" {
|
|
|
- from = email.replyTo
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return from
|
|
|
-}
|
|
|
-
|
|
|
-func (email *Email) hasMixedPart() bool {
|
|
|
- return (len(email.parts) > 0 && len(email.attachments) > 0) || len(email.attachments) > 1
|
|
|
-}
|
|
|
-
|
|
|
-func (email *Email) hasRelatedPart() bool {
|
|
|
- return (len(email.parts) > 0 && len(email.inlines) > 0) || len(email.inlines) > 1
|
|
|
-}
|
|
|
-
|
|
|
-func (email *Email) hasAlternativePart() bool {
|
|
|
- return len(email.parts) > 1
|
|
|
-}
|
|
|
-
|
|
|
-// GetMessage builds and returns the email message
|
|
|
-func (email *Email) GetMessage() string {
|
|
|
- msg := newMessage(email)
|
|
|
-
|
|
|
- if email.hasMixedPart() {
|
|
|
- msg.openMultipart("mixed")
|
|
|
- }
|
|
|
-
|
|
|
- if email.hasRelatedPart() {
|
|
|
- msg.openMultipart("related")
|
|
|
- }
|
|
|
-
|
|
|
- if email.hasAlternativePart() {
|
|
|
- msg.openMultipart("alternative")
|
|
|
- }
|
|
|
-
|
|
|
- for _, part := range email.parts {
|
|
|
- msg.addBody(part.contentType, part.body.Bytes())
|
|
|
- }
|
|
|
-
|
|
|
- if email.hasAlternativePart() {
|
|
|
- msg.closeMultipart()
|
|
|
- }
|
|
|
-
|
|
|
- msg.addFiles(email.inlines, true)
|
|
|
- if email.hasRelatedPart() {
|
|
|
- msg.closeMultipart()
|
|
|
- }
|
|
|
-
|
|
|
- msg.addFiles(email.attachments, false)
|
|
|
- if email.hasMixedPart() {
|
|
|
- msg.closeMultipart()
|
|
|
- }
|
|
|
-
|
|
|
- return msg.getHeaders() + msg.body.String()
|
|
|
-}
|
|
|
-
|
|
|
-// Send sends the composed email
|
|
|
-func (email *Email) Send(client *SMTPClient) error {
|
|
|
-
|
|
|
- if email.Error != nil {
|
|
|
- return email.Error
|
|
|
- }
|
|
|
-
|
|
|
- if len(email.recipients) < 1 {
|
|
|
- return errors.New("Mail Error: No recipient specified")
|
|
|
- }
|
|
|
-
|
|
|
- msg := email.GetMessage()
|
|
|
-
|
|
|
- return send(email.from, email.recipients, msg, client)
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-// dial connects to the smtp server with the request encryption type
|
|
|
-func dial(host string, port string, encryption encryption, config *tls.Config) (*smtpClient, error) {
|
|
|
- var conn net.Conn
|
|
|
- var err error
|
|
|
-
|
|
|
- address := host + ":" + port
|
|
|
-
|
|
|
- // do the actual dial
|
|
|
- switch encryption {
|
|
|
- case EncryptionSSL:
|
|
|
- conn, err = tls.Dial("tcp", address, config)
|
|
|
- default:
|
|
|
- conn, err = net.Dial("tcp", address)
|
|
|
- }
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return nil, errors.New("Mail Error on dailing with encryption type " + encryption.string() + ": " + err.Error())
|
|
|
- }
|
|
|
-
|
|
|
- c, err := newClient(conn, host)
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return nil, errors.New("Mail Error on smtp dial: " + err.Error())
|
|
|
- }
|
|
|
-
|
|
|
- return c, err
|
|
|
-}
|
|
|
-
|
|
|
-// smtpConnect connects to the smtp server and starts TLS and passes auth
|
|
|
-// if necessary
|
|
|
-func smtpConnect(host string, port string, a auth, encryption encryption, config *tls.Config) (*smtpClient, error) {
|
|
|
- // connect to the mail server
|
|
|
- c, err := dial(host, port, encryption, config)
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- // send Hello
|
|
|
- if err = c.hi("localhost"); err != nil {
|
|
|
- c.close()
|
|
|
- return nil, errors.New("Mail Error on Hello: " + err.Error())
|
|
|
- }
|
|
|
-
|
|
|
- // start TLS if necessary
|
|
|
- if encryption == EncryptionTLS {
|
|
|
- if ok, _ := c.extension("STARTTLS"); ok {
|
|
|
- if config.ServerName == "" {
|
|
|
- config = &tls.Config{ServerName: host}
|
|
|
- }
|
|
|
-
|
|
|
- if err = c.startTLS(config); err != nil {
|
|
|
- c.close()
|
|
|
- return nil, errors.New("Mail Error on Start TLS: " + err.Error())
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // pass the authentication if necessary
|
|
|
- if a != nil {
|
|
|
- if ok, _ := c.extension("AUTH"); ok {
|
|
|
- if err = c.authenticate(a); err != nil {
|
|
|
- c.close()
|
|
|
- return nil, errors.New("Mail Error on Auth: " + err.Error())
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return c, nil
|
|
|
-}
|
|
|
-
|
|
|
-//Connect returns the smtp client
|
|
|
-func (server *SMTPServer) Connect() (*SMTPClient, error) {
|
|
|
-
|
|
|
- var a auth
|
|
|
-
|
|
|
- switch server.Authentication {
|
|
|
- case AuthPlain:
|
|
|
- if server.Username != "" || server.Password != "" {
|
|
|
- a = plainAuthfn("", server.Username, server.Password, server.Host)
|
|
|
- }
|
|
|
- case AuthLogin:
|
|
|
- if server.Username != "" || server.Password != "" {
|
|
|
- a = loginAuthfn("", server.Username, server.Password, server.Host)
|
|
|
- }
|
|
|
- case AuthCRAMMD5:
|
|
|
- if server.Username != "" || server.Password != "" {
|
|
|
- a = cramMD5Authfn(server.Username, server.Password)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- var smtpConnectChannel chan error
|
|
|
- var c *smtpClient
|
|
|
- var err error
|
|
|
-
|
|
|
- // if there is a ConnectTimeout, setup the channel and do the connect under a goroutine
|
|
|
- if server.ConnectTimeout != 0 {
|
|
|
- smtpConnectChannel = make(chan error, 2)
|
|
|
- go func() {
|
|
|
- c, err = smtpConnect(server.Host, fmt.Sprintf("%d", server.Port), a, server.Encryption, new(tls.Config))
|
|
|
- // send the result
|
|
|
- smtpConnectChannel <- err
|
|
|
- }()
|
|
|
- }
|
|
|
-
|
|
|
- if server.ConnectTimeout == 0 {
|
|
|
- // no ConnectTimeout, just fire the connect
|
|
|
- c, err = smtpConnect(server.Host, fmt.Sprintf("%d", server.Port), a, server.Encryption, new(tls.Config))
|
|
|
- } else {
|
|
|
- // get the connect result or timeout result, which ever happens first
|
|
|
- select {
|
|
|
- case err = <-smtpConnectChannel:
|
|
|
- if err != nil {
|
|
|
- return nil, errors.New(err.Error())
|
|
|
- }
|
|
|
- case <-time.After(server.ConnectTimeout):
|
|
|
- return nil, errors.New("Mail Error: SMTP Connection timed out")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return &SMTPClient{
|
|
|
- Client: c,
|
|
|
- KeepAlive: server.KeepAlive,
|
|
|
- SendTimeout: server.SendTimeout,
|
|
|
- }, nil
|
|
|
-}
|
|
|
-
|
|
|
-// Reset send RSET command to smtp client
|
|
|
-func (smtpClient *SMTPClient) Reset() error {
|
|
|
- return smtpClient.Client.reset()
|
|
|
-}
|
|
|
-
|
|
|
-// Noop send NOOP command to smtp client
|
|
|
-func (smtpClient *SMTPClient) Noop() error {
|
|
|
- return smtpClient.Client.noop()
|
|
|
-}
|
|
|
-
|
|
|
-// Quit send QUIT command to smtp client
|
|
|
-func (smtpClient *SMTPClient) Quit() error {
|
|
|
- return smtpClient.Client.quit()
|
|
|
-}
|
|
|
-
|
|
|
-// Close closes the connection
|
|
|
-func (smtpClient *SMTPClient) Close() error {
|
|
|
- return smtpClient.Client.close()
|
|
|
-}
|
|
|
-
|
|
|
-// send does the low level sending of the email
|
|
|
-func send(from string, to []string, msg string, client *SMTPClient) error {
|
|
|
- //Check if client struct is not nil
|
|
|
- if client != nil {
|
|
|
-
|
|
|
- //Check if client is not nil
|
|
|
- if client.Client != nil {
|
|
|
- var smtpSendChannel chan error
|
|
|
-
|
|
|
- // if there is a SendTimeout, setup the channel and do the send under a goroutine
|
|
|
- if client.SendTimeout != 0 {
|
|
|
- smtpSendChannel = make(chan error, 1)
|
|
|
-
|
|
|
- go func(from string, to []string, msg string, c *smtpClient) {
|
|
|
- smtpSendChannel <- sendMailProcess(from, to, msg, c)
|
|
|
- }(from, to, msg, client.Client)
|
|
|
- }
|
|
|
-
|
|
|
- if client.SendTimeout == 0 {
|
|
|
- // no SendTimeout, just fire the sendMailProcess
|
|
|
- return sendMailProcess(from, to, msg, client.Client)
|
|
|
- }
|
|
|
-
|
|
|
- // get the send result or timeout result, which ever happens first
|
|
|
- select {
|
|
|
- case sendError := <-smtpSendChannel:
|
|
|
- checkKeepAlive(client)
|
|
|
- return sendError
|
|
|
- case <-time.After(client.SendTimeout):
|
|
|
- checkKeepAlive(client)
|
|
|
- return errors.New("Mail Error: SMTP Send timed out")
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return errors.New("Mail Error: No SMTP Client Provided")
|
|
|
-}
|
|
|
-
|
|
|
-func sendMailProcess(from string, to []string, msg string, c *smtpClient) error {
|
|
|
- // Set the sender
|
|
|
- if err := c.mail(from); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- // Set the recipients
|
|
|
- for _, address := range to {
|
|
|
- if err := c.rcpt(address); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Send the data command
|
|
|
- w, err := c.data()
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- // write the message
|
|
|
- _, err = fmt.Fprint(w, msg)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- err = w.Close()
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-//check if keepAlive for close or reset
|
|
|
-func checkKeepAlive(client *SMTPClient) {
|
|
|
- if client.KeepAlive {
|
|
|
- client.Client.reset()
|
|
|
- } else {
|
|
|
- client.Client.quit()
|
|
|
- client.Client.close()
|
|
|
- }
|
|
|
-}
|