├── .gitignore ├── Gemfile ├── Guardfile ├── README.md ├── attachment.go ├── body.go ├── fixtures └── hello_world ├── header.go ├── mail.go ├── mail_test.go ├── mailer.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'capistrano', '~> 3.0', require: false, group: :development 4 | gem 'guard' 5 | gem 'guard-gotest', git: "git@github.com:jinzhu/guard-gotest.git" 6 | gem 'guard-shell' 7 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'gotest' do 2 | watch(%r{\.go$}) 3 | end 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mail 2 | 3 | (A Go Email Utility, still under development...) 4 | 5 | ## USAGE 6 | 7 | ```go 8 | mail.From("from@example.com"). 9 | To("to1@example.com", "to2@example.com"). 10 | Subject("hello"). 11 | Body("test"). 12 | Body(mail.Body{Value: "
hello world
", ContentType: "text/html; charset=UTF-8"}). 13 | Attach("report.csv"). 14 | Attach(mail.Attachment{FileName: "report2.csv", Content: filebytes}). // filebytes, _ := ioutil.ReadFile("report.csv") 15 | Send() 16 | 17 | 18 | default_from := mail.From("from@example.com") 19 | 20 | default_from.To("hello@example.com").Subject("hello world").Body("hello world").Send() 21 | default_from.To("go@example.com").Subject("go go go").Body("go go go").Send() 22 | ``` 23 | -------------------------------------------------------------------------------- /attachment.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | ) 7 | 8 | type Attachment struct { 9 | FileName string 10 | Content []byte 11 | ContentType string 12 | Inline bool 13 | } 14 | 15 | func (s Attachment) contentType() string { 16 | if len(s.ContentType) > 0 { 17 | return "Content-Type: " + s.ContentType 18 | } 19 | return "Content-Type: application/octet-stream" 20 | } 21 | 22 | func (s Attachment) contentTransferEncoding() string { 23 | return fmt.Sprintf("Content-Transfer-Encoding: %s", "base64") 24 | } 25 | 26 | func (s Attachment) contentDisposition() string { 27 | return fmt.Sprintf(`Content-Disposition: attachment; filename="%v"`, s.FileName) 28 | } 29 | 30 | func (s *Attachment) URL() string { 31 | return "" 32 | } 33 | 34 | func (s *Attachment) Encode() string { 35 | return fmt.Sprintf("%v\r\r\n%v\r\n%v\r\n\r\n%v\r\n", 36 | s.contentType(), s.contentTransferEncoding(), s.contentDisposition(), base64.StdEncoding.EncodeToString(s.Content)) 37 | } 38 | -------------------------------------------------------------------------------- /body.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import "fmt" 4 | 5 | type Body struct { 6 | Value string 7 | ContentType string 8 | mailer *Mailer 9 | } 10 | 11 | func (b Body) contentType() string { 12 | if len(b.ContentType) > 0 { 13 | return "Content-Type: " + b.ContentType 14 | } 15 | return "Content-Type: text/plain; charset=UTF-8" 16 | } 17 | 18 | func (b Body) contentTransferEncoding() string { 19 | return fmt.Sprintf("Content-Transfer-Encoding: %s", "base64") 20 | } 21 | 22 | func (b Body) Encode() string { 23 | return fmt.Sprintf("%v\r\r\n%v\r\n\r\n%v\r\n", b.contentType(), b.contentTransferEncoding(), b.Value) 24 | } 25 | -------------------------------------------------------------------------------- /fixtures/hello_world: -------------------------------------------------------------------------------- 1 | Date: Tue, 24 Dec 2013 18:20:13 +0800 2 | From: mikel@test.lindsaar.net 3 | To: you@test.lindsaar.net 4 | Message-ID: <52b95fdddb552_65d69c5e8c922d7@thinkpad.mail> 5 | Subject: This is a test email 6 | Mime-Version: 1.0 7 | Content-Type: text/plain; 8 | charset=UTF-8 9 | Content-Transfer-Encoding: 7bit 10 | 11 | hhh -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | type Header struct { 4 | Key string 5 | Value string 6 | } 7 | -------------------------------------------------------------------------------- /mail.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | type Mail struct { 4 | Charset string 5 | ContentTransferEncoding string 6 | Error error 7 | From string 8 | To []string 9 | Cc []string 10 | Bcc []string 11 | Subject string 12 | Bodys []Body 13 | Headers []Header 14 | Attachments []Attachment 15 | } 16 | 17 | func (s Mail) clone() Mail { 18 | return Mail{ 19 | Charset: s.Charset, 20 | ContentTransferEncoding: s.ContentTransferEncoding, 21 | Error: s.Error, 22 | From: s.From, 23 | To: s.To, 24 | Cc: s.Cc, 25 | Bcc: s.Bcc, 26 | Subject: s.Subject, 27 | Bodys: s.Bodys, 28 | Headers: s.Headers, 29 | Attachments: s.Attachments, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mail_test.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSendPlainText(t *testing.T) { 9 | mail := Setup() 10 | fmt.Println(mail.To("jinzhu@example.com").From("jinzhu@from.com").Subject("subject"). 11 | Body("text").Body(Body{Value: "html", ContentType: "text/html; charset=UTF-8"}). 12 | Attach("/home/jinzhu/ffff"). 13 | String()) 14 | 15 | fmt.Println(mail.To("jinzhu@example.com").From("jinzhu@from.com").Subject("subject").Body("text").String()) 16 | } 17 | -------------------------------------------------------------------------------- /mailer.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "fmt" 5 | "net/mail" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type Mailer struct { 11 | Mail 12 | Boundary string 13 | } 14 | 15 | func (s *Mailer) clone() *Mailer { 16 | return &Mailer{Mail: s.Mail.clone()} 17 | } 18 | 19 | func (m *Mailer) boundary() string { 20 | if len(m.Boundary) == 0 { 21 | m.Boundary = fmt.Sprintf("_mimepart_%v", time.Now().UnixNano()) 22 | } 23 | return m.Boundary 24 | } 25 | 26 | func (m *Mailer) crlfBoundary() string { 27 | if len(m.contentType()) == 0 { 28 | return "" 29 | } 30 | return fmt.Sprintf("\r\n--%v\r\n", m.boundary()) 31 | } 32 | 33 | func (m *Mailer) endBoundary() string { 34 | if len(m.contentType()) == 0 { 35 | return "" 36 | } 37 | return fmt.Sprintf("\r\n--%v--\r\n", m.boundary()) 38 | } 39 | 40 | func (m *Mailer) charset() string { 41 | if len(m.Mail.Charset) > 0 { 42 | return m.Mail.Charset 43 | } else { 44 | return "utf-8" 45 | } 46 | } 47 | 48 | func (m *Mailer) contentType() string { 49 | var content_type string 50 | if len(m.Mail.Attachments) > 0 || len(m.Mail.Bodys) > 1 { 51 | content_type = fmt.Sprintf(`Content-Type: multipart/alternative; boundary="%v"`, m.boundary()) 52 | } else { 53 | return "" 54 | } 55 | 56 | return fmt.Sprintf(`%v; charset="%v"`, content_type, m.charset()) 57 | } 58 | 59 | func (m *Mailer) contentTransferEncoding() string { 60 | if len(m.contentType()) == 0 { 61 | return "" 62 | } 63 | return "base64" 64 | } 65 | 66 | func (m *Mailer) String() (string, error) { 67 | message, header := "", make(map[string]string) 68 | 69 | headers := map[string][]string{ 70 | "From": []string{m.Mail.From}, 71 | "To": m.Mail.To, 72 | "Cc": m.Mail.Cc, 73 | "Bcc": m.Mail.Bcc, 74 | } 75 | 76 | for key, addresses := range headers { 77 | formated_addresses := []string{} 78 | for _, address := range addresses { 79 | parsed_addresses, err := mail.ParseAddressList(address) 80 | if err == nil { 81 | for _, parsed_address := range parsed_addresses { 82 | if len(parsed_address.Name) > 0 { 83 | formated_addresses = append(formated_addresses, fmt.Sprintf("%v <%v>", parsed_address.Name, parsed_address.Address)) 84 | } else { 85 | formated_addresses = append(formated_addresses, fmt.Sprintf("%v", parsed_address.Address)) 86 | } 87 | } 88 | } 89 | } 90 | header[key] = strings.Join(formated_addresses, ", ") 91 | } 92 | 93 | header["Subject"] = m.Mail.Subject 94 | header["MIME-Version"] = "1.0" 95 | header["Content-Type"] = m.contentType() 96 | header["Content-Transfer-Encoding"] = m.contentTransferEncoding() 97 | 98 | for k, v := range header { 99 | if len(v) > 0 { 100 | message += fmt.Sprintf("%s: %s\r\n", k, v) 101 | } 102 | } 103 | 104 | if len(m.Mail.Bodys) == 0 { 105 | m.Body("") 106 | } 107 | 108 | for _, body := range m.Mail.Bodys { 109 | message += m.crlfBoundary() 110 | message += body.Encode() + "\r\n" 111 | } 112 | 113 | for _, attachment := range m.Mail.Attachments { 114 | message += m.crlfBoundary() 115 | message += attachment.Encode() + "\r\n" 116 | } 117 | 118 | message += m.endBoundary() 119 | 120 | return message, nil 121 | } 122 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "path/filepath" 8 | ) 9 | 10 | func Setup() *Mailer { 11 | return &Mailer{} 12 | } 13 | 14 | func (m *Mailer) From(value string) *Mailer { 15 | new_mail := m.clone() 16 | new_mail.Mail.From = value 17 | return new_mail 18 | } 19 | 20 | func (m *Mailer) To(values ...string) *Mailer { 21 | new_mail := m.clone() 22 | new_mail.Mail.To = append(new_mail.Mail.To, values...) 23 | return new_mail 24 | } 25 | 26 | func (m *Mailer) Cc(values ...string) *Mailer { 27 | new_mail := m.clone() 28 | new_mail.Mail.Cc = append(new_mail.Mail.Cc, values...) 29 | return new_mail 30 | } 31 | 32 | func (m *Mailer) Bcc(values ...string) *Mailer { 33 | new_mail := m.clone() 34 | new_mail.Mail.Bcc = append(new_mail.Mail.Bcc, values...) 35 | return new_mail 36 | } 37 | 38 | func (m *Mailer) Subject(value string) *Mailer { 39 | new_mail := m.clone() 40 | new_mail.Mail.Subject = value 41 | return new_mail 42 | } 43 | 44 | func (m *Mailer) Body(values ...interface{}) *Mailer { 45 | new_mail := m.clone() 46 | for _, value := range values { 47 | if str, ok := value.(string); ok { 48 | new_mail.Mail.Bodys = append(m.Mail.Bodys, Body{Value: str, mailer: m}) 49 | } else if body, ok := value.(Body); ok { 50 | body.mailer = m 51 | new_mail.Mail.Bodys = append(m.Mail.Bodys, body) 52 | } else { 53 | new_mail.Mail.Error = errors.New(fmt.Sprint("Unknown body value", value)) 54 | } 55 | } 56 | return new_mail 57 | } 58 | 59 | func (m *Mailer) Header(key, value string) *Mailer { 60 | new_mail := m.clone() 61 | header := Header{Key: key, Value: value} 62 | new_mail.Mail.Headers = append(m.Mail.Headers, header) 63 | return new_mail 64 | } 65 | 66 | func (m *Mailer) Attach(attachment interface{}) *Mailer { 67 | new_mail := m.clone() 68 | if filename, ok := attachment.(string); ok { 69 | b, err := ioutil.ReadFile(filename) 70 | if err != nil { 71 | new_mail.Mail.Error = err 72 | } 73 | attachment = Attachment{Content: b, FileName: filepath.Base(filename)} 74 | } 75 | if attach, ok := attachment.(Attachment); ok { 76 | new_mail.Mail.Attachments = append(m.Mail.Attachments, attach) 77 | } 78 | return new_mail 79 | } 80 | 81 | func (m *Mailer) Charset(str string) *Mailer { 82 | new_mail := m.clone() 83 | new_mail.Mail.Charset = str 84 | return new_mail 85 | } 86 | 87 | func (m *Mailer) Send() error { 88 | message, err := m.String() 89 | if err == nil { 90 | fmt.Println(message) 91 | } 92 | return err 93 | } 94 | --------------------------------------------------------------------------------