1package sender
2
3import (
4 "bytes"
5 "crypto/rand"
6 "fmt"
7 "mime/multipart"
8 "net/smtp"
9 "net/textproto"
10 "time"
11
12 "github.com/andrinoff/email-cli/config"
13)
14
15// generateMessageID creates a unique Message-ID header.
16func generateMessageID(from string) string {
17 buf := make([]byte, 16)
18 _, err := rand.Read(buf)
19 if err != nil {
20 return fmt.Sprintf("<%d.%s>", time.Now().UnixNano(), from)
21 }
22 return fmt.Sprintf("<%x@%s>", buf, from)
23}
24
25// SendEmail now constructs a multipart message with both plain text and HTML parts.
26func SendEmail(cfg *config.Config, to []string, subject, plainBody, htmlBody string) error {
27 var smtpServer string
28 var smtpPort int
29
30 switch cfg.ServiceProvider {
31 case "gmail":
32 smtpServer = "smtp.gmail.com"
33 smtpPort = 587
34 case "icloud":
35 smtpServer = "smtp.mail.me.com"
36 smtpPort = 587
37 default:
38 return fmt.Errorf("unsupported or missing service_provider in config.json: %s", cfg.ServiceProvider)
39 }
40
41 auth := smtp.PlainAuth("", cfg.Email, cfg.Password, smtpServer)
42
43 fromHeader := cfg.Email
44 if cfg.Name != "" {
45 fromHeader = fmt.Sprintf("%s <%s>", cfg.Name, cfg.Email)
46 }
47
48 // Create a new multipart message.
49 var msg bytes.Buffer
50 mw := multipart.NewWriter(&msg)
51
52 // Set top-level headers.
53 headers := map[string]string{
54 "From": fromHeader,
55 "To": to[0],
56 "Subject": subject,
57 "Date": time.Now().Format(time.RFC1123Z),
58 "Message-ID": generateMessageID(cfg.Email),
59 "Content-Type": "multipart/alternative; boundary=" + mw.Boundary(),
60 }
61 for k, v := range headers {
62 fmt.Fprintf(&msg, "%s: %s\r\n", k, v)
63 }
64 fmt.Fprintf(&msg, "\r\n") // End of headers
65
66 // Create plain text part.
67 textHeader := textproto.MIMEHeader{}
68 textHeader.Set("Content-Type", "text/plain; charset=UTF-8")
69 textHeader.Set("Content-Transfer-Encoding", "quoted-printable")
70 part, err := mw.CreatePart(textHeader)
71 if err != nil {
72 return err
73 }
74 fmt.Fprint(part, plainBody)
75
76 // Create HTML part.
77 htmlHeader := textproto.MIMEHeader{}
78 htmlHeader.Set("Content-Type", "text/html; charset=UTF-8")
79 htmlHeader.Set("Content-Transfer-Encoding", "quoted-printable")
80 part, err = mw.CreatePart(htmlHeader)
81 if err != nil {
82 return err
83 }
84 fmt.Fprint(part, htmlBody)
85
86 // Close the multipart writer to write the final boundary.
87 if err := mw.Close(); err != nil {
88 return err
89 }
90
91 addr := fmt.Sprintf("%s:%d", smtpServer, smtpPort)
92 return smtp.SendMail(addr, auth, cfg.Email, to, msg.Bytes())
93}