sender.go

 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}