internal/mail/mail.go
65 lines · 1658 bytes
1// Package mail sends transactional email over SMTP: verification codes and
2// invites. STARTTLS is used when the server offers it; PLAIN auth when
3// credentials are configured.
4package mail
5
6import (
7 "crypto/tls"
8 "fmt"
9 "net"
10 "net/smtp"
11 "strings"
12 "time"
13
14 "gitbay.org/gitbay/internal/config"
15)
16
17// Send delivers one plain-text message. cfg.Mail.SMTPHost is host:port.
18func Send(cfg config.Config, to, subject, body string) error {
19 m := cfg.Mail
20 if m.SMTPHost == "" || m.From == "" {
21 return fmt.Errorf("[mail] smtp_host and from must be configured")
22 }
23 host := m.SMTPHost
24 if !strings.Contains(host, ":") {
25 host += ":587"
26 }
27 hostname, _, _ := net.SplitHostPort(host)
28
29 msg := strings.NewReplacer("\n", "\r\n").Replace(fmt.Sprintf(
30 "From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\n\n%s\n",
31 m.From, to, subject, time.Now().Format(time.RFC1123Z), body))
32
33 c, err := smtp.Dial(host)
34 if err != nil {
35 return fmt.Errorf("smtp dial %s: %w", host, err)
36 }
37 defer c.Close()
38 if ok, _ := c.Extension("STARTTLS"); ok {
39 if err := c.StartTLS(&tls.Config{ServerName: hostname}); err != nil {
40 return fmt.Errorf("starttls: %w", err)
41 }
42 }
43 if m.SMTPUser != "" {
44 if err := c.Auth(smtp.PlainAuth("", m.SMTPUser, m.SMTPPass, hostname)); err != nil {
45 return fmt.Errorf("smtp auth: %w", err)
46 }
47 }
48 if err := c.Mail(m.From); err != nil {
49 return err
50 }
51 if err := c.Rcpt(to); err != nil {
52 return err
53 }
54 w, err := c.Data()
55 if err != nil {
56 return err
57 }
58 if _, err := w.Write([]byte(msg)); err != nil {
59 return err
60 }
61 if err := w.Close(); err != nil {
62 return err
63 }
64 return c.Quit()
65}