├── .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 |
--------------------------------------------------------------------------------