krz/gitbay
A CLI-first git forge.
clone: git clone https://gitbay.org/krz/gitbay.git
repo-descriptions: internal/mail/mail.go · raw
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 "fmt"
8 "net"
9 "net/smtp"
10 "strings"
11 "time"
12
13 "gitbay.org/gitbay/internal/config"
14)
15
16// Send delivers one plain-text message. cfg.Mail.SMTPHost is host:port.
17func Send(cfg config.Config, to, subject, body string) error {
18 m := cfg.Mail
19 if m.SMTPHost == "" || m.From == "" {
20 return fmt.Errorf("[mail] smtp_host and from must be configured")
21 }
22 host := m.SMTPHost
23 if !strings.Contains(host, ":") {
24 host += ":587"
25 }
26 hostname, _, _ := net.SplitHostPort(host)
27
28 msg := strings.NewReplacer("\n", "\r\n").Replace(fmt.Sprintf(
29 "From: %s\nTo: %s\nSubject: %s\nDate: %s\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\n\n%s\n",
30 m.From, to, subject, time.Now().Format(time.RFC1123Z), body))
31
32 c, err := smtp.Dial(host)
33 if err != nil {
34 return fmt.Errorf("smtp dial %s: %w", host, err)
35 }
36 defer c.Close()
37 if ok, _ := c.Extension("STARTTLS"); ok {
38 if err := c.StartTLS(nil); err != nil {
39 return fmt.Errorf("starttls: %w", err)
40 }
41 }
42 if m.SMTPUser != "" {
43 if err := c.Auth(smtp.PlainAuth("", m.SMTPUser, m.SMTPPass, hostname)); err != nil {
44 return fmt.Errorf("smtp auth: %w", err)
45 }
46 }
47 if err := c.Mail(m.From); err != nil {
48 return err
49 }
50 if err := c.Rcpt(to); err != nil {
51 return err
52 }
53 w, err := c.Data()
54 if err != nil {
55 return err
56 }
57 if _, err := w.Write([]byte(msg)); err != nil {
58 return err
59 }
60 if err := w.Close(); err != nil {
61 return err
62 }
63 return c.Quit()
64}