krz/gitbay

A CLI-first git forge.

clone: git clone https://gitbay.org/krz/gitbay.git

main: 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}