├── .travis.yml ├── LICENSE ├── README.md ├── email.go ├── email_test.go └── example_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.6 5 | - tip 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # PROJECT DISCONTINUED 3 | 4 | This repository only exists for archival purposes. 5 | 6 | --- 7 | 8 | 9 | # email [![Travis-CI](https://travis-ci.org/scorredoira/email.svg?branch=master)](https://travis-ci.org/scorredoira/email) [![GoDoc](https://godoc.org/github.com/scorredoira/email?status.svg)](http://godoc.org/github.com/scorredoira/email) [![Report card](https://goreportcard.com/badge/github.com/scorredoira/email)](https://goreportcard.com/report/github.com/scorredoira/email) 10 | 11 | An easy way to send emails with attachments in Go 12 | 13 | # Install 14 | 15 | ```bash 16 | go get github.com/scorredoira/email 17 | ``` 18 | 19 | # Usage 20 | 21 | ```go 22 | package email_test 23 | 24 | import ( 25 | "log" 26 | "net/mail" 27 | "net/smtp" 28 | 29 | "github.com/scorredoira/email" 30 | ) 31 | 32 | func Example() { 33 | // compose the message 34 | m := email.NewMessage("Hi", "this is the body") 35 | m.From = mail.Address{Name: "From", Address: "from@example.com"} 36 | m.To = []string{"to@example.com"} 37 | 38 | // add attachments 39 | if err := m.Attach("email.go"); err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | // add headers 44 | m.AddHeader("X-CUSTOMER-id", "xxxxx") 45 | 46 | // send it 47 | auth := smtp.PlainAuth("", "from@example.com", "pwd", "smtp.zoho.com") 48 | if err := email.Send("smtp.zoho.com:587", auth, m); err != nil { 49 | log.Fatal(err) 50 | } 51 | } 52 | ``` 53 | 54 | # Html 55 | 56 | ```go 57 | // use the html constructor 58 | m := email.NewHTMLMessage("Hi", "this is the body") 59 | ``` 60 | 61 | # Inline 62 | 63 | ```go 64 | // use Inline to display the attachment inline. 65 | if err := m.Inline("main.go"); err != nil { 66 | log.Fatal(err) 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /email.go: -------------------------------------------------------------------------------- 1 | // Package email allows to send emails with attachments. 2 | package email 3 | 4 | import ( 5 | "bytes" 6 | "encoding/base64" 7 | "fmt" 8 | "io/ioutil" 9 | "mime" 10 | "net/mail" 11 | "net/smtp" 12 | "path/filepath" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // Attachment represents an email attachment. 18 | type Attachment struct { 19 | Filename string 20 | Data []byte 21 | Inline bool 22 | } 23 | 24 | // Header represents an additional email header. 25 | type Header struct { 26 | Key string 27 | Value string 28 | } 29 | 30 | // Message represents a smtp message. 31 | type Message struct { 32 | From mail.Address 33 | To []string 34 | Cc []string 35 | Bcc []string 36 | ReplyTo string 37 | Subject string 38 | Body string 39 | BodyContentType string 40 | Headers []Header 41 | Attachments map[string]*Attachment 42 | } 43 | 44 | func (m *Message) attach(file string, inline bool) error { 45 | data, err := ioutil.ReadFile(file) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | _, filename := filepath.Split(file) 51 | 52 | m.Attachments[filename] = &Attachment{ 53 | Filename: filename, 54 | Data: data, 55 | Inline: inline, 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (m *Message) AddTo(address mail.Address) []string { 62 | m.To = append(m.To, address.String()) 63 | return m.To 64 | } 65 | 66 | func (m *Message) AddCc(address mail.Address) []string { 67 | m.Cc = append(m.Cc, address.String()) 68 | return m.Cc 69 | } 70 | 71 | func (m *Message) AddBcc(address mail.Address) []string { 72 | m.Bcc = append(m.Bcc, address.String()) 73 | return m.Bcc 74 | } 75 | 76 | // AttachBuffer attaches a binary attachment. 77 | func (m *Message) AttachBuffer(filename string, buf []byte, inline bool) error { 78 | m.Attachments[filename] = &Attachment{ 79 | Filename: filename, 80 | Data: buf, 81 | Inline: inline, 82 | } 83 | return nil 84 | } 85 | 86 | // Attach attaches a file. 87 | func (m *Message) Attach(file string) error { 88 | return m.attach(file, false) 89 | } 90 | 91 | // Inline includes a file as an inline attachment. 92 | func (m *Message) Inline(file string) error { 93 | return m.attach(file, true) 94 | } 95 | 96 | // Ads a Header to message 97 | func (m *Message) AddHeader(key string, value string) Header { 98 | newHeader := Header{Key: key, Value: value} 99 | m.Headers = append(m.Headers, newHeader) 100 | return newHeader 101 | } 102 | 103 | func newMessage(subject string, body string, bodyContentType string) *Message { 104 | m := &Message{Subject: subject, Body: body, BodyContentType: bodyContentType} 105 | 106 | m.Attachments = make(map[string]*Attachment) 107 | 108 | return m 109 | } 110 | 111 | // NewMessage returns a new Message that can compose an email with attachments 112 | func NewMessage(subject string, body string) *Message { 113 | return newMessage(subject, body, "text/plain") 114 | } 115 | 116 | // NewHTMLMessage returns a new Message that can compose an HTML email with attachments 117 | func NewHTMLMessage(subject string, body string) *Message { 118 | return newMessage(subject, body, "text/html") 119 | } 120 | 121 | // Tolist returns all the recipients of the email 122 | func (m *Message) Tolist() []string { 123 | rcptList := []string{} 124 | 125 | toList, _ := mail.ParseAddressList(strings.Join(m.To, ",")) 126 | for _, to := range toList { 127 | rcptList = append(rcptList, to.Address) 128 | } 129 | 130 | ccList, _ := mail.ParseAddressList(strings.Join(m.Cc, ",")) 131 | for _, cc := range ccList { 132 | rcptList = append(rcptList, cc.Address) 133 | } 134 | 135 | bccList, _ := mail.ParseAddressList(strings.Join(m.Bcc, ",")) 136 | for _, bcc := range bccList { 137 | rcptList = append(rcptList, bcc.Address) 138 | } 139 | 140 | return rcptList 141 | } 142 | 143 | // Bytes returns the mail data 144 | func (m *Message) Bytes() []byte { 145 | buf := bytes.NewBuffer(nil) 146 | 147 | buf.WriteString("From: " + m.From.String() + "\r\n") 148 | 149 | t := time.Now() 150 | buf.WriteString("Date: " + t.Format(time.RFC1123Z) + "\r\n") 151 | 152 | buf.WriteString("To: " + strings.Join(m.To, ",") + "\r\n") 153 | if len(m.Cc) > 0 { 154 | buf.WriteString("Cc: " + strings.Join(m.Cc, ",") + "\r\n") 155 | } 156 | 157 | //fix Encode 158 | var coder = base64.StdEncoding 159 | var subject = "=?UTF-8?B?" + coder.EncodeToString([]byte(m.Subject)) + "?=" 160 | buf.WriteString("Subject: " + subject + "\r\n") 161 | 162 | if len(m.ReplyTo) > 0 { 163 | buf.WriteString("Reply-To: " + m.ReplyTo + "\r\n") 164 | } 165 | 166 | buf.WriteString("MIME-Version: 1.0\r\n") 167 | 168 | // Add custom headers 169 | if len(m.Headers) > 0 { 170 | for _, header := range m.Headers { 171 | buf.WriteString(fmt.Sprintf("%s: %s\r\n", header.Key, header.Value)) 172 | } 173 | } 174 | 175 | boundary := "f46d043c813270fc6b04c2d223da" 176 | 177 | if len(m.Attachments) > 0 { 178 | buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\r\n") 179 | buf.WriteString("\r\n--" + boundary + "\r\n") 180 | } 181 | 182 | buf.WriteString(fmt.Sprintf("Content-Type: %s; charset=utf-8\r\n\r\n", m.BodyContentType)) 183 | buf.WriteString(m.Body) 184 | buf.WriteString("\r\n") 185 | 186 | if len(m.Attachments) > 0 { 187 | for _, attachment := range m.Attachments { 188 | buf.WriteString("\r\n\r\n--" + boundary + "\r\n") 189 | 190 | if attachment.Inline { 191 | buf.WriteString("Content-Type: message/rfc822\r\n") 192 | buf.WriteString("Content-Disposition: inline; filename=\"" + attachment.Filename + "\"\r\n\r\n") 193 | 194 | buf.Write(attachment.Data) 195 | } else { 196 | ext := filepath.Ext(attachment.Filename) 197 | mimetype := mime.TypeByExtension(ext) 198 | if mimetype != "" { 199 | mime := fmt.Sprintf("Content-Type: %s\r\n", mimetype) 200 | buf.WriteString(mime) 201 | } else { 202 | buf.WriteString("Content-Type: application/octet-stream\r\n") 203 | } 204 | buf.WriteString("Content-Transfer-Encoding: base64\r\n") 205 | 206 | buf.WriteString("Content-Disposition: attachment; filename=\"=?UTF-8?B?") 207 | buf.WriteString(coder.EncodeToString([]byte(attachment.Filename))) 208 | buf.WriteString("?=\"\r\n\r\n") 209 | 210 | b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data))) 211 | base64.StdEncoding.Encode(b, attachment.Data) 212 | 213 | // write base64 content in lines of up to 76 chars 214 | for i, l := 0, len(b); i < l; i++ { 215 | buf.WriteByte(b[i]) 216 | if (i+1)%76 == 0 { 217 | buf.WriteString("\r\n") 218 | } 219 | } 220 | } 221 | 222 | buf.WriteString("\r\n--" + boundary) 223 | } 224 | 225 | buf.WriteString("--") 226 | } 227 | 228 | return buf.Bytes() 229 | } 230 | 231 | // Send sends the message. 232 | func Send(addr string, auth smtp.Auth, m *Message) error { 233 | return smtp.SendMail(addr, auth, m.From.Address, m.Tolist(), m.Bytes()) 234 | } 235 | -------------------------------------------------------------------------------- /email_test.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import ( 4 | "net/mail" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestAttachment(t *testing.T) { 10 | m := NewMessage("Hi", "this is the body") 11 | 12 | if err := m.AttachBuffer("test.ics", []byte("test"), false); err != nil { 13 | t.Fatal(err) 14 | } 15 | 16 | if strings.Contains(string(m.Bytes()), "text/calendar") == false { 17 | t.Fatal("Issue with mailer") 18 | } 19 | } 20 | 21 | func TestHeaders(t *testing.T) { 22 | m := NewMessage("Hi", "this is the body") 23 | 24 | m.AddHeader("X-HEADER-KEY", "HEADERVAL") 25 | 26 | if strings.Contains(string(m.Bytes()), "X-HEADER-KEY: HEADERVAL\r\n") == false { 27 | t.Fatal("Could not find header in message") 28 | } 29 | } 30 | 31 | func TestAddTo(t *testing.T) { 32 | m := NewMessage("Hi", "this is the body") 33 | names := []string{"firstName", "secondName"} 34 | addresses := []string{"firstAddress", "secondAddress"} 35 | 36 | firstAddress := mail.Address{Name: names[0], Address: addresses[0]} 37 | m.AddTo(firstAddress) 38 | if m.To[0] != firstAddress.String() { 39 | t.Fatal("Incorrect first element") 40 | } 41 | 42 | secondAddress := mail.Address{Name: names[1], Address: addresses[1]} 43 | m.AddTo(secondAddress) 44 | if m.To[1] != secondAddress.String() { 45 | t.Fatal("Incorrect second element") 46 | } 47 | } 48 | 49 | func TestAddCc(t *testing.T) { 50 | m := NewMessage("Hi", "this is the body") 51 | names := []string{"firstName", "secondName"} 52 | addresses := []string{"firstAddress", "secondAddress"} 53 | 54 | firstAddress := mail.Address{Name: names[0], Address: addresses[0]} 55 | m.AddCc(firstAddress) 56 | if m.Cc[0] != firstAddress.String() { 57 | t.Fatal("Incorrect first element") 58 | } 59 | 60 | secondAddress := mail.Address{Name: names[1], Address: addresses[1]} 61 | m.AddCc(secondAddress) 62 | if m.Cc[1] != secondAddress.String() { 63 | t.Fatal("Incorrect second element") 64 | } 65 | } 66 | 67 | func TestAddBcc(t *testing.T) { 68 | m := NewMessage("Hi", "this is the body") 69 | names := []string{"firstName", "secondName"} 70 | addresses := []string{"firstAddress", "secondAddress"} 71 | 72 | firstAddress := mail.Address{Name: names[0], Address: addresses[0]} 73 | m.AddBcc(firstAddress) 74 | if m.Bcc[0] != firstAddress.String() { 75 | t.Fatal("Incorrect first element") 76 | } 77 | 78 | secondAddress := mail.Address{Name: names[1], Address: addresses[1]} 79 | m.AddBcc(secondAddress) 80 | if m.Bcc[1] != secondAddress.String() { 81 | t.Fatal("Incorrect second element") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package email_test 2 | 3 | import ( 4 | "log" 5 | "net/mail" 6 | "net/smtp" 7 | 8 | "github.com/scorredoira/email" 9 | ) 10 | 11 | func Example() { 12 | // compose the message 13 | m := email.NewMessage("Hi", "this is the body") 14 | m.From = mail.Address{Name: "From", Address: "from@example.com"} 15 | m.AddTo(mail.Address{Name: "someToName", Address: "to@example.com"}) 16 | m.AddCc(mail.Address{Name: "someCcName", Address: "cc@example.com"}) 17 | m.AddBcc(mail.Address{Name: "someBccName", Address: "bcc@example.com"}) 18 | 19 | // add attachments 20 | if err := m.Attach("email.go"); err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // add headers 25 | m.AddHeader("X-CUSTOMER-id", "xxxxx") 26 | 27 | // send it 28 | auth := smtp.PlainAuth("", "from@example.com", "pwd", "smtp.zoho.com") 29 | if err := email.Send("smtp.zoho.com:587", auth, m); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | --------------------------------------------------------------------------------