email.go 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. //Email is designed to be a package providing an "email interface for humans."
  2. //Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
  3. package email
  4. import (
  5. "bytes"
  6. "io/ioutil"
  7. "log"
  8. "mime"
  9. "net/mail"
  10. "net/smtp"
  11. "net/textproto"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. )
  16. //Email is the type used for email messages
  17. type Email struct {
  18. From string
  19. To []string
  20. Bcc []string
  21. Cc []string
  22. Subject string
  23. Text string //Plaintext message (optional)
  24. Html string //Html message (optional)
  25. Headers textproto.MIMEHeader
  26. Attachments map[string]*Attachment
  27. }
  28. //NewEmail creates an Email, and returns the pointer to it.
  29. func NewEmail() *Email {
  30. return &Email{Attachments: make(map[string]*Attachment), Headers: make(textproto.MIMEHeader)}
  31. }
  32. //Attach is used to attach a file to the email.
  33. //It attempts to open the file reference by filename and, if successful, creates an Attachment.
  34. //This Attachment is then appended to the slice of Email.Attachments.
  35. //The function will then return the Attachment for reference, as well as nil for the error if successful.
  36. func (e *Email) Attach(filename string) (a *Attachment, err error) {
  37. //Check if the file exists, return any error
  38. if _, err := os.Stat(filename); os.IsNotExist(err) {
  39. log.Fatal("%s does not exist", filename)
  40. return nil, err
  41. }
  42. //Read the file, and set the appropriate headers
  43. buffer, _ := ioutil.ReadFile(filename)
  44. e.Attachments[filename] = &Attachment{
  45. Filename: filename,
  46. Header: make(textproto.MIMEHeader),
  47. Content: buffer}
  48. //Get the Content-Type to be used in the MIMEHeader
  49. ct := mime.TypeByExtension(filepath.Ext(filename))
  50. if ct != "" {
  51. e.Attachments[filename].Header.Set("Content-Type", ct)
  52. } else {
  53. //If the Content-Type is blank, set the Content-Type to "application/octet-stream"
  54. e.Attachments[filename].Header.Set("Content-Type", "application/octet-stream")
  55. }
  56. return e.Attachments[filename], nil
  57. }
  58. //Bytes converts the Email object to a []byte representation of it, including all needed MIMEHeaders, boundaries, etc.
  59. func (e *Email) Bytes() []byte {
  60. buff := bytes.Buffer{}
  61. //w := multipart.NewWriter(&buff)
  62. //Set the appropriate headers (overwriting any conflicts)
  63. //Leave out Bcc (only included in envelope headers)
  64. //TODO: Support wrapping on 76 characters (ref: MIME RFC)
  65. e.Headers.Set("To", strings.Join(e.To, ","))
  66. e.Headers.Set("Cc", strings.Join(e.Cc, ","))
  67. e.Headers.Set("From", e.From)
  68. e.Headers.Set("Subject", e.Subject)
  69. //Write the envelope headers (including any custom headers)
  70. return buff.Bytes()
  71. }
  72. //Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
  73. //This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
  74. func (e *Email) Send(addr string, a smtp.Auth) error {
  75. // Merge the To, Cc, and Bcc fields
  76. to := append(append(e.To, e.Cc...), e.Bcc...)
  77. from, err := mail.ParseAddress(e.From)
  78. if err != nil {
  79. return err
  80. }
  81. return smtp.SendMail(addr, a, from.Address, to, e.Bytes())
  82. }
  83. //Attachment is a struct representing an email attachment.
  84. //Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
  85. type Attachment struct {
  86. Filename string
  87. Header textproto.MIMEHeader
  88. Content []byte
  89. }