email.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Package email allows to send emails with attachments.
  2. package emailer
  3. import (
  4. "bytes"
  5. "encoding/base64"
  6. "fmt"
  7. "io/ioutil"
  8. "mime"
  9. "net/mail"
  10. "path/filepath"
  11. "strings"
  12. "time"
  13. )
  14. // Attachment represents an email attachment.
  15. type Attachment struct {
  16. Filename string
  17. Data []byte
  18. Inline bool
  19. }
  20. // Header represents an additional email header.
  21. type Header struct {
  22. Key string
  23. Value string
  24. }
  25. // Message represents a smtp message.
  26. type Message struct {
  27. From mail.Address
  28. To []string
  29. Cc []string
  30. Bcc []string
  31. ReplyTo string
  32. Subject string
  33. Body string
  34. BodyContentType string
  35. Headers []Header
  36. Attachments map[string]*Attachment
  37. }
  38. func (m *Message) attach(file string, inline bool) error {
  39. data, err := ioutil.ReadFile(file)
  40. if err != nil {
  41. return err
  42. }
  43. _, filename := filepath.Split(file)
  44. m.Attachments[filename] = &Attachment{
  45. Filename: filename,
  46. Data: data,
  47. Inline: inline,
  48. }
  49. return nil
  50. }
  51. func (m *Message) AddTo(address mail.Address) []string {
  52. m.To = append(m.To, address.String())
  53. return m.To
  54. }
  55. func (m *Message) AddCc(address mail.Address) []string {
  56. m.Cc = append(m.Cc, address.String())
  57. return m.Cc
  58. }
  59. func (m *Message) AddBcc(address mail.Address) []string {
  60. m.Bcc = append(m.Bcc, address.String())
  61. return m.Bcc
  62. }
  63. // AttachBuffer attaches a binary attachment.
  64. func (m *Message) AttachBuffer(filename string, buf []byte, inline bool) error {
  65. m.Attachments[filename] = &Attachment{
  66. Filename: filename,
  67. Data: buf,
  68. Inline: inline,
  69. }
  70. return nil
  71. }
  72. // Attach attaches a file.
  73. func (m *Message) Attach(file string) error {
  74. return m.attach(file, false)
  75. }
  76. // Inline includes a file as an inline attachment.
  77. func (m *Message) Inline(file string) error {
  78. return m.attach(file, true)
  79. }
  80. // Ads a Header to message
  81. func (m *Message) AddHeader(key string, value string) Header {
  82. newHeader := Header{Key: key, Value: value}
  83. m.Headers = append(m.Headers, newHeader)
  84. return newHeader
  85. }
  86. func newMessage(subject string, body string, bodyContentType string) *Message {
  87. m := &Message{Subject: subject, Body: body, BodyContentType: bodyContentType}
  88. m.Attachments = make(map[string]*Attachment)
  89. return m
  90. }
  91. // NewMessage returns a new Message that can compose an email with attachments
  92. func NewMessage(subject string, body string) *Message {
  93. return newMessage(subject, body, "text/plain")
  94. }
  95. // NewHTMLMessage returns a new Message that can compose an HTML email with attachments
  96. func NewHTMLMessage(subject string, body string) *Message {
  97. return newMessage(subject, body, "text/html")
  98. }
  99. // Tolist returns all the recipients of the email
  100. func (m *Message) Tolist() []string {
  101. rcptList := []string{}
  102. toList, _ := mail.ParseAddressList(strings.Join(m.To, ","))
  103. for _, to := range toList {
  104. rcptList = append(rcptList, to.Address)
  105. }
  106. ccList, _ := mail.ParseAddressList(strings.Join(m.Cc, ","))
  107. for _, cc := range ccList {
  108. rcptList = append(rcptList, cc.Address)
  109. }
  110. bccList, _ := mail.ParseAddressList(strings.Join(m.Bcc, ","))
  111. for _, bcc := range bccList {
  112. rcptList = append(rcptList, bcc.Address)
  113. }
  114. return rcptList
  115. }
  116. // Bytes returns the mail data
  117. func (m *Message) Bytes() []byte {
  118. buf := bytes.NewBuffer(nil)
  119. buf.WriteString("From: " + m.From.String() + "\r\n")
  120. t := time.Now()
  121. buf.WriteString("Date: " + t.Format(time.RFC1123Z) + "\r\n")
  122. buf.WriteString("To: " + strings.Join(m.To, ",") + "\r\n")
  123. if len(m.Cc) > 0 {
  124. buf.WriteString("Cc: " + strings.Join(m.Cc, ",") + "\r\n")
  125. }
  126. //fix Encode
  127. var coder = base64.StdEncoding
  128. var subject = "=?UTF-8?B?" + coder.EncodeToString([]byte(m.Subject)) + "?="
  129. buf.WriteString("Subject: " + subject + "\r\n")
  130. if len(m.ReplyTo) > 0 {
  131. buf.WriteString("Reply-To: " + m.ReplyTo + "\r\n")
  132. }
  133. buf.WriteString("MIME-Version: 1.0\r\n")
  134. // Add custom headers
  135. if len(m.Headers) > 0 {
  136. for _, header := range m.Headers {
  137. buf.WriteString(fmt.Sprintf("%s: %s\r\n", header.Key, header.Value))
  138. }
  139. }
  140. boundary := "f46d043c813270fc6b04c2d223da"
  141. if len(m.Attachments) > 0 {
  142. buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\r\n")
  143. buf.WriteString("\r\n--" + boundary + "\r\n")
  144. }
  145. buf.WriteString(fmt.Sprintf("Content-Type: %s; charset=utf-8\r\n\r\n", m.BodyContentType))
  146. buf.WriteString(m.Body)
  147. buf.WriteString("\r\n")
  148. if len(m.Attachments) > 0 {
  149. for _, attachment := range m.Attachments {
  150. buf.WriteString("\r\n\r\n--" + boundary + "\r\n")
  151. if attachment.Inline {
  152. buf.WriteString("Content-Type: message/rfc822\r\n")
  153. buf.WriteString("Content-Disposition: inline; filename=\"" + attachment.Filename + "\"\r\n\r\n")
  154. buf.Write(attachment.Data)
  155. } else {
  156. ext := filepath.Ext(attachment.Filename)
  157. mimetype := mime.TypeByExtension(ext)
  158. if mimetype != "" {
  159. mime := fmt.Sprintf("Content-Type: %s\r\n", mimetype)
  160. buf.WriteString(mime)
  161. } else {
  162. buf.WriteString("Content-Type: application/octet-stream\r\n")
  163. }
  164. buf.WriteString("Content-Transfer-Encoding: base64\r\n")
  165. buf.WriteString("Content-Disposition: attachment; filename=\"=?UTF-8?B?")
  166. buf.WriteString(coder.EncodeToString([]byte(attachment.Filename)))
  167. buf.WriteString("?=\"\r\n\r\n")
  168. b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
  169. base64.StdEncoding.Encode(b, attachment.Data)
  170. // write base64 content in lines of up to 76 chars
  171. for i, l := 0, len(b); i < l; i++ {
  172. buf.WriteByte(b[i])
  173. if (i+1)%76 == 0 {
  174. buf.WriteString("\r\n")
  175. }
  176. }
  177. }
  178. buf.WriteString("\r\n--" + boundary)
  179. }
  180. buf.WriteString("--")
  181. }
  182. return buf.Bytes()
  183. }
  184. // Send sends the message.
  185. func Send(addr string, auth Auth, m *Message) error {
  186. return SendMail(addr, auth, m.From.Address, m.Tolist(), m.Bytes())
  187. }